| 1 | import QtQuick |
| 2 | import Quickshell |
| 3 | import Quickshell.Io |
| 4 | import Quickshell.Hyprland |
| 5 | import Quickshell.Services.Pipewire |
| 6 | |
| 7 | // aurora: bar + control panel + notifications, themed from pywal16. |
| 8 | ShellRoot { |
| 9 | id: root |
| 10 | |
| 11 | property bool opened: false |
| 12 | property int tab: 0 |
| 13 | property double closedAt: 0 |
| 14 | property string lastTab: "" |
| 15 | property string lastScreen: "" |
| 16 | property var panelScreen: focusedScreen() |
| 17 | readonly property var focusedScr: focusedScreen() |
| 18 | // centered overlay windows (wallpaper picker, clipboard, ...): one at a time, on the focused monitor |
| 19 | property string overlay: "" |
| 20 | property var overlayScreen: focusedScreen() |
| 21 | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif", "settings"] |
| 22 | |
| 23 | // screen of the monitor that currently has focus (falls back to the first screen) |
| 24 | function focusedScreen() { |
| 25 | var m = Hyprland.focusedMonitor |
| 26 | var l = Quickshell.screens |
| 27 | if (m) for (var i = 0; i < l.length; i++) if (l[i].name === m.name) return l[i] |
| 28 | return l.length ? l[0] : null |
| 29 | } |
| 30 | function close() { |
| 31 | if (!opened) return |
| 32 | opened = false |
| 33 | closedAt = Date.now() |
| 34 | lastTab = tabNames[tab] |
| 35 | lastScreen = panelScreen ? panelScreen.name : "" |
| 36 | } |
| 37 | property double overlayClosedAt: 0 |
| 38 | property string lastOverlay: "" |
| 39 | function closeOverlay() { |
| 40 | if (overlay === "") return |
| 41 | lastOverlay = overlay |
| 42 | overlayClosedAt = Date.now() |
| 43 | overlay = "" |
| 44 | } |
| 45 | // scr: where to open (defaults to the focused monitor). Same instant re-open guard as togglePanel. |
| 46 | function toggleOverlay(name, scr) { |
| 47 | if (overlay === name) { closeOverlay(); return } |
| 48 | if (lastOverlay === name && Date.now() - overlayClosedAt < 250) return |
| 49 | close() |
| 50 | overlayScreen = scr ? scr : focusedScreen() |
| 51 | overlay = name |
| 52 | } |
| 53 | function openTab(name) { |
| 54 | var i = tabNames.indexOf(name) |
| 55 | if (i >= 0) tab = i |
| 56 | panelScreen = focusedScreen() |
| 57 | opened = true |
| 58 | } |
| 59 | // bar buttons: click once to open that tab, again to close. |
| 60 | // (the focus grab already closed the panel on the press, so guard against instant re-open) |
| 61 | // scr = the screen whose bar was clicked, so the panel opens on that monitor |
| 62 | function togglePanel(name, scr) { |
| 63 | var i = tabNames.indexOf(name) |
| 64 | var sn = scr ? scr.name : "" |
| 65 | if (opened && tab === i && panelScreen && panelScreen.name === sn) { close(); return } |
| 66 | if (!opened && lastTab === name && lastScreen === sn && Date.now() - closedAt < 250) return |
| 67 | tab = i |
| 68 | panelScreen = scr |
| 69 | opened = true |
| 70 | } |
| 71 | |
| 72 | // keep every pipewire node bound so volume / mute / properties are readable |
| 73 | PwObjectTracker { objects: Pipewire.nodes.values } |
| 74 | |
| 75 | Variants { |
| 76 | model: Quickshell.screens |
| 77 | Desktop {} |
| 78 | } |
| 79 | |
| 80 | Variants { |
| 81 | model: Quickshell.screens |
| 82 | Dock {} |
| 83 | } |
| 84 | |
| 85 | Variants { |
| 86 | model: Quickshell.screens |
| 87 | Bar { |
| 88 | onTogglePanel: (name, scr) => root.togglePanel(name, scr) |
| 89 | onToggleOverlay: (name, scr) => root.toggleOverlay(name, scr) |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | ControlPanel { |
| 94 | screen: root.panelScreen |
| 95 | opened: root.opened |
| 96 | tab: root.tab |
| 97 | onTabRequested: i => root.tab = i |
| 98 | onCloseRequested: root.close() |
| 99 | onOverlayRequested: name => { root.close(); root.toggleOverlay(name) } |
| 100 | } |
| 101 | |
| 102 | Overview { |
| 103 | screen: root.overlayScreen |
| 104 | opened: root.overlay === "overview" |
| 105 | onCloseRequested: root.closeOverlay() |
| 106 | } |
| 107 | |
| 108 | CalPopup { |
| 109 | screen: root.overlayScreen |
| 110 | opened: root.overlay === "cal" |
| 111 | onCloseRequested: root.closeOverlay() |
| 112 | } |
| 113 | |
| 114 | ClipWindow { |
| 115 | screen: root.overlayScreen |
| 116 | opened: root.overlay === "clip" |
| 117 | onCloseRequested: root.closeOverlay() |
| 118 | } |
| 119 | |
| 120 | WallPicker { |
| 121 | screen: root.overlayScreen |
| 122 | opened: root.overlay === "wall" |
| 123 | onCloseRequested: root.closeOverlay() |
| 124 | } |
| 125 | |
| 126 | PowerMenu { |
| 127 | screen: root.overlayScreen |
| 128 | opened: root.overlay === "power" |
| 129 | onCloseRequested: root.closeOverlay() |
| 130 | } |
| 131 | |
| 132 | NotifPopups { screen: root.focusedScr; suppressed: root.opened } |
| 133 | |
| 134 | Osd { id: osd; screen: root.focusedScr; suppressed: root.opened } |
| 135 | Connections { |
| 136 | target: Bright |
| 137 | // brightnessctl only knows the laptop panel; external monitors go through ddcutil (no readback) |
| 138 | function onStepped() { |
| 139 | var m = Hyprland.focusedMonitor |
| 140 | if (Bright.available && (!m || m.name.indexOf("eDP") === 0)) |
| 141 | osd.show(Theme.iBright, Bright.percent / 100, false) |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | IpcHandler { |
| 146 | target: "shell" |
| 147 | function toggle(): void { if (root.opened) root.close(); else root.openTab(root.tabNames[root.tab]) } |
| 148 | function open(): void { root.openTab(root.tabNames[root.tab]) } |
| 149 | function close(): void { root.close() } |
| 150 | function show(name: string): void { root.openTab(name) } |
| 151 | // `qs ipc call shell show x` is swallowed by the qs CLI (it has its own `show`), so use this |
| 152 | function tab(name: string): void { root.openTab(name) } |
| 153 | function dnd(): void { Notifs.dnd = !Notifs.dnd } |
| 154 | function shot(mode: string): void { root.close(); Shot.take(mode) } // region | window | output | ocr |
| 155 | function shotsave(): void { Shot.save = !Shot.save } |
| 156 | function overview(): void { root.toggleOverlay("overview") } |
| 157 | function cal(): void { root.toggleOverlay("cal") } |
| 158 | function clip(): void { root.toggleOverlay("clip") } |
| 159 | function wall(): void { root.toggleOverlay("wall") } |
| 160 | function power(): void { root.toggleOverlay("power") } |
| 161 | function wallrandom(): void { Wall.random() } |
| 162 | function wallset(path: string): void { Wall.apply(path) } // absolute path (setwall resolves it) |
| 163 | function rec(mode: string): void { root.close(); Rec.toggle(mode === "region" ? "region" : "screen") } // screen (default) | region |
| 164 | function caffeine(): void { Toggles.caffeine = !Toggles.caffeine } |
| 165 | function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down |
| 166 | } |
| 167 | } |