auroraa5716dc
aurora: add caffeine, airplane, colour picker and screen recording toggles
+162 −1
7 files changed
- M .config/quickshell/aurora/Bar.qml +22 −0
- M .config/quickshell/aurora/ControlPanel.qml +1 −1
- M .config/quickshell/aurora/HomePage.qml +33 −0
- A .config/quickshell/aurora/Rec.qml +45 −0
- M .config/quickshell/aurora/Theme.qml +9 −0
- A .config/quickshell/aurora/Toggles.qml +50 −0
- M .config/quickshell/aurora/shell.qml +2 −0
| @@ -20,6 +20,12 @@ PanelWindow { | |||
| 20 | 20 | ||
| 21 | 21 | SystemClock { id: clock; precision: SystemClock.Minutes } | |
| 22 | 22 | ||
| 23 | + | // caffeine: one inhibitor is enough, keep it on the first screen's bar | |
| 24 | + | IdleInhibitor { | |
| 25 | + | window: bar | |
| 26 | + | enabled: Toggles.caffeine && bar.modelData === Quickshell.screens[0] | |
| 27 | + | } | |
| 28 | + | ||
| 23 | 29 | // ---- state ---- | |
| 24 | 30 | readonly property var audio: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null | |
| 25 | 31 | readonly property var mic: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null | |
| @@ -89,6 +95,22 @@ PanelWindow { | |||
| 89 | 95 | ||
| 90 | 96 | Tray { show: bar.monFocused } | |
| 91 | 97 | ||
| 98 | + | BarPill { | |
| 99 | + | visible: Rec.running | |
| 100 | + | icon: Theme.iRec | |
| 101 | + | text: Theme.fmtClock(Rec.elapsed) | |
| 102 | + | tint: Theme.err | |
| 103 | + | underline: Theme.err | |
| 104 | + | onActivated: Rec.stop() | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | BarPill { | |
| 108 | + | visible: Toggles.caffeine || Toggles.airplane | |
| 109 | + | icon: (Toggles.caffeine ? Theme.iCoffee : "") + (Toggles.caffeine && Toggles.airplane ? " " : "") | |
| 110 | + | + (Toggles.airplane ? Theme.iPlane : "") | |
| 111 | + | onActivated: bar.togglePanel("home", bar.modelData) | |
| 112 | + | } | |
| 113 | + | ||
| 92 | 114 | BarPill { | |
| 93 | 115 | visible: bar.headset !== null | |
| 94 | 116 | icon: Theme.iHeadphones | |
| @@ -16,7 +16,7 @@ PanelWindow { | |||
| 16 | 16 | anchors { top: true; right: true } | |
| 17 | 17 | margins { top: 6; right: 8 } // sits just under the bar (bar's exclusive zone is respected) | |
| 18 | 18 | implicitWidth: 440 | |
| 19 | − | implicitHeight: 640 | |
| 19 | + | implicitHeight: 720 | |
| 20 | 20 | exclusiveZone: 0 | |
| 21 | 21 | color: "transparent" | |
| 22 | 22 | WlrLayershell.layer: WlrLayer.Overlay | |
| @@ -95,6 +95,39 @@ ColumnLayout { | |||
| 95 | 95 | onClicked: if (audio) audio.muted = !audio.muted | |
| 96 | 96 | } | |
| 97 | 97 | } | |
| 98 | + | RowLayout { | |
| 99 | + | Layout.fillWidth: true | |
| 100 | + | spacing: 6 | |
| 101 | + | IconBtn { | |
| 102 | + | Layout.fillWidth: true | |
| 103 | + | icon: Theme.iCoffee | |
| 104 | + | label: "caffeine" | |
| 105 | + | active: Toggles.caffeine | |
| 106 | + | onClicked: Toggles.caffeine = !Toggles.caffeine | |
| 107 | + | } | |
| 108 | + | IconBtn { | |
| 109 | + | Layout.fillWidth: true | |
| 110 | + | icon: Theme.iPlane | |
| 111 | + | label: "airplane" | |
| 112 | + | active: Toggles.airplane | |
| 113 | + | onClicked: Toggles.setAirplane(!Toggles.airplane) | |
| 114 | + | } | |
| 115 | + | IconBtn { | |
| 116 | + | Layout.fillWidth: true | |
| 117 | + | visible: Toggles.hasPicker | |
| 118 | + | icon: Theme.iPicker | |
| 119 | + | label: "colour" | |
| 120 | + | onClicked: { page.closeRequested(); Toggles.pickColour() } | |
| 121 | + | } | |
| 122 | + | IconBtn { // click: record a region; while recording: stop | |
| 123 | + | Layout.fillWidth: true | |
| 124 | + | visible: Rec.available | |
| 125 | + | icon: Rec.running ? Theme.iStop : Theme.iRec | |
| 126 | + | label: Rec.running ? Theme.fmtClock(Rec.elapsed) : "rec" | |
| 127 | + | active: Rec.running | |
| 128 | + | onClicked: { if (!Rec.running) page.closeRequested(); Rec.toggle("region") } | |
| 129 | + | } | |
| 130 | + | } | |
| 98 | 131 | ||
| 99 | 132 | SectionLabel { text: "desktop" } | |
| 100 | 133 | ||
| @@ -0,0 +1,45 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | import Quickshell.Hyprland | |
| 6 | + | ||
| 7 | + | // Screen recording with wf-recorder (region via slurp, or the focused monitor), saved to ~/vids/rec. | |
| 8 | + | // Everything is hidden if wf-recorder isn't installed. | |
| 9 | + | Singleton { | |
| 10 | + | id: root | |
| 11 | + | property bool available: false | |
| 12 | + | readonly property bool running: proc.running | |
| 13 | + | property string dir: Quickshell.env("HOME") + "/vids/rec" | |
| 14 | + | property string file: "" | |
| 15 | + | property int elapsed: 0 // seconds | |
| 16 | + | ||
| 17 | + | function start(mode) { // "region" | "screen" | |
| 18 | + | if (!available || proc.running) return | |
| 19 | + | file = dir + "/" + Qt.formatDateTime(new Date(), "yyyy-MM-dd_HH-mm-ss") + ".mp4" | |
| 20 | + | var m = Hyprland.focusedMonitor | |
| 21 | + | // exec so the Process pid is wf-recorder itself and SIGINT reaches it | |
| 22 | + | proc.command = mode === "region" | |
| 23 | + | ? ["sh", "-c", 'mkdir -p "${1%/*}"; g=$(slurp) || exit 1; exec wf-recorder -g "$g" -f "$1"', "sh", file] | |
| 24 | + | : ["sh", "-c", 'mkdir -p "${1%/*}"; exec wf-recorder -o "$2" -f "$1"', "sh", file, m ? m.name : ""] | |
| 25 | + | elapsed = 0 | |
| 26 | + | proc.running = true | |
| 27 | + | } | |
| 28 | + | function stop() { if (proc.running) proc.signal(2) } // SIGINT: wf-recorder finalises the file | |
| 29 | + | function toggle(mode) { if (proc.running) stop(); else start(mode) } | |
| 30 | + | ||
| 31 | + | Process { | |
| 32 | + | id: proc | |
| 33 | + | onExited: code => { | |
| 34 | + | Quickshell.execDetached(["sh", "-c", | |
| 35 | + | '[ -s "$1" ] && notify-send -a aurora "recording saved" "$1"', "sh", root.file]) | |
| 36 | + | } | |
| 37 | + | } | |
| 38 | + | Timer { interval: 1000; repeat: true; running: proc.running; onTriggered: root.elapsed++ } | |
| 39 | + | ||
| 40 | + | Process { | |
| 41 | + | command: ["sh", "-c", "command -v wf-recorder && command -v slurp"] | |
| 42 | + | running: true | |
| 43 | + | onExited: code => root.available = code === 0 | |
| 44 | + | } | |
| 45 | + | } | |
| @@ -151,6 +151,11 @@ Singleton { | |||
| 151 | 151 | readonly property string iBatCharge: cp(0xF0084) | |
| 152 | 152 | readonly property string iShuffle: cp(0xF049D) | |
| 153 | 153 | readonly property string iImage: cp(0xF02E9) | |
| 154 | + | readonly property string iCoffee: cp(0xF0176) | |
| 155 | + | readonly property string iPlane: cp(0xF001D) | |
| 156 | + | readonly property string iPicker: cp(0xF020A) | |
| 157 | + | readonly property string iRec: cp(0xF044A) | |
| 158 | + | readonly property string iStop: cp(0xF04DB) | |
| 154 | 159 | ||
| 155 | 160 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 156 | 161 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |
| @@ -176,6 +181,10 @@ Singleton { | |||
| 176 | 181 | return h > 0 ? h + "h " + m + "m" : m + "m" | |
| 177 | 182 | } | |
| 178 | 183 | ||
| 184 | + | function fmtClock(s) { // 75 -> "1:15" | |
| 185 | + | var m = Math.floor(s / 60), r = s % 60 | |
| 186 | + | return m + ":" + (r < 10 ? "0" : "") + r | |
| 187 | + | } | |
| 179 | 188 | function volIcon(v, muted) { | |
| 180 | 189 | if (muted || v <= 0) return iVolOff | |
| 181 | 190 | return v < 0.34 ? iVolLow : (v < 0.67 ? iVolMed : iVolHigh) | |
| @@ -0,0 +1,50 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | import Quickshell.Bluetooth | |
| 6 | + | ||
| 7 | + | // Small session toggles used by the Home tab and the bar. | |
| 8 | + | Singleton { | |
| 9 | + | id: root | |
| 10 | + | ||
| 11 | + | // ---- caffeine: an IdleInhibitor on the bar (see Bar.qml) keeps hypridle from dimming/locking | |
| 12 | + | property bool caffeine: false | |
| 13 | + | ||
| 14 | + | // ---- airplane: wifi + bluetooth off, previous state restored when turned off again | |
| 15 | + | property bool airplane: false | |
| 16 | + | property bool hadWifi: true | |
| 17 | + | property bool hadBt: false | |
| 18 | + | function setAirplane(on) { | |
| 19 | + | var ad = Bluetooth.defaultAdapter | |
| 20 | + | if (on) { | |
| 21 | + | hadWifi = Net.wifiEnabled | |
| 22 | + | hadBt = ad ? ad.enabled : false | |
| 23 | + | Net.setWifi(false) | |
| 24 | + | if (ad) ad.enabled = false | |
| 25 | + | } else { | |
| 26 | + | if (hadWifi) Net.setWifi(true) | |
| 27 | + | if (ad && hadBt) ad.enabled = true | |
| 28 | + | } | |
| 29 | + | airplane = on | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | // ---- colour picker: hyprpicker copies the hex itself, we just show it as a notification | |
| 33 | + | property bool hasPicker: false | |
| 34 | + | function pickColour() { if (!picker.running) picker.running = true } | |
| 35 | + | Process { | |
| 36 | + | id: picker | |
| 37 | + | command: ["hyprpicker", "-a", "-f", "hex"] | |
| 38 | + | stdout: StdioCollector { | |
| 39 | + | onStreamFinished: { | |
| 40 | + | var c = text.trim() | |
| 41 | + | if (c !== "") Quickshell.execDetached(["notify-send", "-a", "aurora", "colour copied", c]) | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | Process { | |
| 46 | + | command: ["sh", "-c", "command -v hyprpicker"] | |
| 47 | + | running: true | |
| 48 | + | onExited: code => root.hasPicker = code === 0 | |
| 49 | + | } | |
| 50 | + | } | |
| @@ -145,6 +145,8 @@ ShellRoot { | |||
| 145 | 145 | function clip(): void { root.toggleOverlay("clip") } | |
| 146 | 146 | function wall(): void { root.toggleOverlay("wall") } | |
| 147 | 147 | function wallrandom(): void { Wall.random() } | |
| 148 | + | function rec(mode: string): void { root.close(); Rec.toggle(mode === "screen" ? "screen" : "region") } // region | screen | |
| 149 | + | function caffeine(): void { Toggles.caffeine = !Toggles.caffeine } | |
| 148 | 150 | function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down | |
| 149 | 151 | } | |
| 150 | 152 | } | |