add attwm support
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Singleton service wrapping attwm's IPC socket.
|
||||
//
|
||||
// Adapted from the reference client that ships with attwm (quickshell/Attwm.qml
|
||||
// in that tree); keep the two in step when the state schema changes.
|
||||
//
|
||||
// One connection does both jobs: it subscribes to state, and commands are
|
||||
// written back down the same socket. attwm keeps subscribers connected after a
|
||||
// command precisely so a bar does not need a second connection or a process
|
||||
// spawn per click.
|
||||
//
|
||||
// Only instantiate this under attwm -- see Compositor.isAttwm. Merely reading a
|
||||
// property here opens the socket and starts the reconnect timer.
|
||||
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Latest state from attwm, parsed. Left untouched when the connection
|
||||
// drops, so a restart of attwm freezes the bar on the last known state
|
||||
// rather than blanking it for the second it takes to reconnect.
|
||||
property var state: ({})
|
||||
|
||||
readonly property var outputs: state.outputs ?? []
|
||||
readonly property var tagNames: state.tag_names ?? []
|
||||
readonly property bool locked: state.locked ?? false
|
||||
readonly property bool connected: socket.connected
|
||||
|
||||
// 0 until the first state object lands: the tag row is driven by this, and
|
||||
// a default of 9 would paint a row of dead tags before (or without) attwm.
|
||||
readonly property int tagCount: state.tag_count ?? 0
|
||||
|
||||
// The output the user is currently working on.
|
||||
readonly property var focusedOutput: {
|
||||
for (const o of outputs)
|
||||
if (o.focused) return o;
|
||||
return outputs.length > 0 ? outputs[0] : null;
|
||||
}
|
||||
|
||||
readonly property string focusedTitle: {
|
||||
if (!focusedOutput) return "";
|
||||
for (const w of focusedOutput.windows)
|
||||
if (w.focused) return w.title;
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Look up an output by name, so a multi-monitor bar can show per-screen
|
||||
/// state rather than the focused output's.
|
||||
function outputFor(name) {
|
||||
for (const o of outputs)
|
||||
if (o.name === name) return o;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Send a command. Same grammar as attwmctl, e.g. send("view 3").
|
||||
function send(command) {
|
||||
if (socket.connected) socket.write(command + "\n");
|
||||
}
|
||||
|
||||
// A tag is "occupied" if some window carries it, mirroring dwm's bar.
|
||||
function tagOccupied(output, index) {
|
||||
return output ? (output.occupied & (1 << index)) !== 0 : false;
|
||||
}
|
||||
|
||||
function tagActive(output, index) {
|
||||
return output ? (output.tags & (1 << index)) !== 0 : false;
|
||||
}
|
||||
|
||||
Socket {
|
||||
id: 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}/attwm-${display}.sock`;
|
||||
}
|
||||
|
||||
connected: true
|
||||
|
||||
onConnectionStateChanged: {
|
||||
if (connected) {
|
||||
// attwm replies with a full state object immediately, so there
|
||||
// is no empty-bar flash on connect.
|
||||
socket.write("subscribe\n");
|
||||
} else {
|
||||
reconnect.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attwm may be restarted independently of the bar; keep trying.
|
||||
Timer {
|
||||
id: reconnect
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (socket.connected) {
|
||||
running = false;
|
||||
} else {
|
||||
socket.connected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user