add attwm support

This commit is contained in:
2026-07-26 13:48:27 +02:00
parent 9058dd7926
commit 202879eb0e
10 changed files with 404 additions and 44 deletions
+62 -35
View File
@@ -4,59 +4,86 @@ import Quickshell
import Quickshell.I3
import Quickshell.Hyprland
import "../config"
import "../services"
// Workspace switcher for a single monitor. Works on both Sway/i3 and Hyprland:
// the compositor is detected once from its instance env var, and the matching
// Quickshell backend model is used. The two backends expose the same item shape
// (name / active / focused / urgent / monitor.name / activate()), so a single
// chip renderer covers both. The `?:` on `model` only evaluates the taken
// branch, so the unused backend singleton is never instantiated (no stray IPC).
// Workspace switcher for a single monitor. Three backends, one chip renderer
// (WorkspaceChip):
//
// * Sway/i3 and Hyprland expose the same item shape (name / active / focused
// / urgent / monitor.name / activate()), so both feed the same Repeater.
// * attwm is a different model entirely -- nine fixed tags addressed by
// bitmask, several of which can be viewed at once -- so it gets its own
// Repeater over an index range.
//
// Exactly one of the two Repeaters is ever non-empty. The `?:` and `&&` guards
// only evaluate the taken branch, which is what keeps the unused backend
// singletons from being instantiated (no stray IPC) -- see Compositor.
Row {
id: root
required property var screen // ShellScreen this bar lives on
spacing: Theme.gap
// Which compositor are we under? Hyprland always exports this signature.
readonly property bool isHyprland: !!Quickshell.env("HYPRLAND_INSTANCE_SIGNATURE")
// Name of the output this bar is on, used to filter workspaces.
readonly property string monitorName: screen ? screen.name : ""
Repeater {
model: root.isHyprland ? Hyprland.workspaces : I3.workspaces
// attwm keeps tags, layout and geometry per output, so the tag row on this
// bar reflects this monitor rather than whichever one has focus.
readonly property var attwmOutput: Compositor.isAttwm
? Attwm.outputFor(monitorName)
: null
Rectangle {
id: chip
// ── Sway/i3 and Hyprland ────────────────────────────────────────────────
Repeater {
model: Compositor.isAttwm ? 0
: Compositor.isHyprland ? Hyprland.workspaces
: I3.workspaces
WorkspaceChip {
required property var modelData
// only show workspaces belonging to this monitor. Both I3Workspace
// and HyprlandWorkspace expose .monitor as an object with a .name.
visible: modelData.monitor && modelData.monitor.name === root.monitorName
width: visible ? Math.max(height, label.implicitWidth + 16) : 0
height: Theme.barHeight - Theme.gap * 2
radius: Theme.radius
readonly property bool isFocused: modelData.focused
readonly property bool isUrgent: modelData.urgent
label: modelData.name
active: modelData.active
focused: modelData.focused
urgent: modelData.urgent
color: isUrgent ? Theme.red
: isFocused ? Theme.blue
: modelData.active ? Theme.surface1
: Theme.surface0
onClicked: modelData.activate()
}
}
Text {
id: label
anchors.centerIn: parent
text: chip.modelData.name
font.family: Theme.font
font.pixelSize: Theme.fontSize
font.bold: chip.isFocused || chip.isUrgent
color: (chip.isFocused || chip.isUrgent) ? Theme.mantle : Theme.text
}
// ── attwm ───────────────────────────────────────────────────────────────
Repeater {
// 0 until the socket has produced a state object, so nothing is drawn
// if we guessed the window manager wrong.
model: Compositor.isAttwm ? Attwm.tagCount : 0
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: chip.modelData.activate()
WorkspaceChip {
id: tag
required property int index
readonly property bool viewing: Attwm.tagActive(root.attwmOutput, index)
readonly property bool occupied: Attwm.tagOccupied(root.attwmOutput, index)
label: Attwm.tagNames[index] ?? (index + 1)
active: viewing
// Tags are per output and several outputs can view a tag at once;
// only the focused output's get the focused colour.
focused: viewing && !!root.attwmOutput && root.attwmOutput.focused
dimmed: !viewing && !occupied
dot: occupied && !viewing
// dwm's mouse bindings: view / toggle into view / send window there.
onClicked: button => {
const n = index + 1;
if (button === Qt.RightButton)
Attwm.send(`toggle-view ${n}`);
else if (button === Qt.MiddleButton)
Attwm.send(`tag ${n}`);
else
Attwm.send(`view ${n}`);
}
}
}