initial commit
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
// attwm's IPC socket: the window list, and the commands that act on a window.
|
||||
//
|
||||
// river 0.4 ships no window management policy of its own — an external client
|
||||
// speaking river-window-management-v1 owns it, which here is attwm. That
|
||||
// protocol has no `activate_requested` event and focus is set solely by the
|
||||
// window manager, so the `activate` request wlr-foreign-toplevel-management
|
||||
// sends on a task-list click reaches nobody at all and focus never moves. This
|
||||
// socket is the way in. See modules/WindowList.qml.
|
||||
//
|
||||
// One connection does both jobs: it subscribes to state and commands go back
|
||||
// down the same socket, so a click costs no process spawn.
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// True once attwm has answered. Everything here is inert otherwise, which
|
||||
// is what keeps the panel working unchanged on other compositors.
|
||||
readonly property bool available: link.item ? link.item.connected : false
|
||||
|
||||
// Only worth trying under river — anywhere else the socket will never
|
||||
// exist and the retry below would spend the session failing to connect.
|
||||
readonly property bool enabled: !!Quickshell.env("ATTWM_SOCKET") || (Quickshell.env("XDG_CURRENT_DESKTOP") || "").toLowerCase().includes("river")
|
||||
|
||||
// Latest state object from attwm, parsed.
|
||||
property var state: ({})
|
||||
|
||||
readonly property var outputs: root.state.outputs ?? []
|
||||
|
||||
readonly property var allWindows: {
|
||||
const all = [];
|
||||
for (const out of root.outputs)
|
||||
all.push(...out.windows);
|
||||
return all;
|
||||
}
|
||||
|
||||
function outputFor(name: string): var {
|
||||
for (const out of root.outputs)
|
||||
if (out.name === name)
|
||||
return out;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Both take a window id — the `id` field of a window in the state above,
|
||||
// which is river's own identifier for it.
|
||||
function focusWindow(id: string): void {
|
||||
root.send(`focus-window ${id}`);
|
||||
}
|
||||
|
||||
function closeWindow(id: string): void {
|
||||
root.send(`close-window ${id}`);
|
||||
}
|
||||
|
||||
function send(command: string): void {
|
||||
if (root.available)
|
||||
link.item.write(`${command}\n`);
|
||||
}
|
||||
|
||||
// A Socket that fails to connect stays dead: it reports no state change and
|
||||
// re-asserting `connected` does not make it try again. Reconnecting
|
||||
// therefore means building a new one, so the socket lives in a Loader that
|
||||
// can be thrown away. The panel and attwm start independently and either
|
||||
// may restart, so giving up after the first attempt is not an option.
|
||||
Loader {
|
||||
id: link
|
||||
|
||||
active: root.enabled
|
||||
sourceComponent: Socket {
|
||||
// attwm names its socket after the Wayland display, so several
|
||||
// river sessions can run side by side without colliding.
|
||||
path: {
|
||||
const explicit = Quickshell.env("ATTWM_SOCKET");
|
||||
if (explicit)
|
||||
return explicit;
|
||||
|
||||
const dir = Quickshell.env("XDG_RUNTIME_DIR");
|
||||
const display = Quickshell.env("WAYLAND_DISPLAY") || "wayland-0";
|
||||
return `${dir}/att_wm-${display}.sock`;
|
||||
}
|
||||
|
||||
connected: true
|
||||
|
||||
// attwm replies with a full state object immediately, so the window
|
||||
// list does not flash empty on connect.
|
||||
onConnectionStateChanged: if (connected)
|
||||
write("subscribe\n")
|
||||
|
||||
parser: SplitParser {
|
||||
onRead: line => {
|
||||
// Command acknowledgements ("ok" / "err ...") share the
|
||||
// stream with state objects; only the latter are JSON.
|
||||
if (!line.startsWith("{"))
|
||||
return;
|
||||
|
||||
try {
|
||||
root.state = JSON.parse(line);
|
||||
} catch (e) {
|
||||
console.warn("attwm: bad state line:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: root.enabled && !root.available
|
||||
|
||||
onTriggered: {
|
||||
// Drop whatever the dead connection last said, so a reconnect
|
||||
// cannot briefly show a window list from before the restart.
|
||||
root.state = ({});
|
||||
link.active = false;
|
||||
link.active = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user