Krolyxon
Branch master

Branches

Refs →

aurora.configquickshellaurorashell.qml

246 lines · 9.7 KB · QML

1//@ pragma UseQApplication
2// ^ tray menus (SystemTray display()) are platform menus: they need a QApplication
3//@ pragma IconTheme Papirus-Dark
4// ^ same as gtk-3.0: notify-send -i names (drive-removable-media, network-wireless, ...) resolve through it
5import QtQuick
6import Quickshell
7import Quickshell.Io
8import Quickshell.Hyprland
9import Quickshell.Services.Pipewire
10
11// aurora: bar + control panel + notifications, themed from matugen.
12ShellRoot {
13 id: root
14
15 property bool opened: false
16 property int tab: 0
17 property double closedAt: 0
18 property string lastTab: ""
19 property string lastScreen: ""
20 property var panelScreen: focusedScreen()
21 readonly property var focusedScr: focusedScreen()
22 // centered overlay windows (wallpaper picker, clipboard, ...): one at a time, on the focused monitor
23 property string overlay: ""
24 property bool barShown: true // `bar` IPC (SUPER+B); not persisted, a restart shows it again
25 property var overlayScreen: focusedScreen()
26 // touch singletons that must start with the shell (Idle creates ~/.config/aurora/hypridle.conf)
27 readonly property string idleFile: Idle.file
28 readonly property bool batCare: Battery.ready // charge limit + low battery warnings
29 readonly property bool devWatch: Devices.scanned // removable drive notifications
30 readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif"]
31 // settings app (a normal window): open state + shown section
32 property bool settingsOpen: false
33 property string settingsSection: "network"
34
35 // screen of the monitor that currently has focus (falls back to the first screen)
36 function focusedScreen() {
37 var m = Hyprland.focusedMonitor
38 var l = Quickshell.screens
39 if (m) for (var i = 0; i < l.length; i++) if (l[i].name === m.name) return l[i]
40 return l.length ? l[0] : null
41 }
42 function close() {
43 if (!opened) return
44 opened = false
45 closedAt = Date.now()
46 lastTab = tabNames[tab]
47 lastScreen = panelScreen ? panelScreen.name : ""
48 }
49 property double overlayClosedAt: 0
50 property string lastOverlay: ""
51 function closeOverlay() {
52 if (overlay === "") return
53 lastOverlay = overlay
54 overlayClosedAt = Date.now()
55 overlay = ""
56 }
57 // scr: where to open (defaults to the focused monitor). Same instant re-open guard as togglePanel.
58 function toggleOverlay(name, scr) {
59 if (overlay === name) { closeOverlay(); return }
60 if (lastOverlay === name && Date.now() - overlayClosedAt < 250) return
61 close()
62 overlayScreen = scr ? scr : focusedScreen()
63 overlay = name
64 }
65 // screenshots: whatever aurora shows must slide away before grim freezes the screen
66 function shot(mode) {
67 if (mode === "edit") { Shot.edit(); return } // the last shot in the editor
68 // also when one closed a moment ago (the runner closes itself before running "screenshot")
69 var gone = Math.max(closedAt, overlayClosedAt)
70 var wait = opened || overlay !== "" ? Theme.t + 60 : Math.max(0, Theme.t + 60 - (Date.now() - gone))
71 close()
72 closeOverlay()
73 if (mode === "ocr") Shot.ocr(wait)
74 else Shot.open(mode, wait)
75 }
76 // section "" = toggle the window on its last section; a name opens (or switches) to it
77 function settings(section) {
78 if (section === "" || section === undefined) { settingsOpen = !settingsOpen; return }
79 settingsSection = section
80 settingsOpen = true
81 }
82 function openTab(name) {
83 if (name === "settings") { settings("appearance"); return } // old panel tab lives in the app now
84 var i = tabNames.indexOf(name)
85 if (i >= 0) tab = i
86 panelScreen = focusedScreen()
87 opened = true
88 }
89 // bar buttons: click once to open that tab, again to close.
90 // (the focus grab already closed the panel on the press, so guard against instant re-open)
91 // scr = the screen whose bar was clicked, so the panel opens on that monitor
92 function togglePanel(name, scr) {
93 var i = tabNames.indexOf(name)
94 var sn = scr ? scr.name : ""
95 if (opened && tab === i && panelScreen && panelScreen.name === sn) { close(); return }
96 if (!opened && lastTab === name && lastScreen === sn && Date.now() - closedAt < 250) return
97 tab = i
98 panelScreen = scr
99 opened = true
100 }
101
102 // keep every pipewire node bound so volume / mute / properties are readable
103 PwObjectTracker { objects: Pipewire.nodes.values }
104
105 // the wallpaper (Background layer), then the home screen on top of it (Bottom layer)
106 Variants {
107 model: Quickshell.screens
108 Back {}
109 }
110
111 Variants {
112 model: Quickshell.screens
113 Desktop {}
114 }
115
116 Variants {
117 model: Quickshell.screens
118 Dock {}
119 }
120
121 Variants {
122 model: Quickshell.screens
123 Bar {
124 visible: root.barShown
125 onTogglePanel: (name, scr) => root.togglePanel(name, scr)
126 onToggleOverlay: (name, scr) => root.toggleOverlay(name, scr)
127 }
128 }
129
130 ControlPanel {
131 screen: root.panelScreen
132 opened: root.opened
133 tab: root.tab
134 onTabRequested: i => root.tab = i
135 onCloseRequested: root.close()
136 onOverlayRequested: name => { root.close(); root.toggleOverlay(name) }
137 onSettingsRequested: { root.close(); root.settingsOpen = true }
138 }
139
140 Overview {
141 screen: root.overlayScreen
142 opened: root.overlay === "overview"
143 onCloseRequested: root.closeOverlay()
144 }
145
146 CalPopup {
147 screen: root.overlayScreen
148 opened: root.overlay === "cal"
149 onCloseRequested: root.closeOverlay()
150 }
151
152 ClipWindow {
153 screen: root.overlayScreen
154 opened: root.overlay === "clip"
155 onCloseRequested: root.closeOverlay()
156 }
157
158 CharPicker {
159 screen: root.overlayScreen
160 opened: root.overlay === "chars"
161 onCloseRequested: root.closeOverlay()
162 }
163
164 WallPicker {
165 screen: root.overlayScreen
166 opened: root.overlay === "wall"
167 onCloseRequested: root.closeOverlay()
168 }
169
170 // polkit password prompt (Agents), on the focused monitor
171 AuthPrompt { screen: root.focusedScr }
172
173 ShotAim {}
174 ShotEditor {}
175
176 PowerMenu {
177 screen: root.overlayScreen
178 opened: root.overlay === "power"
179 onCloseRequested: root.closeOverlay()
180 }
181
182 Runner {
183 id: runner
184 screen: root.overlayScreen
185 opened: root.overlay === "runner"
186 shell: root
187 onCloseRequested: root.closeOverlay()
188 onOpenRequested: {
189 root.close()
190 if (root.overlay === "runner") return // already open (run mode): it just switched to the list
191 root.overlayScreen = root.focusedScreen()
192 root.overlay = "runner"
193 }
194 }
195
196 SettingsApp {
197 opened: root.settingsOpen
198 section: root.settingsSection
199 onCloseRequested: root.settingsOpen = false
200 onSectionRequested: name => root.settingsSection = name
201 onOverlayRequested: name => root.toggleOverlay(name)
202 }
203
204 NotifPopups { screen: root.focusedScr; suppressed: root.opened }
205
206 Osd { id: osd; screen: root.focusedScr; suppressed: root.opened }
207 Connections {
208 target: Bright
209 // brightnessctl only knows the laptop panel; external monitors go through ddcutil (no readback)
210 function onStepped() {
211 var m = Hyprland.focusedMonitor
212 if (Bright.available && (!m || m.name.indexOf("eDP") === 0))
213 osd.show(Theme.iBright, Bright.percent / 100, false)
214 }
215 }
216
217 IpcHandler {
218 target: "shell"
219 function toggle(): void { if (root.opened) root.close(); else root.openTab(root.tabNames[root.tab]) }
220 function open(): void { root.openTab(root.tabNames[root.tab]) }
221 function close(): void { root.close() }
222 function show(name: string): void { root.openTab(name) }
223 // `qs ipc call shell show x` is swallowed by the qs CLI (it has its own `show`), so use this
224 function tab(name: string): void { root.openTab(name) }
225 function dnd(): void { Notifs.dnd = !Notifs.dnd }
226 // screenshot viewfinder in that mode (region | window | screen | record), ocr (the `ocr` script), edit (the last shot)
227 function shot(mode: string): void { root.shot(mode) }
228 function overview(): void { root.toggleOverlay("overview") }
229 function cal(): void { root.toggleOverlay("cal") }
230 function clip(): void { root.toggleOverlay("clip") }
231 function chars(): void { root.toggleOverlay("chars") } // glyph picker (SUPER+grave)
232 function wall(): void { root.toggleOverlay("wall") }
233 function power(): void { root.toggleOverlay("power") }
234 function run(): void { root.toggleOverlay("runner") }
235 function dmenu(dir: string): void { runner.startDmenu(dir) } // used by ~/.local/bin/aurora-run -d
236 function bar(): void { root.barShown = !root.barShown }
237 function wallrandom(): void { Wall.random() }
238 function wallset(path: string): void { Wall.apply(path) } // absolute path (setwall resolves it)
239 function rec(mode: string): void { root.close(); Rec.toggle(mode === "region" ? "region" : "screen") } // screen (default) | region
240 function settings(section: string): void { root.settings(section) } // "" = toggle
241 function caffeine(): void { Toggles.caffeine = !Toggles.caffeine }
242 function portal(): void { Net.signIn() } // open the captive portal login page (or say we're online)
243 function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down
244 function updates(): void { Updates.finished() } // re-check pending updates (the upgrade terminal calls it)
245 }
246}