Add KineticWE Layouts plugin (#252)
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
--!nonstrict
|
||||
-- KineticWE layout bridge service.
|
||||
--
|
||||
-- Watches the compositor's /Tiling DBus interface (plus virtual-desktop
|
||||
-- switches, which implicitly change the "current" layout) and publishes a
|
||||
-- single plain-data table on noctalia.state key "layout" for the widget and
|
||||
-- panel entries:
|
||||
--
|
||||
-- { ok = true, current = "Columns", enabled = { "MasterStack", ... },
|
||||
-- qdbus = "qdbus-qt6" }
|
||||
-- { ok = false, reason = "missing-qdbus" | "missing-monitor" | "missing-interface" }
|
||||
--
|
||||
-- "current" is the kind string for the active output's current desktop, or
|
||||
-- "" when native tiling is disabled.
|
||||
|
||||
local KINDS = {
|
||||
MasterStack = true,
|
||||
Stacked = true,
|
||||
CenterTile = true,
|
||||
Monocle = true,
|
||||
AutoGrid = true,
|
||||
Columns = true,
|
||||
}
|
||||
|
||||
local QDBUS = nil
|
||||
local refreshing = false
|
||||
local refreshQueued = false
|
||||
local lastPublished = nil
|
||||
|
||||
local function findQdbus()
|
||||
for _, name in ipairs({ "qdbus-qt6", "qdbus6", "qdbus" }) do
|
||||
if noctalia.commandExists(name) then
|
||||
return name
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function publish(state)
|
||||
noctalia.state.set("layout", state)
|
||||
lastPublished = state
|
||||
end
|
||||
|
||||
-- qdbus prints a QStringList reply as { "MasterStack", "Stacked", ... }.
|
||||
-- Some builds print bare words instead, so fall back to word splitting and
|
||||
-- keep only tokens that are real layout kinds.
|
||||
local function parseKindList(out)
|
||||
local items, seen = {}, {}
|
||||
local function add(s)
|
||||
if KINDS[s] and not seen[s] then
|
||||
seen[s] = true
|
||||
table.insert(items, s)
|
||||
end
|
||||
end
|
||||
for s in string.gmatch(out, '"([^"]+)"') do
|
||||
add(s)
|
||||
end
|
||||
if #items == 0 then
|
||||
for s in string.gmatch(out, "[%a]+") do
|
||||
add(s)
|
||||
end
|
||||
end
|
||||
return items
|
||||
end
|
||||
|
||||
local function finishRefresh()
|
||||
refreshing = false
|
||||
if refreshQueued then
|
||||
refreshQueued = false
|
||||
refresh()
|
||||
end
|
||||
end
|
||||
|
||||
function refresh()
|
||||
if refreshing then
|
||||
-- Coalesce signal bursts (e.g. one per desktop on a reconcile pass)
|
||||
-- into a single follow-up query.
|
||||
refreshQueued = true
|
||||
return
|
||||
end
|
||||
refreshing = true
|
||||
|
||||
if QDBUS == nil then
|
||||
QDBUS = findQdbus()
|
||||
if QDBUS == nil then
|
||||
publish({ ok = false, reason = "missing-qdbus" })
|
||||
finishRefresh()
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
noctalia.runAsync(QDBUS .. " org.kde.KWin /Tiling org.kde.KWin.Tiling.currentLayout", function(res)
|
||||
if res.exitCode ~= 0 then
|
||||
publish({ ok = false, reason = "missing-interface" })
|
||||
finishRefresh()
|
||||
return
|
||||
end
|
||||
local current = noctalia.string.trim(res.stdout)
|
||||
noctalia.runAsync(QDBUS .. " org.kde.KWin /Tiling org.kde.KWin.Tiling.enabledLayouts", function(res2)
|
||||
if res2.exitCode ~= 0 then
|
||||
publish({ ok = false, reason = "missing-interface" })
|
||||
finishRefresh()
|
||||
return
|
||||
end
|
||||
local enabled = parseKindList(res2.stdout)
|
||||
local unchanged = lastPublished
|
||||
and lastPublished.ok
|
||||
and lastPublished.current == current
|
||||
and table.concat(lastPublished.enabled, ",") == table.concat(enabled, ",")
|
||||
if not unchanged then
|
||||
publish({ ok = true, current = current, enabled = enabled, qdbus = QDBUS })
|
||||
end
|
||||
finishRefresh()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
local function onMonitorLine(line)
|
||||
-- Any of: member=layoutChanged, member=enabledLayoutsChanged,
|
||||
-- member=currentChanged (desktop switch). Re-query rather than parse
|
||||
-- arguments: it is cheap and always yields a consistent snapshot.
|
||||
if string.find(line, "member=") then
|
||||
refresh()
|
||||
end
|
||||
end
|
||||
|
||||
local function startMonitor()
|
||||
if not noctalia.commandExists("dbus-monitor") then
|
||||
noctalia.log("dbus-monitor not found; falling back to polling only")
|
||||
return
|
||||
end
|
||||
noctalia.runStream(
|
||||
"dbus-monitor --session"
|
||||
.. " \"type='signal',interface='org.kde.KWin.Tiling'\""
|
||||
.. " \"type='signal',interface='org.kde.KWin.VirtualDesktopManager',member='currentChanged'\"",
|
||||
onMonitorLine
|
||||
)
|
||||
end
|
||||
|
||||
-- Startup: query immediately, then keep a slow poll as a self-healing net
|
||||
-- (recovers when the monitor dies or the compositor gains /Tiling after a
|
||||
-- rebuild). Signal-driven refreshes make updates feel instant.
|
||||
refresh()
|
||||
startMonitor()
|
||||
noctalia.setUpdateInterval(5000)
|
||||
|
||||
function update()
|
||||
refresh()
|
||||
end
|
||||
Reference in New Issue
Block a user