add attwm support
This commit is contained in:
+15
-5
@@ -35,7 +35,7 @@ PanelWindow {
|
||||
// depend on which row anything sits in, so this can't bind-loop.
|
||||
readonly property real neededWidth:
|
||||
(Theme.gap + 2) * 2 // outer margins
|
||||
+ workspaces.implicitWidth
|
||||
+ leftRow.implicitWidth
|
||||
+ Theme.gap * 4 // breathing room between clusters
|
||||
+ metricsRow.implicitWidth
|
||||
+ Theme.gap
|
||||
@@ -50,13 +50,23 @@ PanelWindow {
|
||||
color: Theme.barColor
|
||||
}
|
||||
|
||||
// row 1 left: workspaces
|
||||
Workspaces {
|
||||
id: workspaces
|
||||
// row 1 left: workspaces, plus the layout symbol under attwm (hidden
|
||||
// otherwise, and a hidden item takes no space in a RowLayout)
|
||||
RowLayout {
|
||||
id: leftRow
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.gap + 2
|
||||
y: (Theme.barHeight - height) / 2
|
||||
screen: panel.modelData
|
||||
spacing: Theme.gap
|
||||
|
||||
Workspaces {
|
||||
screen: panel.modelData
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
LayoutSymbol {
|
||||
screen: panel.modelData
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
// row 1 right: clock, tray
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../config"
|
||||
import "../services"
|
||||
|
||||
// attwm's layout indicator: "[]=" master, "[M]" monocle, "|||" tabbed. Click
|
||||
// cycles forward, right-click back -- dwm's bar binding.
|
||||
//
|
||||
// Layout is per output, so this shows the monitor the bar is on, not the
|
||||
// focused one. Hidden entirely under any other window manager; the `&&` guards
|
||||
// short-circuit before Attwm is touched, so the socket is never opened there.
|
||||
MouseArea {
|
||||
id: root
|
||||
required property var screen
|
||||
|
||||
readonly property var output: Compositor.isAttwm
|
||||
? Attwm.outputFor(screen ? screen.name : "")
|
||||
: null
|
||||
|
||||
visible: Compositor.isAttwm && Attwm.connected && !!output
|
||||
implicitWidth: pill.implicitWidth
|
||||
implicitHeight: pill.implicitHeight
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: event => Attwm.send(event.button === Qt.RightButton
|
||||
? "cycle-layout prev"
|
||||
: "cycle-layout next")
|
||||
|
||||
Pill {
|
||||
id: pill
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: root.output ? root.output.layout_symbol : ""
|
||||
font.family: Theme.monoFont
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.subtext
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import QtQuick
|
||||
import "../config"
|
||||
|
||||
// One workspace/tag chip. Every backend renders through this, so the left end
|
||||
// of the bar looks the same whichever window manager is driving it.
|
||||
//
|
||||
// `dimmed` and `dot` are presentational rather than semantic on purpose: what
|
||||
// counts as "empty" differs per backend (i3 only lists workspaces that exist,
|
||||
// attwm always publishes all nine tags), so each call site decides and this
|
||||
// stays a plain renderer.
|
||||
Rectangle {
|
||||
id: chip
|
||||
|
||||
property string label
|
||||
property bool active: false // shown on this monitor
|
||||
property bool focused: false // shown *and* holding input focus
|
||||
property bool urgent: false
|
||||
property bool dimmed: false // exists, but nothing on it
|
||||
property bool dot: false // has windows but isn't shown (dwm's corner square)
|
||||
|
||||
signal clicked(int button)
|
||||
|
||||
implicitWidth: Math.max(implicitHeight, text.implicitWidth + 16)
|
||||
implicitHeight: Theme.barHeight - Theme.gap * 2
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
radius: Theme.radius
|
||||
|
||||
color: urgent ? Theme.red
|
||||
: focused ? Theme.blue
|
||||
: active ? Theme.surface1
|
||||
: Theme.surface0
|
||||
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: chip.label
|
||||
font.family: Theme.font
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.bold: chip.focused || chip.urgent
|
||||
color: (chip.focused || chip.urgent) ? Theme.mantle
|
||||
: chip.dimmed ? Theme.overlay
|
||||
: Theme.text
|
||||
}
|
||||
|
||||
// dwm puts this in the tag's top-left corner, but the chip is only as wide
|
||||
// as one digit -- a corner mark lands on the glyph. Underlining it says the
|
||||
// same thing and suits the rounded chips.
|
||||
Rectangle {
|
||||
visible: chip.dot
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 4
|
||||
width: 6
|
||||
height: 2
|
||||
radius: 1
|
||||
color: Theme.blue
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onClicked: event => chip.clicked(event.button)
|
||||
}
|
||||
}
|
||||
+62
-35
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user