Krolyxon
Commit eff182a

Branches

Refs →

auroraeff182a

aurora: pending updates indicator, gesture settings

Author
krolyxon
Date
2026-10-25 16:00 +0530
Parent
ce210bb
Commit
eff182a5a626591d04279f16cbc853c6daa92250

+156 −3

9 files changed

  1. M .config/quickshell/aurora/Bar.qml +11 −0
  2. M .config/quickshell/aurora/HyprConf.qml +1 −0
  3. M .config/quickshell/aurora/SetInput.qml +5 −1
  4. M .config/quickshell/aurora/SetShell.qml +2 −0
  5. M .config/quickshell/aurora/Settings.qml +3 −1
  6. M .config/quickshell/aurora/SysPage.qml +45 −1
  7. M .config/quickshell/aurora/Theme.qml +1 −0
  8. A .config/quickshell/aurora/Updates.qml +87 −0
  9. M .config/quickshell/aurora/shell.qml +1 −0
M .config/quickshell/aurora/Bar.qml View
@@ -128,6 +128,17 @@ PanelWindow {
128128 underline: Theme.err
129129 }
130130
131+ BarPill { // left click: list in the sys tab, right click: upgrade in foot
132+ visible: bar.monFocused && Settings.updatesPill && Updates.count > 0
133+ icon: Theme.iUpdate
134+ text: String(Updates.count)
135+ tint: Updates.upgrading ? Theme.dim : Theme.fg
136+ onActivated: button => {
137+ if (button === Qt.RightButton) Updates.upgrade()
138+ else bar.togglePanel("sys", bar.modelData)
139+ }
140+ }
141+
131142 BarPill {
132143 visible: Toggles.caffeine || Toggles.airplane
133144 icon: (Toggles.caffeine ? Theme.iCoffee : "") + (Toggles.caffeine && Toggles.airplane ? " " : "")
M .config/quickshell/aurora/HyprConf.qml View
@@ -31,6 +31,7 @@ Singleton {
3131 "input:left_handed": "bool",
3232 "input:touchpad:natural_scroll": "bool", "input:touchpad:tap_to_click": "bool",
3333 "input:touchpad:disable_while_typing": "bool", "input:touchpad:scroll_factor": "float",
34+ "gestures:workspace_swipe_invert": "bool", "gestures:workspace_swipe_distance": "int",
3435 "cursor:inactive_timeout": "int", "cursor:hide_on_key_press": "bool"
3536 })
3637
M .config/quickshell/aurora/SetInput.qml View
@@ -57,6 +57,10 @@ Flickable {
5757 Switch { label: "disable while typing"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) }
5858 Knob { label: "scroll speed"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) }
5959
60+ SectionLabel { text: "gestures" }
61+ Switch { label: "invert swipe"; hint: "reverse the 3-finger workspace swipe"; checked: flick.v("gestures:workspace_swipe_invert", true); onToggled: on => HyprConf.set("gestures:workspace_swipe_invert", on) }
62+ Knob { label: "swipe distance"; unit: "px"; from: 100; to: 1000; value: flick.v("gestures:workspace_swipe_distance", 300); onMoved: x => HyprConf.set("gestures:workspace_swipe_distance", Math.round(x / 10) * 10) }
63+
6064 SectionLabel { text: "cursor" }
6165 Knob { label: "hide when idle"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) }
6266 Switch { label: "hide while typing"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) }
@@ -72,7 +76,7 @@ Flickable {
7276 font.family: Theme.font
7377 font.pixelSize: Theme.fontSize - 2
7478 }
75− IconBtn { icon: Theme.iRefresh; label: "reset to config"; onClicked: HyprConf.resetAll(["input:", "cursor:"]) }
79+ IconBtn { icon: Theme.iRefresh; label: "reset to config"; onClicked: HyprConf.resetAll(["input:", "cursor:", "gestures:"]) }
7680 }
7781 }
7882 }
M .config/quickshell/aurora/SetShell.qml View
@@ -49,7 +49,9 @@ Flickable {
4949 SectionLabel { text: "network" }
5050 Switch { label: "captive portals"; hint: "open the login page in the browser when a network needs one"; checked: Settings.portalAutoOpen; onToggled: on => Settings.portalAutoOpen = on }
5151
52+ SectionLabel { text: "bar" }
5253 Switch { label: "privacy indicators"; hint: "show in the bar when an app records the mic, uses the camera or shares the screen"; checked: Settings.privacyIndicators; onToggled: on => Settings.privacyIndicators = on }
54+ Switch { label: "pending updates"; hint: "show the pacman + AUR update count in the bar"; checked: Settings.updatesPill; onToggled: on => Settings.updatesPill = on }
5355
5456 SectionLabel { text: "notifications" }
5557 Switch { label: "do not disturb"; hint: "hide popups and mute the sound (critical ones still show)"; checked: Notifs.dnd; onToggled: on => Notifs.dnd = on }
M .config/quickshell/aurora/Settings.qml View
@@ -17,7 +17,7 @@ Singleton {
1717 notifSound: true, notifVolume: 0.7,
1818 idleDim: 150, idleOff: 330, idleLock: 600, idleSuspend: 0, charTone: 0, portalAutoOpen: true,
1919 chargeMode: "off", batLow: 15, batCritical: 5, batCriticalAction: "none",
20− privacyIndicators: true
20+ privacyIndicators: true, updatesPill: true
2121 })
2222
2323 property alias fontSize: a.fontSize
@@ -46,6 +46,7 @@ Singleton {
4646 property alias batCritical: a.batCritical
4747 property alias batCriticalAction: a.batCriticalAction
4848 property alias privacyIndicators: a.privacyIndicators
49+ property alias updatesPill: a.updatesPill
4950
5051 function reset() {
5152 for (var k in defaults) a[k] = defaults[k]
@@ -96,6 +97,7 @@ Singleton {
9697 property int batCritical: 5
9798 property string batCriticalAction: "none" // none | suspend | poweroff
9899 property bool privacyIndicators: true // mic/camera/screen-share icons in the bar (Privacy.qml)
100+ property bool updatesPill: true // pending package updates in the bar (Updates.qml)
99101 }
100102 }
101103 // first run: create the file once the directory exists
M .config/quickshell/aurora/SysPage.qml View
@@ -13,7 +13,7 @@ Flickable {
1313 function size(v) { return v >= 1000 ? (v / 1024).toFixed(1) + "T" : (v >= 100 ? Math.round(v) : v.toFixed(1)) + "G" }
1414
1515 // pick up drives plugged in since the last poll
16− onVisibleChanged: if (visible) SysStats.refreshDisks()
16+ onVisibleChanged: if (visible) { SysStats.refreshDisks(); Updates.check(false) }
1717
1818 ColumnLayout {
1919 id: col
@@ -79,6 +79,50 @@ Flickable {
7979 }
8080 }
8181
82+ // pending pacman + AUR updates (Updates)
83+ SectionLabel { text: "updates"; Layout.topMargin: 6; visible: Updates.available }
84+ RowLayout {
85+ visible: Updates.available
86+ Layout.fillWidth: true
87+ spacing: 6
88+ Text {
89+ Layout.fillWidth: true
90+ text: Updates.checking ? "checking..."
91+ : Updates.error !== "" ? Updates.error
92+ : (Updates.count === 0 ? "up to date" : Updates.repo + " repo, " + Updates.aur + " aur")
93+ + (Updates.lastCheck.getTime() > 0 ? " · " + Qt.formatDateTime(Updates.lastCheck, "HH:mm") : "")
94+ color: Updates.error !== "" ? Theme.err : Theme.fg
95+ font.family: Theme.font
96+ font.pixelSize: Theme.fontSize
97+ elide: Text.ElideRight
98+ }
99+ IconBtn { icon: Theme.iRefresh; label: "check"; onClicked: Updates.check(true) }
100+ IconBtn {
101+ visible: Updates.count > 0
102+ icon: Theme.iUpdate
103+ label: Updates.upgrading ? "running" : "upgrade"
104+ active: Updates.upgrading
105+ onClicked: Updates.upgrade()
106+ }
107+ }
108+ Repeater {
109+ model: Updates.available ? Updates.list.slice(0, 6) : []
110+ delegate: ListRow {
111+ required property var modelData
112+ icon: Theme.iUpdate
113+ title: modelData.name
114+ subtitle: modelData.from + " → " + modelData.to
115+ trailing: modelData.aur ? "aur" : ""
116+ }
117+ }
118+ Text {
119+ visible: Updates.available && Updates.list.length > 6
120+ text: "+ " + (Updates.list.length - 6) + " more"
121+ color: Theme.dim
122+ font.family: Theme.font
123+ font.pixelSize: Theme.fontSize - 2
124+ }
125+
82126 BatteryInfo { Layout.topMargin: 6 }
83127
84128 ColumnLayout {
M .config/quickshell/aurora/Theme.qml View
@@ -173,6 +173,7 @@ Singleton {
173173 readonly property string iDisk: cp(0xF02CA)
174174 readonly property string iUsb: cp(0xF129B)
175175 readonly property string iEject: cp(0xF01EA)
176+ readonly property string iUpdate: cp(0xF03D5) // package up
176177 readonly property string iFolderOpen: cp(0xF0770)
177178 readonly property string iGpu: cp(0xF08AE)
178179 readonly property string iLeaf: cp(0xF032A)
A .config/quickshell/aurora/Updates.qml View
@@ -0,0 +1,87 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Io
5+
6+// pending package updates: checkupdates (pacman-contrib, syncs a private db copy) + paru -Qua (AUR).
7+// Checked a minute after start, then every 2h; the sys tab asks for a fresh one when opened (check(false)
8+// skips it if the last one is under 10 min old). upgrade() runs paru in foot, started through Hyprland so
9+// restarting aurora can't kill a pacman transaction; the terminal calls the `updates` IPC when it closes.
10+Singleton {
11+ id: root
12+ property bool available: false // checkupdates installed
13+ property bool checking: false
14+ property bool upgrading: false
15+ property var list: [] // [{name, from, to, aur}], repo packages first
16+ property int repo: 0
17+ property int aur: 0
18+ readonly property int count: repo + aur
19+ property date lastCheck: new Date(0)
20+ property string error: "" // checkupdates failed (offline, db locked...): the old list is kept
21+
22+ function check(force) {
23+ if (!available || checking) return
24+ if (!force && Date.now() - lastCheck.getTime() < 600000) return
25+ checking = true
26+ checkProc.running = true
27+ }
28+
29+ function upgrade() {
30+ if (upgrading) return
31+ upgrading = true
32+ upgradeGuard.restart()
33+ Quickshell.execDetached(["hyprctl", "dispatch", "hl.dsp.exec_cmd([[foot -T 'system upgrade' -e sh -c "
34+ + "'paru; printf \"\\ndone, press enter to close\"; read _; qs -c aurora ipc call shell updates']])"])
35+ }
36+ // called by the upgrade terminal when it closes (and usable by hand after updating elsewhere)
37+ function finished() {
38+ upgrading = false
39+ upgradeGuard.stop()
40+ check(true)
41+ }
42+
43+ // "name old -> new [ignored]" lines; @repo <exit code> separates the two lists
44+ function parse(text) {
45+ var out = [], nr = 0, na = 0, isAur = false, code = 0
46+ var lines = text.split("\n")
47+ for (var i = 0; i < lines.length; i++) {
48+ var l = lines[i].trim()
49+ var c = l.match(/^@repo (\d+)$/)
50+ if (c) { code = parseInt(c[1]); isAur = true; continue }
51+ var m = l.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)/)
52+ if (!m || l.indexOf("[ignored]") >= 0) continue
53+ out.push({ name: m[1], from: m[2], to: m[3], aur: isAur })
54+ if (isAur) na++; else nr++
55+ }
56+ // checkupdates: 0 = updates, 2 = none, anything else = failed
57+ if (code !== 0 && code !== 2) {
58+ error = "checkupdates failed (" + code + ")"
59+ out = list.filter(function (p) { return !p.aur }).concat(out.filter(function (p) { return p.aur }))
60+ nr = repo
61+ } else {
62+ error = ""
63+ }
64+ list = out
65+ repo = nr
66+ aur = na
67+ lastCheck = new Date()
68+ }
69+
70+ Process {
71+ command: ["sh", "-c", "command -v checkupdates"]
72+ running: true
73+ onExited: code => root.available = code === 0
74+ }
75+
76+ Process {
77+ id: checkProc
78+ command: ["sh", "-c", 'checkupdates 2>/dev/null; echo "@repo $?"; command -v paru >/dev/null && paru -Qua 2>/dev/null; true']
79+ stdout: StdioCollector { onStreamFinished: { root.parse(text); root.checking = false } }
80+ }
81+
82+ // the terminal may be closed with the window's X, skipping the IPC call
83+ Timer { id: upgradeGuard; interval: 3600000; onTriggered: root.upgrading = false }
84+
85+ Timer { interval: 60000; running: root.available; onTriggered: root.check(true) }
86+ Timer { interval: 7200000; running: root.available; repeat: true; onTriggered: root.check(true) }
87+}
M .config/quickshell/aurora/shell.qml View
@@ -219,5 +219,6 @@ ShellRoot {
219219 function caffeine(): void { Toggles.caffeine = !Toggles.caffeine }
220220 function portal(): void { Net.signIn() } // open the captive portal login page (or say we're online)
221221 function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down
222+ function updates(): void { Updates.finished() } // re-check pending updates (the upgrade terminal calls it)
222223 }
223224 }