Krolyxon
Commit f0edd5d

Branches

Refs →

auroraf0edd5d

aurora: add autohide dock (pinned + per-monitor running apps)

Author
krolyxon
Date
2026-10-17 15:08 +0530
Parent
53e4da3
Commit
f0edd5df3b36f364ea0603fbd537c5b42aa0df51

+183 −0

2 files changed

  1. A .config/quickshell/aurora/Dock.qml +178 −0
  2. M .config/quickshell/aurora/shell.qml +5 −0
A .config/quickshell/aurora/Dock.qml View
@@ -0,0 +1,178 @@
1+import QtQuick
2+import Quickshell
3+import Quickshell.Wayland
4+import Quickshell.Hyprland
5+
6+// Autohide dock (per monitor): touch the bottom edge to slide it up.
7+// Pinned apps (Settings.dockPinned) + apps with windows on this monitor's workspaces.
8+// Left click: focus (cycles through that app's windows) or launch. Middle: new instance. Right: pin/unpin.
9+PanelWindow {
10+ id: win
11+ required property var modelData
12+ screen: modelData
13+ visible: Settings.dock
14+
15+ readonly property var hmon: Hyprland.monitorFor(modelData)
16+ readonly property int size: 40
17+ property bool shown: false
18+
19+ anchors.bottom: true
20+ implicitWidth: Math.max(160, row.implicitWidth + 16)
21+ implicitHeight: size + 16
22+ exclusiveZone: 0
23+ color: "transparent"
24+ // hidden: only a 2px strip at the very bottom catches the mouse; shown: the whole dock
25+ mask: Region { item: win.shown ? panel : strip }
26+ WlrLayershell.layer: WlrLayer.Top
27+ WlrLayershell.namespace: "aurora-dock"
28+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
29+
30+ Timer { id: hide; interval: 600; onTriggered: win.shown = false }
31+
32+ function appId(t) {
33+ if (t.wayland && t.wayland.appId) return t.wayland.appId
34+ return t.lastIpcObject && t.lastIpcObject.class ? t.lastIpcObject.class : ""
35+ }
36+ function entryFor(id) {
37+ return DesktopEntries.byId(id) || DesktopEntries.heuristicLookup(id)
38+ }
39+ // [{id, entry, wins: [HyprlandToplevel]}], pinned first (in pin order), then running by first seen
40+ readonly property var apps: {
41+ var out = [], byId = {}, pins = Settings.dockPinned
42+ for (var i = 0; i < pins.length; i++) {
43+ var p = { id: pins[i], entry: entryFor(pins[i]), wins: [], pinned: true }
44+ byId[pins[i].toLowerCase()] = p
45+ out.push(p)
46+ }
47+ var l = Hyprland.toplevels.values
48+ for (var j = 0; j < l.length; j++) {
49+ var t = l[j]
50+ if (!t.workspace || !t.workspace.monitor || t.workspace.monitor !== hmon) continue
51+ var id = appId(t)
52+ if (id === "") continue
53+ var e = entryFor(id)
54+ // match pins by id, or by the desktop entry they resolve to (e.g. "footclient" -> foot)
55+ var key = id.toLowerCase()
56+ var a = byId[key]
57+ if (!a && e) for (var k = 0; k < out.length; k++) if (out[k].entry === e) { a = out[k]; break }
58+ if (!a) {
59+ a = { id: id, entry: e, wins: [], pinned: false }
60+ byId[key] = a
61+ out.push(a)
62+ }
63+ a.wins.push(t)
64+ }
65+ return out
66+ }
67+
68+ function activate(a) {
69+ if (a.wins.length === 0) { if (a.entry) a.entry.execute(); return }
70+ // focus the next window of that app after the active one (cycles)
71+ var act = Hyprland.activeToplevel, i = -1
72+ for (var k = 0; k < a.wins.length; k++) if (act && a.wins[k].address === act.address) i = k
73+ Hypr.focusWindow(a.wins[(i + 1) % a.wins.length].address)
74+ }
75+
76+ Item {
77+ id: strip
78+ anchors.bottom: parent.bottom
79+ width: parent.width
80+ height: 2
81+ }
82+
83+ MouseArea {
84+ anchors.fill: parent
85+ hoverEnabled: true
86+ onEntered: { hide.stop(); win.shown = true }
87+ onExited: hide.restart()
88+
89+ Rectangle {
90+ id: panel
91+ width: parent.width
92+ height: parent.height - 6
93+ y: win.shown ? 0 : parent.height
94+ Behavior on y { NumberAnimation { duration: 140; easing.type: Easing.OutCubic } }
95+ color: Theme.bgA
96+ border.width: 1
97+ border.color: Theme.accent
98+
99+ Row {
100+ id: row
101+ anchors.centerIn: parent
102+ spacing: 4
103+
104+ Repeater {
105+ model: win.apps
106+ delegate: Item {
107+ id: cell
108+ required property var modelData
109+ readonly property bool running: modelData.wins.length > 0
110+ readonly property bool focused: {
111+ var act = Hyprland.activeToplevel
112+ if (!act) return false
113+ for (var k = 0; k < modelData.wins.length; k++)
114+ if (modelData.wins[k].address === act.address) return true
115+ return false
116+ }
117+ width: win.size
118+ height: win.size
119+
120+ Rectangle {
121+ anchors.fill: parent
122+ color: cell.focused ? Theme.accentDim : (cm.containsMouse ? Theme.surfaceHi : "transparent")
123+ }
124+ Image {
125+ id: ico
126+ anchors.centerIn: parent
127+ width: win.size - 12
128+ height: width
129+ sourceSize.width: 64
130+ sourceSize.height: 64
131+ asynchronous: true
132+ source: Quickshell.iconPath(cell.modelData.entry ? cell.modelData.entry.icon : cell.modelData.id.toLowerCase(), true)
133+ }
134+ Text { // icon not in the theme: first letter of the app name
135+ anchors.centerIn: parent
136+ visible: ico.status !== Image.Ready
137+ text: (cell.modelData.entry ? cell.modelData.entry.name : cell.modelData.id).charAt(0).toUpperCase()
138+ color: Theme.accent
139+ font.family: Theme.font
140+ font.pixelSize: 18
141+ font.bold: true
142+ }
143+ Rectangle { // running marker: accent underline, wider with more windows
144+ visible: cell.running
145+ anchors.bottom: parent.bottom
146+ anchors.horizontalCenter: parent.horizontalCenter
147+ width: Math.min(parent.width - 8, 8 + (cell.modelData.wins.length - 1) * 8)
148+ height: 2
149+ color: Theme.accent
150+ }
151+ Rectangle { // pinned marker: small dot top-right
152+ visible: cell.modelData.pinned
153+ anchors.top: parent.top
154+ anchors.right: parent.right
155+ anchors.margins: 3
156+ width: 3
157+ height: 3
158+ color: Theme.dim
159+ }
160+ MouseArea {
161+ id: cm
162+ anchors.fill: parent
163+ hoverEnabled: true
164+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
165+ cursorShape: Qt.PointingHandCursor
166+ onEntered: { hide.stop(); win.shown = true }
167+ onClicked: m => {
168+ if (m.button === Qt.RightButton) Settings.togglePin(cell.modelData.id)
169+ else if (m.button === Qt.MiddleButton) { if (cell.modelData.entry) cell.modelData.entry.execute() }
170+ else win.activate(cell.modelData)
171+ }
172+ }
173+ }
174+ }
175+ }
176+ }
177+ }
178+}
M .config/quickshell/aurora/shell.qml View
@@ -77,6 +77,11 @@ ShellRoot {
7777 Desktop {}
7878 }
7979
80+ Variants {
81+ model: Quickshell.screens
82+ Dock {}
83+ }
84+
8085 Variants {
8186 model: Quickshell.screens
8287 Bar {