Krolyxon
Commit ed4e5ea

Branches

Refs →

auroraed4e5ea

aurora: nothing os redesign, phase 1 (design system, control panel)

Nothing OS look after glyph-rice (GPL-3.0, credited): Doto / Space Grotesk / Space Mono
(vendored, OFL), dot-matrix shaders, primitives (Lb, Num, Icon, Ring, Seg, DotMask, DotArt,
Chip, Tile, TogglePill, Segmented, PlayControls, Media). Colours are Material You roles
from matugen (primary fills, tertiary for live things, tinted surface ladder); the aurora
matugen template now exposes the M3 roles. Control panel rebuilt: Doto clock header, chip
tabs, glyph quick-settings home, restyled sound/net/bt/sys/notif pages and shared parts.
Author
krolyxon
Date
2026-10-28 19:16 +0530
Parent
fb9b856
Commit
ed4e5ea2a017d34fd807be0cf044dca3b40a1305

+1742 −837

50 files changed

  1. M .config/matugen/templates/aurora.json +20 −11
  2. M .config/quickshell/aurora/AudioPage.qml +41 −19
  3. M .config/quickshell/aurora/BarSlider.qml +16 −24
  4. M .config/quickshell/aurora/BatteryInfo.qml +42 −23
  5. M .config/quickshell/aurora/BtPage.qml +13 −11
  6. A .config/quickshell/aurora/Chip.qml +41 −0
  7. M .config/quickshell/aurora/ControlPanel.qml +66 −55
  8. A .config/quickshell/aurora/DotArt.qml +40 −0
  9. A .config/quickshell/aurora/DotMask.qml +13 −0
  10. M .config/quickshell/aurora/GpuSwitch.qml +9 −8
  11. M .config/quickshell/aurora/HomePage.qml +212 −197
  12. A .config/quickshell/aurora/Icon.qml +13 −0
  13. M .config/quickshell/aurora/IconBtn.qml +15 −16
  14. A .config/quickshell/aurora/Lb.qml +12 −0
  15. M .config/quickshell/aurora/ListRow.qml +55 −41
  16. A .config/quickshell/aurora/Media.qml +28 −0
  17. M .config/quickshell/aurora/MediaCard.qml +67 −128
  18. M .config/quickshell/aurora/NetPage.qml +68 −48
  19. M .config/quickshell/aurora/NotifCard.qml +112 −80
  20. M .config/quickshell/aurora/NotifPage.qml +20 −17
  21. M .config/quickshell/aurora/Notifs.qml +18 −0
  22. A .config/quickshell/aurora/Num.qml +16 −0
  23. A .config/quickshell/aurora/PlayControls.qml +44 −0
  24. A .config/quickshell/aurora/Ring.qml +34 −0
  25. M .config/quickshell/aurora/SectionLabel.qml +24 −7
  26. A .config/quickshell/aurora/Seg.qml +31 −0
  27. A .config/quickshell/aurora/Segmented.qml +76 −0
  28. M .config/quickshell/aurora/Settings.qml +10 −3
  29. M .config/quickshell/aurora/SliderRow.qml +64 −17
  30. M .config/quickshell/aurora/Spark.qml +18 −18
  31. M .config/quickshell/aurora/StatBar.qml +17 −20
  32. M .config/quickshell/aurora/SysPage.qml +61 −73
  33. M .config/quickshell/aurora/Theme.qml +81 −20
  34. A .config/quickshell/aurora/Tile.qml +51 −0
  35. A .config/quickshell/aurora/TogglePill.qml +101 −0
  36. A .config/quickshell/aurora/shaders/dots.frag +28 −0
  37. A .config/quickshell/aurora/shaders/dots.frag.qsb
  38. A .config/quickshell/aurora/shaders/wall.frag +59 −0
  39. A .config/quickshell/aurora/shaders/wall.frag.qsb
  40. A .local/share/fonts/glyph/Doto-Variable.ttf
  41. A .local/share/fonts/glyph/OFL.txt +95 −0
  42. A .local/share/fonts/glyph/SpaceGrotesk-Bold.otf
  43. A .local/share/fonts/glyph/SpaceGrotesk-Light.otf
  44. A .local/share/fonts/glyph/SpaceGrotesk-Medium.otf
  45. A .local/share/fonts/glyph/SpaceGrotesk-Regular.otf
  46. A .local/share/fonts/glyph/SpaceGrotesk-SemiBold.otf
  47. A .local/share/fonts/glyph/SpaceMono-Bold.ttf
  48. A .local/share/fonts/glyph/SpaceMono-Regular.ttf
  49. M .stow-local-ignore +1 −0
  50. M README.md +10 −1
M .config/matugen/templates/aurora.json View
@@ -1,18 +1,27 @@
11 {
22 "image": "{{image}}",
3− "bg": "{{colors.surface.default.hex | set_lightness: 5.0}}",
4− "fg": "{{colors.on_surface.default.hex}}",
5− "primary": "{{colors.primary_container.default.hex}}",
6− "primaryFg": "{{colors.on_primary_container.default.hex}}",
7− "primaryContainer": "{{colors.primary_container.default.hex | set_lightness: 25.0}}",
3+ "primary": "{{colors.primary.default.hex}}",
4+ "primaryFg": "{{colors.on_primary.default.hex}}",
5+ "primaryContainer": "{{colors.primary_container.default.hex}}",
6+ "primaryContainerFg": "{{colors.on_primary_container.default.hex}}",
87 "secondary": "{{colors.secondary.default.hex}}",
8+ "secondaryFg": "{{colors.on_secondary.default.hex}}",
9+ "secondaryContainer": "{{colors.secondary_container.default.hex}}",
10+ "secondaryContainerFg": "{{colors.on_secondary_container.default.hex}}",
911 "tertiary": "{{colors.tertiary.default.hex}}",
10− "surfaceLow": "{{colors.surface.default.hex | set_lightness: 8.0}}",
11− "surface": "{{colors.surface.default.hex | set_lightness: 10.0}}",
12− "surfaceHigh": "{{colors.surface.default.hex | set_lightness: 14.0}}",
13− "surfaceHighest": "{{colors.surface.default.hex | set_lightness: 18.0}}",
12+ "tertiaryFg": "{{colors.on_tertiary.default.hex}}",
13+ "tertiaryContainer": "{{colors.tertiary_container.default.hex}}",
14+ "tertiaryContainerFg": "{{colors.on_tertiary_container.default.hex}}",
15+ "surface": "{{colors.surface.default.hex}}",
16+ "surfaceLowest": "{{colors.surface_container_lowest.default.hex}}",
17+ "surfaceLow": "{{colors.surface_container_low.default.hex}}",
18+ "surfaceContainer": "{{colors.surface_container.default.hex}}",
19+ "surfaceHigh": "{{colors.surface_container_high.default.hex}}",
20+ "surfaceHighest": "{{colors.surface_container_highest.default.hex}}",
21+ "surfaceFg": "{{colors.on_surface.default.hex}}",
22+ "surfaceVariantFg": "{{colors.on_surface_variant.default.hex}}",
1423 "outline": "{{colors.outline.default.hex}}",
1524 "outlineVariant": "{{colors.outline_variant.default.hex}}",
16− "surfaceVariantFg": "{{colors.on_surface_variant.default.hex}}",
17− "err": "{{colors.error.default.hex}}"
25+ "err": "{{colors.error.default.hex}}",
26+ "errFg": "{{colors.on_error.default.hex}}"
1827 }
M .config/quickshell/aurora/AudioPage.qml View
@@ -2,11 +2,13 @@ import QtQuick
22 import QtQuick.Layouts
33 import Quickshell.Services.Pipewire
44
5+// Control panel > sound (also the settings app's sound section): output / input levels, devices, per-app volume.
56 Flickable {
67 id: flick
78 clip: true
89 contentWidth: width
910 contentHeight: col.implicitHeight
11+ implicitHeight: col.implicitHeight
1012 boundsBehavior: Flickable.StopAtBounds
1113
1214 function mediaClass(n) {
@@ -34,67 +36,87 @@ Flickable {
3436 readonly property var sinks: nodes("sink")
3537 readonly property var sources: nodes("source")
3638 readonly property var streams: nodes("stream")
39+ readonly property var out: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
40+ readonly property var inp: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
3741
3842 ColumnLayout {
3943 id: col
4044 width: flick.width
41− spacing: 4
45+ spacing: 6
4246
43− SectionLabel { text: "output device" }
47+ SectionLabel { text: "levels" }
48+ SliderRow {
49+ Layout.fillWidth: true
50+ Layout.topMargin: 4
51+ icon: Theme.volIcon(flick.out ? flick.out.volume : 0, flick.out ? flick.out.muted : true)
52+ value: flick.out ? flick.out.volume : 0
53+ muted: flick.out ? flick.out.muted : false
54+ onMoved: v => { if (flick.out) flick.out.volume = v }
55+ onIconClicked: if (flick.out) flick.out.muted = !flick.out.muted
56+ }
57+ SliderRow {
58+ Layout.fillWidth: true
59+ Layout.topMargin: 6
60+ icon: flick.inp && !flick.inp.muted ? Theme.iMic : Theme.iMicOff
61+ value: flick.inp ? flick.inp.volume : 0
62+ muted: flick.inp ? flick.inp.muted : false
63+ onMoved: v => { if (flick.inp) flick.inp.volume = v }
64+ onIconClicked: if (flick.inp) flick.inp.muted = !flick.inp.muted
65+ }
66+
67+ SectionLabel { text: "output"; count: String(flick.sinks.length); Layout.topMargin: 14 }
4468 Repeater {
4569 model: flick.sinks
4670 delegate: ListRow {
4771 required property var modelData
4872 icon: flick.sinkIcon(modelData)
4973 title: flick.label(modelData)
50− subtitle: current ? "default output" : ""
74+ subtitle: current ? "default" : ""
5175 current: Pipewire.defaultAudioSink === modelData
5276 trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : ""
5377 onClicked: Pipewire.preferredDefaultAudioSink = modelData
5478 }
5579 }
5680
57− SectionLabel { text: "input device"; Layout.topMargin: 10 }
81+ SectionLabel { text: "input"; count: String(flick.sources.length); Layout.topMargin: 14 }
5882 Repeater {
5983 model: flick.sources
6084 delegate: ListRow {
6185 required property var modelData
6286 icon: Theme.iMic
6387 title: flick.label(modelData)
64− subtitle: current ? "default input" : ""
88+ subtitle: current ? "default" : ""
6589 current: Pipewire.defaultAudioSource === modelData
6690 trailing: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : ""
6791 onClicked: Pipewire.preferredDefaultAudioSource = modelData
6892 }
6993 }
7094
71− SectionLabel { text: "applications"; Layout.topMargin: 10 }
72− Text {
95+ SectionLabel { text: "apps"; count: String(flick.streams.length); Layout.topMargin: 14 }
96+ Lb {
7397 visible: flick.streams.length === 0
74− text: "no apps playing audio"
75− color: Theme.dim
76− font.family: Theme.font
77− font.pixelSize: Theme.fontSize - 1
78− Layout.leftMargin: 10
98+ Layout.leftMargin: 6
99+ text: "no app is playing"
100+ color: Theme.mute
79101 }
80102 Repeater {
81103 model: flick.streams
82104 delegate: ColumnLayout {
83105 required property var modelData
84106 Layout.fillWidth: true
85− spacing: 0
86− Text {
87− Layout.leftMargin: 4
107+ Layout.topMargin: 4
108+ spacing: 6
109+ Lb {
110+ Layout.leftMargin: 6
88111 Layout.fillWidth: true
89112 elide: Text.ElideRight
113+ px: 10
90114 text: (modelData.properties && modelData.properties["application.name"])
91115 ? modelData.properties["application.name"] : flick.label(modelData)
92− color: Theme.fg
93− font.family: Theme.font
94− font.pixelSize: Theme.fontSize - 1
95116 }
96117 SliderRow {
97118 Layout.fillWidth: true
119+ implicitHeight: 44
98120 icon: Theme.volIcon(modelData.audio.volume, modelData.audio.muted)
99121 value: modelData.audio.volume
100122 muted: modelData.audio.muted
@@ -103,6 +125,6 @@ Flickable {
103125 }
104126 }
105127 }
106− Item { Layout.preferredHeight: 8 }
128+ Item { Layout.preferredHeight: 4 }
107129 }
108130 }
M .config/quickshell/aurora/BarSlider.qml View
@@ -1,5 +1,7 @@
11 import QtQuick
22
3+// Dotted slider: a row of LED dots, the lit part white up to a larger head dot, the rest unlit.
4+// Click/drag sets, the wheel steps. muted = the lit dots go grey.
35 Item {
46 id: s
57 property real value: 0
@@ -10,40 +12,30 @@ Item {
1012 signal moved(real v)
1113
1214 readonly property real frac: to > from ? Math.max(0, Math.min(1, (value - from) / (to - from))) : 0
15+ readonly property int n: Math.max(2, Math.floor((width - 8) / 9) + 1)
16+ readonly property int head: Math.round(frac * (n - 1))
1317
1418 implicitHeight: 20
1519
16− Rectangle {
17− id: track
18− anchors.verticalCenter: parent.verticalCenter
19− width: parent.width
20− height: 6
21− color: Theme.surfaceHi
22−
20+ Repeater {
21+ model: s.n
2322 Rectangle {
24− width: parent.width * s.frac
25− height: parent.height
26− opacity: s.muted ? 0.35 : 1
27− gradient: Gradient {
28− orientation: Gradient.Horizontal
29− GradientStop { position: 0; color: Theme.accentDim }
30− GradientStop { position: 1; color: Theme.accent }
31− }
23+ required property int index
24+ readonly property bool isHead: index === s.head
25+ readonly property real d: isHead ? 9 : 4
26+ x: 4 + index * (s.width - 8) / (s.n - 1) - d / 2
27+ anchors.verticalCenter: parent.verticalCenter
28+ width: d
29+ height: d
30+ radius: d / 2
31+ color: index <= s.head ? (s.muted ? Theme.grey : Theme.fill) : Theme.s3
3232 }
3333 }
3434
35− Rectangle {
36− x: Math.max(0, Math.min(s.width - width, s.width * s.frac - width / 2))
37− anchors.verticalCenter: parent.verticalCenter
38− width: 4
39− height: 14
40− color: Theme.fg
41− opacity: s.muted ? 0.5 : 1
42− }
43−
4435 MouseArea {
4536 anchors.fill: parent
4637 cursorShape: Qt.PointingHandCursor
38+ preventStealing: true
4739 function setFromX(mx) {
4840 var f = Math.max(0, Math.min(1, mx / width))
4941 s.moved(s.from + f * (s.to - s.from))
M .config/quickshell/aurora/BatteryInfo.qml View
@@ -2,50 +2,69 @@ import QtQuick
22 import QtQuick.Layouts
33 import Quickshell.Services.UPower
44
5−// battery block: charge bar, time left / to full, power draw, health (SysPage, settings app > Power)
5+// Battery block (SysPage, settings app > Power): charge in Doto, state, a segment bar (accent when low), details.
6+// After glyph-rice's battery card (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), bar/Bar.qml.
67 ColumnLayout {
78 id: root
89 readonly property var bat: UPower.displayDevice
910 readonly property bool hasBat: bat !== null && bat.isPresent
1011 readonly property real batPct: hasBat ? (bat.percentage > 1 ? bat.percentage / 100 : bat.percentage) : 0
1112 readonly property bool charging: hasBat && bat.state === UPowerDeviceState.Charging
12− function stateText() {
13+ readonly property bool low: batPct <= Settings.batLow / 100 && !charging
14+ function stateLine() {
1315 if (!hasBat) return ""
1416 switch (bat.state) {
15− case UPowerDeviceState.Charging: return "charging"
16− case UPowerDeviceState.Discharging: return "discharging"
17− case UPowerDeviceState.FullyCharged: return "full"
18− default: return "idle"
17+ case UPowerDeviceState.Charging:
18+ return "charging" + (bat.timeToFull > 0 ? " · full in " + Theme.fmtTime(bat.timeToFull) : "")
19+ case UPowerDeviceState.Discharging:
20+ return "on battery" + (bat.timeToEmpty > 0 ? " · " + Theme.fmtTime(bat.timeToEmpty) + " left" : "")
21+ case UPowerDeviceState.FullyCharged: return "on ac · full"
22+ default: return UPower.onBattery ? "on battery" : "on ac · not charging"
1923 }
2024 }
2125 visible: hasBat
2226 Layout.fillWidth: true
23− spacing: 8
27+ spacing: 12
28+
2429 SectionLabel { text: "battery" }
25− StatBar {
26− label: "charge"
27− frac: root.batPct
28− fill: root.batPct <= 0.15 && !root.charging ? Theme.err : Theme.accent
29− valueText: Math.round(root.batPct * 100) + "% " + root.stateText()
30+ RowLayout {
31+ Layout.leftMargin: 6
32+ spacing: 14
33+ Num {
34+ text: Math.round(root.batPct * 100)
35+ font.pixelSize: 56
36+ }
37+ ColumnLayout {
38+ spacing: 6
39+ Lb { text: "% battery"; color: Theme.fg }
40+ Lb { text: root.stateLine() }
41+ }
42+ }
43+ Seg {
44+ Layout.fillWidth: true
45+ Layout.preferredWidth: 100
46+ Layout.preferredHeight: 6
47+ Layout.leftMargin: 6
48+ Layout.rightMargin: 6
49+ n: 24
50+ f: root.batPct
51+ on: root.low ? Theme.err : Theme.fill
3052 }
31− Text {
53+ Lb {
3254 Layout.fillWidth: true
55+ Layout.leftMargin: 6
3356 wrapMode: Text.Wrap
34− color: Theme.dim
35− font.family: Theme.font
36− font.pixelSize: Theme.fontSize - 1
37− lineHeight: 1.3
57+ lineHeight: 1.5
58+ px: 10
3859 text: {
3960 if (!root.hasBat) return ""
4061 var lines = []
41− var t = root.charging ? root.bat.timeToFull : root.bat.timeToEmpty
42− lines.push((root.charging ? "time to full " : "time left ") + Theme.fmtTime(t))
43− if (root.bat.changeRate !== undefined)
44− lines.push("power draw " + Math.abs(root.bat.changeRate).toFixed(1) + " W")
62+ if (root.bat.changeRate !== undefined && Math.abs(root.bat.changeRate) > 0.05)
63+ lines.push("power draw " + Math.abs(root.bat.changeRate).toFixed(1) + " w")
4564 if (Settings.chargeMode !== "off" && Battery.supported)
46− lines.push("charge limit conservation" + (Battery.held ? ", holding" : ""))
65+ lines.push("charge limit · conservation" + (Battery.held ? ", holding" : ""))
4766 if (root.bat.healthSupported)
48− lines.push("health " + Math.round(root.bat.healthPercentage) + "%")
67+ lines.push("health " + Math.round(root.bat.healthPercentage) + "%")
4968 return lines.join("\n")
5069 }
5170 }
M .config/quickshell/aurora/BtPage.qml View
@@ -2,10 +2,12 @@ import QtQuick
22 import QtQuick.Layouts
33 import Quickshell.Bluetooth
44
5+// Control panel > bt (also the settings app's bluetooth section): radio, scan, devices.
6+// Click a row = connect / disconnect, or pair a new device (then connect).
57 ColumnLayout {
68 id: page
79 property bool active: false
8− spacing: 8
10+ spacing: 10
911
1012 readonly property var adapter: Bluetooth.defaultAdapter
1113
@@ -53,16 +55,15 @@ ColumnLayout {
5355
5456 RowLayout {
5557 Layout.fillWidth: true
56− spacing: 6
57− Text {
58+ Layout.leftMargin: 6
59+ spacing: 8
60+ Lb {
5861 Layout.fillWidth: true
5962 elide: Text.ElideRight
6063 text: !page.adapter ? "no bluetooth adapter"
61− : (!page.adapter.enabled ? "bluetooth is off"
62− : (page.adapter.discovering ? "scanning…" : (page.adapter.name || "adapter ready")))
63− color: page.adapter && page.adapter.enabled ? Theme.accent : Theme.dim
64− font.family: Theme.font
65− font.pixelSize: Theme.fontSize
64+ : (!page.adapter.enabled ? "bluetooth · off"
65+ : (page.adapter.discovering ? "bluetooth · searching" : "bluetooth · " + (page.adapter.name || "on")))
66+ color: page.adapter && page.adapter.enabled ? Theme.fg : Theme.grey
6667 }
6768 IconBtn {
6869 visible: page.adapter !== null
@@ -82,12 +83,13 @@ ColumnLayout {
8283 }
8384 }
8485
85− SectionLabel { text: "devices" }
86+ SectionLabel { text: "devices"; count: page.adapter && page.adapter.enabled ? String(page.devices.length) : "" }
8687
8788 Flickable {
8889 id: flick
8990 Layout.fillWidth: true
9091 Layout.fillHeight: true
92+ Layout.preferredHeight: col.implicitHeight
9193 clip: true
9294 contentWidth: width
9395 contentHeight: col.implicitHeight
@@ -117,7 +119,7 @@ ColumnLayout {
117119 if (dev.state === BluetoothDeviceState.Connecting) return "connecting…"
118120 if (dev.pairing) return "pairing…"
119121 if (dev.connected)
120− return "connected" + (dev.batteryAvailable ? " · " + Math.round(dev.battery * 100) + "%" : "")
122+ return "connected" + (dev.batteryAvailable ? " · " + Math.round(dev.battery * 100) + "%" : "")
121123 return page.paired(dev) ? "paired" : "available"
122124 }
123125 onClicked: {
@@ -136,7 +138,7 @@ ColumnLayout {
136138 IconBtn {
137139 visible: page.paired(row.dev)
138140 icon: Theme.iTrash
139− height: 26
141+ implicitHeight: 32
140142 onClicked: row.dev.forget()
141143 }
142144 }
A .config/quickshell/aurora/Chip.qml View
@@ -0,0 +1,41 @@
1+import QtQuick
2+
3+// A caps label in a pill: tabs, filters, modes, small actions. selected = white pill with black text.
4+// `dot` adds a small accent dot after the label (unread, live).
5+Rectangle {
6+ id: root
7+
8+ property string text
9+ property bool selected: false
10+ property bool dot: false
11+ property bool filled: false // a visible pill even when not selected (action buttons on cards)
12+ property real px: 11
13+ signal clicked()
14+
15+ implicitWidth: lab.implicitWidth + 32 + (dot ? 12 : 0)
16+ implicitHeight: lab.implicitHeight + 20
17+ radius: height / 2
18+ color: selected ? Theme.fill : hov.hovered ? (filled ? Theme.mute : Theme.s3) : (filled ? Theme.s3 : "transparent")
19+ Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
20+
21+ Lb {
22+ id: lab
23+ anchors.centerIn: parent
24+ anchors.horizontalCenterOffset: root.dot ? -6 : 0
25+ text: root.text
26+ px: root.px
27+ color: root.selected ? Theme.fillFg : (hov.hovered || root.filled) ? Theme.fg : Theme.grey
28+ }
29+ Rectangle {
30+ visible: root.dot
31+ anchors.left: lab.right
32+ anchors.leftMargin: 6
33+ anchors.verticalCenter: parent.verticalCenter
34+ width: 6
35+ height: 6
36+ radius: 3
37+ color: Theme.live
38+ }
39+ HoverHandler { id: hov; cursorShape: Qt.PointingHandCursor }
40+ TapHandler { onTapped: root.clicked() }
41+}
M .config/quickshell/aurora/ControlPanel.qml View
@@ -4,6 +4,9 @@ import Quickshell
44 import Quickshell.Wayland
55 import Quickshell.Hyprland
66
7+// Control panel: a card at the top right, as tall as the shown page (pages scroll when the screen is shorter).
8+// Header: Doto clock, date, settings + power. Tabs are chips. Slides in from the right.
9+// After glyph-rice's quick settings (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml.
710 PanelWindow {
811 id: win
912 property bool opened: false
@@ -13,13 +16,17 @@ PanelWindow {
1316 signal overlayRequested(string name)
1417 signal settingsRequested()
1518
16− visible: opened
17− anchors { top: true; right: true }
18− margins { top: 6; right: 8 } // sits just under the bar (bar's exclusive zone is respected)
19− implicitWidth: 440
20− implicitHeight: 720
19+ // 0 = closed .. 1 = open; the window stays mapped until the slide-out ends
20+ property real t: opened ? 1 : 0
21+ Behavior on t { NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide } }
22+
23+ visible: t > 0
24+ anchors { top: true; right: true; bottom: true }
25+ margins { top: 6; right: 8; bottom: 8 } // sits just under the bar (bar's exclusive zone is respected)
26+ implicitWidth: card.width + 24 // room for the slide
2127 exclusiveZone: 0
2228 color: "transparent"
29+ mask: Region { item: card } // the empty part below the card stays click-through
2330 WlrLayershell.layer: WlrLayer.Overlay
2431 WlrLayershell.namespace: "aurora-panel"
2532 WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
@@ -32,19 +39,27 @@ PanelWindow {
3239 }
3340
3441 readonly property var tabs: [
35− { icon: Theme.iHome, name: "home" },
36− { icon: Theme.iVolHigh, name: "audio" },
37− { icon: Theme.iWifi, name: "net" },
38− { icon: Theme.iBt, name: "bt" },
39− { icon: Theme.iChip, name: "system" },
40− { icon: Theme.iBell, name: "notifs" }
42+ { name: "home", label: "home" },
43+ { name: "audio", label: "sound" },
44+ { name: "net", label: "net" },
45+ { name: "bt", label: "bt" },
46+ { name: "system", label: "sys" },
47+ { name: "notifs", label: "notif" }
4148 ]
4249
50+ SystemClock { id: clock; precision: SystemClock.Minutes }
51+
4352 Rectangle {
44− anchors.fill: parent
45− color: Theme.bgA
46− border.width: 2
47− border.color: Theme.accent
53+ id: card
54+ anchors.right: parent.right
55+ anchors.rightMargin: -24 * (1 - win.t)
56+ width: 470
57+ height: Math.min(parent.height, col.implicitHeight + 44)
58+ opacity: win.t
59+ radius: 36
60+ color: Qt.rgba(Theme.s1.r, Theme.s1.g, Theme.s1.b, Theme.panelAlpha)
61+ border.width: 1
62+ border.color: Theme.s2
4863 focus: true
4964 Keys.onEscapePressed: win.closeRequested()
5065 // Tab / Shift+Tab cycle through the tabs (wraps around)
@@ -52,66 +67,62 @@ PanelWindow {
5267 Keys.onBacktabPressed: win.tabRequested((win.tab + win.tabs.length - 1) % win.tabs.length)
5368
5469 ColumnLayout {
55− anchors.fill: parent
56− anchors.margins: 12
57− spacing: 10
70+ id: col
71+ x: 22
72+ y: 22
73+ width: parent.width - 44
74+ height: parent.height - 44
75+ spacing: 16
5876
77+ // ---- header: clock, date, settings, power ----
5978 RowLayout {
6079 Layout.fillWidth: true
61− spacing: 2
80+ Layout.leftMargin: 6
81+ spacing: 14
82+ Num {
83+ text: Theme.hh(clock.date) + ":" + Qt.formatDateTime(clock.date, "mm")
84+ font.pixelSize: 44
85+ }
86+ Lb {
87+ Layout.alignment: Qt.AlignVCenter
88+ text: Qt.locale("en_US").toString(clock.date, "ddd d MMM")
89+ }
90+ Item { Layout.fillWidth: true }
91+ IconBtn { implicitHeight: 44; iconSize: 18; icon: Theme.iCog; onClicked: win.settingsRequested() }
92+ IconBtn { implicitHeight: 44; iconSize: 18; icon: Theme.iPower; onClicked: win.overlayRequested("power") }
93+ }
6294
95+ // ---- tabs ----
96+ Row {
97+ Layout.fillWidth: true
98+ spacing: 2
6399 Repeater {
64100 model: win.tabs
65− delegate: Rectangle {
66− id: tabBtn
101+ delegate: Chip {
67102 required property var modelData
68103 required property int index
69− readonly property bool current: win.tab === index
70− implicitWidth: lbl.implicitWidth + 22
71− implicitHeight: 28
72− color: current ? Theme.accent : (tm.containsMouse ? Theme.surfaceHi : "transparent")
73−
74− Text {
75− id: lbl
76− anchors.centerIn: parent
77− text: tabBtn.current ? tabBtn.modelData.icon + " " + tabBtn.modelData.name : tabBtn.modelData.icon
78− color: tabBtn.current ? Theme.bg : Theme.fg
79− font.family: Theme.font
80− font.pixelSize: Theme.fontSize
81− font.bold: true
82− }
83− Rectangle { // unread badge on the notifs tab
84− visible: tabBtn.modelData.name === "notifs" && Notifs.count > 0 && !tabBtn.current
85− width: 6; height: 6
86− anchors.top: parent.top
87− anchors.right: parent.right
88− anchors.margins: 3
89− color: Theme.accent
90− }
91− MouseArea {
92− id: tm
93− anchors.fill: parent
94− hoverEnabled: true
95− cursorShape: Qt.PointingHandCursor
96− onClicked: win.tabRequested(tabBtn.index)
97− }
104+ text: modelData.label
105+ selected: win.tab === index
106+ dot: modelData.name === "notifs" && Notifs.count > 0 && win.tab !== index
107+ onClicked: win.tabRequested(index)
98108 }
99109 }
100− Item { Layout.fillWidth: true }
101− IconBtn { icon: Theme.iCog; implicitHeight: 28; onClicked: win.settingsRequested() }
102− IconBtn { icon: Theme.iClose; implicitHeight: 28; onClicked: win.closeRequested() }
103110 }
104111
105− Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Theme.line }
106−
107112 StackLayout {
113+ id: stack
108114 Layout.fillWidth: true
109115 Layout.fillHeight: true
116+ // as tall as the shown page; the card is capped at the screen height and the page scrolls
117+ Layout.preferredHeight: children[win.tab] ? children[win.tab].implicitHeight : 0
110118 currentIndex: win.tab
111119
112120 HomePage {
113121 onCloseRequested: win.closeRequested()
114122 onOverlayRequested: name => win.overlayRequested(name)
123+ onTabRequested: name => {
124+ for (var i = 0; i < win.tabs.length; i++) if (win.tabs[i].name === name) win.tabRequested(i)
125+ }
115126 }
116127 AudioPage {}
117128 NetPage { active: win.opened && win.tab === 2 }
A .config/quickshell/aurora/DotArt.qml View
@@ -0,0 +1,40 @@
1+import QtQuick
2+import QtQuick.Shapes
3+
4+// A track cover as LED dots in a circle; without a cover, the conical grey gradient from glyph's concept.
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Art.qml.
6+Item {
7+ id: root
8+
9+ property string url
10+ property real step: 6
11+
12+ layer.enabled: true
13+ layer.effect: DotMask { step: root.step; circle: true }
14+
15+ Shape {
16+ anchors.fill: parent
17+ visible: img.status !== Image.Ready
18+ ShapePath {
19+ strokeWidth: -1
20+ fillGradient: ConicalGradient {
21+ centerX: root.width / 2; centerY: root.height / 2; angle: 50
22+ GradientStop { position: 0; color: Theme.fg }
23+ GradientStop { position: 0.2; color: Theme.mute }
24+ GradientStop { position: 0.4; color: Theme.fg }
25+ GradientStop { position: 0.6; color: Theme.s1 }
26+ GradientStop { position: 0.8; color: Theme.grey }
27+ GradientStop { position: 1; color: Theme.fg }
28+ }
29+ PathRectangle { width: root.width; height: root.height }
30+ }
31+ }
32+ Image {
33+ id: img
34+ anchors.fill: parent
35+ source: root.url
36+ fillMode: Image.PreserveAspectCrop
37+ sourceSize: Qt.size(256, 256)
38+ asynchronous: true
39+ }
40+}
A .config/quickshell/aurora/DotMask.qml View
@@ -0,0 +1,13 @@
1+import QtQuick
2+
3+// Dot-matrix mask for layer.effect: `layer.enabled: true; layer.effect: DotMask { step: 4 }`.
4+// The item becomes evenly sized grey LED dots (luminance only). Album covers, app icons, the halftone orb.
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Dm.qml.
6+ShaderEffect {
7+ property real step: 5
8+ readonly property real pitch: step // the shader's uniform: "step" is a GLSL builtin
9+ property bool circle: false // only the dots inside the inscribed circle
10+ readonly property real circ: circle ? 1 : 0
11+ readonly property size size: Qt.size(width, height)
12+ fragmentShader: Qt.resolvedUrl("shaders/dots.frag.qsb")
13+}
M .config/quickshell/aurora/GpuSwitch.qml View
@@ -1,7 +1,7 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4−// envycontrol mode buttons with the two-click arm (SysPage, settings app > Power)
4+// envycontrol mode pills with the two-click arm (SysPage, settings app > Power)
55 ColumnLayout {
66 id: root
77 // two-click arm before switching (needs a reboot to apply)
@@ -15,8 +15,8 @@ ColumnLayout {
1515 }
1616 visible: Gpu.mode !== ""
1717 Layout.fillWidth: true
18− spacing: 8
19− SectionLabel { text: "graphics · envycontrol" }
18+ spacing: 10
19+ SectionLabel { text: "graphics · envycontrol" }
2020 RowLayout {
2121 Layout.fillWidth: true
2222 spacing: 6
@@ -25,6 +25,7 @@ ColumnLayout {
2525 delegate: IconBtn {
2626 required property string modelData
2727 Layout.fillWidth: true
28+ implicitHeight: 44
2829 icon: Theme.iGpu
2930 label: root.armedGpu === modelData ? "sure?" : modelData
3031 active: Gpu.mode === modelData
@@ -33,13 +34,13 @@ ColumnLayout {
3334 }
3435 }
3536 }
36− Text {
37+ Lb {
3738 visible: Gpu.pending !== "" || Gpu.message !== "" || Gpu.busy
3839 Layout.fillWidth: true
40+ Layout.leftMargin: 6
3941 wrapMode: Text.Wrap
40− color: Gpu.message !== "" ? Theme.err : Theme.accent
41− font.family: Theme.font
42− font.pixelSize: Theme.fontSize - 2
43− text: Gpu.busy ? "switching…" : (Gpu.message !== "" ? Gpu.message : "queued: " + Gpu.pending + " · reboot to apply")
42+ px: 10
43+ color: Gpu.message !== "" ? Theme.err : Theme.live
44+ text: Gpu.busy ? "switching…" : (Gpu.message !== "" ? Gpu.message : "queued: " + Gpu.pending + " · reboot to apply")
4445 }
4546 }
M .config/quickshell/aurora/HomePage.qml View
@@ -4,238 +4,253 @@ import Quickshell
44 import Quickshell.Bluetooth
55 import Quickshell.Services.Pipewire
66
7−ColumnLayout {
7+// Control panel > home: Wi-Fi / Bluetooth pills (chevron = their tab), round toggles, desktop actions, capture,
8+// volume + brightness, power profile, the player, the latest notifications. Session actions: the header's power button.
9+// After glyph-rice's quick settings (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml.
10+Flickable {
811 id: page
9− spacing: 10
1012 signal closeRequested()
1113 signal overlayRequested(string name)
14+ signal tabRequested(string name)
1215
13− property string armed: ""
14− Timer { id: disarm; interval: 3000; onTriggered: page.armed = "" }
16+ clip: true
17+ contentWidth: width
18+ contentHeight: col.implicitHeight
19+ implicitHeight: col.implicitHeight
20+ boundsBehavior: Flickable.StopAtBounds
1521
16− function act(name, fn, confirm) {
17− if (confirm && armed !== name) { armed = name; disarm.restart(); return }
18− armed = ""
19− fn()
20− closeRequested()
22+ property bool shotOpen: false // the capture options under the SHOT tile
23+ onVisibleChanged: if (!visible) shotOpen = false
24+
25+ readonly property var sink: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
26+ readonly property var source: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
27+ readonly property var adapter: Bluetooth.defaultAdapter
28+ function btDevice() {
29+ var l = Bluetooth.devices.values
30+ for (var i = 0; i < l.length; i++) if (l[i].connected) return l[i]
31+ return null
2132 }
33+ readonly property var btDev: btDevice()
2234
2335 Timer {
2436 interval: 3000
2537 running: page.visible
2638 repeat: true
39+ triggeredOnStart: true
2740 onTriggered: Bright.refresh()
2841 }
2942
30− MediaCard { Layout.fillWidth: true }
31−
32− SectionLabel { text: "levels" }
33−
34− SliderRow {
35− Layout.fillWidth: true
36− readonly property var audio: Pipewire.defaultAudioSink ? Pipewire.defaultAudioSink.audio : null
37− icon: Theme.volIcon(audio ? audio.volume : 0, audio ? audio.muted : true)
38− value: audio ? audio.volume : 0
39− muted: audio ? audio.muted : false
40− onMoved: v => { if (audio) audio.volume = v }
41− onIconClicked: if (audio) audio.muted = !audio.muted
42− }
43− SliderRow {
44− Layout.fillWidth: true
45− readonly property var audio: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
46− icon: audio && !audio.muted ? Theme.iMic : Theme.iMicOff
47− value: audio ? audio.volume : 0
48− muted: audio ? audio.muted : false
49− onMoved: v => { if (audio) audio.volume = v }
50− onIconClicked: if (audio) audio.muted = !audio.muted
51− }
52− SliderRow {
53− Layout.fillWidth: true
54− visible: Bright.available
55− icon: Theme.iBright
56− value: Bright.percent / 100
57− valueText: Bright.percent + "%"
58− onMoved: v => Bright.setPercent(Math.max(0.01, v) * 100)
59− }
43+ ColumnLayout {
44+ id: col
45+ width: page.width
46+ spacing: 16
6047
61− SectionLabel { text: "quick toggles" }
62−
63− RowLayout {
64− Layout.fillWidth: true
65− spacing: 6
66− IconBtn {
67− Layout.fillWidth: true
68− icon: Net.wifiEnabled ? Theme.iWifi : Theme.iWifiOff
69− label: "wifi"
70− active: Net.wifiEnabled
71− onClicked: Net.setWifi(!Net.wifiEnabled)
72− }
73− IconBtn {
74− Layout.fillWidth: true
75− readonly property var ad: Bluetooth.defaultAdapter
76− visible: ad !== null
77− icon: ad && ad.enabled ? Theme.iBt : Theme.iBtOff
78− label: "bt"
79− active: ad ? ad.enabled : false
80− onClicked: if (ad) ad.enabled = !ad.enabled
81− }
82− IconBtn {
83− Layout.fillWidth: true
84− icon: Notifs.dnd ? Theme.iBellOff : Theme.iBell
85− label: "dnd"
86− active: Notifs.dnd
87− onClicked: Notifs.dnd = !Notifs.dnd
88− }
89− IconBtn {
90− Layout.fillWidth: true
91− readonly property var audio: Pipewire.defaultAudioSource ? Pipewire.defaultAudioSource.audio : null
92− icon: audio && audio.muted ? Theme.iMicOff : Theme.iMic
93− label: "mic"
94− active: audio ? !audio.muted : false
95− onClicked: if (audio) audio.muted = !audio.muted
96− }
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 the focused monitor; while recording: stop
48+ // ---- Wi-Fi / Bluetooth ----
49+ RowLayout {
12350 Layout.fillWidth: true
124− visible: Rec.available
125− icon: Rec.busy ? Theme.iStop : Theme.iRec
126− label: Rec.running ? Theme.fmtClock(Rec.elapsed) : "rec"
127− active: Rec.busy
128− onClicked: { if (!Rec.busy) page.closeRequested(); Rec.toggle("screen") }
51+ spacing: 12
52+ TogglePill {
53+ Layout.fillWidth: true
54+ Layout.preferredWidth: 1
55+ on: Net.wifiEnabled
56+ icon: Net.wifiEnabled ? Theme.wifiIcon(Net.connected !== "" ? Net.activeSignal : 0) : Theme.iWifiOff
57+ name: "Wi-Fi"
58+ alert: Net.portal
59+ sub: {
60+ if (Net.portal) return "sign in · " + Net.portalNet
61+ if (!Net.wifiEnabled) return Net.wired ? "off · wired" : "off"
62+ if (Net.connected !== "") return Net.connected
63+ return Net.wired ? "wired · " + Net.wiredDevs[0].name : "not connected"
64+ }
65+ onClicked: Net.setWifi(!Net.wifiEnabled)
66+ onMenu: page.tabRequested("net")
67+ }
68+ TogglePill {
69+ Layout.fillWidth: true
70+ Layout.preferredWidth: 1
71+ on: page.adapter !== null && page.adapter.enabled
72+ icon: !on ? Theme.iBtOff : (page.btDev ? Theme.iBtConn : Theme.iBt)
73+ name: "Bluetooth"
74+ sub: page.adapter === null ? "no adapter" : (!page.adapter.enabled ? "off"
75+ : (page.btDev ? (page.btDev.name || page.btDev.address) : "on"))
76+ onClicked: if (page.adapter) page.adapter.enabled = !page.adapter.enabled
77+ onMenu: page.tabRequested("bt")
78+ }
12979 }
130− }
131−
132− SectionLabel { text: "desktop" }
13380
134− RowLayout {
135− Layout.fillWidth: true
136− spacing: 6
137− IconBtn {
138− Layout.fillWidth: true
139− icon: Theme.iImage
140− label: "wallpaper"
141− onClicked: page.overlayRequested("wall")
142− }
143− IconBtn {
144− Layout.fillWidth: true
145− icon: Theme.iShuffle
146− label: "random"
147− onClicked: Wall.random()
148− }
149− IconBtn {
81+ // ---- toggles ----
82+ Grid {
83+ id: tiles
15084 Layout.fillWidth: true
151− icon: Theme.iWindow
152− label: "overview"
153− onClicked: page.overlayRequested("overview")
85+ Layout.preferredWidth: 1
86+ columns: 4
87+ rowSpacing: 14
88+ readonly property real cw: width / 4
89+ Tile {
90+ width: tiles.cw
91+ icon: Theme.iBellOff
92+ name: "dnd"
93+ on: Notifs.dnd
94+ onClicked: Notifs.dnd = !Notifs.dnd
95+ }
96+ Tile {
97+ width: tiles.cw
98+ icon: page.source && !page.source.muted ? Theme.iMic : Theme.iMicOff
99+ name: "mic"
100+ on: page.source !== null && !page.source.muted
101+ onClicked: if (page.source) page.source.muted = !page.source.muted
102+ }
103+ Tile {
104+ width: tiles.cw
105+ icon: Theme.iCoffee
106+ name: "caffeine"
107+ on: Toggles.caffeine
108+ onClicked: Toggles.caffeine = !Toggles.caffeine
109+ }
110+ Tile {
111+ width: tiles.cw
112+ icon: Theme.iPlane
113+ name: "airplane"
114+ on: Toggles.airplane
115+ onClicked: Toggles.setAirplane(!Toggles.airplane)
116+ }
117+ Tile {
118+ width: tiles.cw
119+ visible: Toggles.hasNightLight
120+ icon: Theme.iNight
121+ name: "night"
122+ on: Toggles.nightLight
123+ onClicked: Toggles.setNightLight(!Toggles.nightLight)
124+ }
125+ Tile { // click: record the focused monitor, right click: a region; while recording: stop
126+ width: tiles.cw
127+ visible: Rec.available
128+ icon: Rec.busy ? Theme.iStop : Theme.iRec
129+ name: Rec.running ? Theme.fmtClock(Rec.elapsed) : (Rec.selecting ? "select" : "rec")
130+ live: Rec.busy
131+ onClicked: { if (!Rec.busy) page.closeRequested(); Rec.toggle("screen") }
132+ onMenu: { if (!Rec.busy) { page.closeRequested(); Rec.toggle("region") } }
133+ }
134+ Tile {
135+ width: tiles.cw
136+ visible: Toggles.hasPicker
137+ icon: Theme.iPicker
138+ name: "colour"
139+ onClicked: { page.closeRequested(); Toggles.pickColour() }
140+ }
141+ Tile {
142+ width: tiles.cw
143+ icon: Theme.iShot
144+ name: "shot"
145+ on: page.shotOpen
146+ onClicked: page.shotOpen = !page.shotOpen
147+ onMenu: page.shotOpen = !page.shotOpen
148+ }
154149 }
155− IconBtn {
156− Layout.fillWidth: true
157− visible: Clip.available
158− icon: Theme.iClipboard
159− label: "clipboard"
160− onClicked: page.overlayRequested("clip")
161− }
162− }
163150
164− SectionLabel { text: "capture" }
165−
166− RowLayout {
167− Layout.fillWidth: true
168− spacing: 6
169− IconBtn { // where the shot goes: clipboard or ~/pix/ss
151+ // ---- capture (under SHOT): clipboard or ~/pix/ss, then the kind of shot ----
152+ Rectangle {
153+ visible: page.shotOpen
170154 Layout.fillWidth: true
171− icon: Shot.save ? Theme.iSave : Theme.iClipboard
172− label: Shot.save ? "save" : "copy"
173− active: true
174− onClicked: Shot.save = !Shot.save
155+ implicitHeight: cap.implicitHeight + 24
156+ radius: 30
157+ color: Theme.s2
158+ ColumnLayout {
159+ id: cap
160+ x: 12
161+ y: 12
162+ width: parent.width - 24
163+ spacing: 10
164+ Segmented {
165+ Layout.fillWidth: true
166+ implicitHeight: 44
167+ color: Theme.s3
168+ options: [{ value: false, label: "copy" }, { value: true, label: "save" }]
169+ value: Shot.save
170+ onPicked: v => Shot.save = v
171+ }
172+ RowLayout {
173+ Layout.fillWidth: true
174+ spacing: 6
175+ Repeater {
176+ model: [{ mode: "region", label: "area" }, { mode: "window", label: "window" },
177+ { mode: "output", label: "screen" }, { mode: "ocr", label: "ocr" }]
178+ delegate: Chip {
179+ required property var modelData
180+ Layout.fillWidth: true
181+ text: modelData.label
182+ filled: true
183+ onClicked: { page.closeRequested(); Shot.take(modelData.mode) }
184+ }
185+ }
186+ }
187+ }
175188 }
176− IconBtn {
189+
190+ // ---- desktop ----
191+ RowLayout {
177192 Layout.fillWidth: true
178− icon: Theme.iCrop
179− label: "area"
180− onClicked: { page.closeRequested(); Shot.take("region") }
193+ spacing: 0
194+ Tile { Layout.fillWidth: true; Layout.preferredWidth: 1; size: 44; icon: Theme.iImage; name: "wallpaper"; onClicked: page.overlayRequested("wall") }
195+ Tile { Layout.fillWidth: true; Layout.preferredWidth: 1; size: 44; icon: Theme.iShuffle; name: "random"; onClicked: Wall.random() }
196+ Tile { Layout.fillWidth: true; Layout.preferredWidth: 1; size: 44; icon: Theme.iDashboard; name: "overview"; onClicked: page.overlayRequested("overview") }
197+ Tile { Layout.fillWidth: true; Layout.preferredWidth: 1; size: 44; visible: Clip.available; icon: Theme.iClipboard; name: "clipboard"; onClicked: page.overlayRequested("clip") }
181198 }
182− IconBtn {
199+
200+ // ---- levels (mic level is in the SOUND tab) ----
201+ SliderRow {
183202 Layout.fillWidth: true
184− icon: Theme.iWindow
185− label: "window"
186− onClicked: { page.closeRequested(); Shot.take("window") }
203+ icon: Theme.volIcon(page.sink ? page.sink.volume : 0, page.sink ? page.sink.muted : true)
204+ value: page.sink ? page.sink.volume : 0
205+ muted: page.sink ? page.sink.muted : false
206+ onMoved: v => { if (page.sink) page.sink.volume = v }
207+ onIconClicked: if (page.sink) page.sink.muted = !page.sink.muted
187208 }
188− IconBtn {
209+ SliderRow {
189210 Layout.fillWidth: true
190− icon: Theme.iMonitor
191− label: "screen"
192− onClicked: { page.closeRequested(); Shot.take("output") }
211+ visible: Bright.available
212+ icon: Theme.iBright
213+ value: Bright.percent / 100
214+ valueText: Bright.percent + "%"
215+ onMoved: v => Bright.setPercent(Math.max(0.01, v) * 100)
193216 }
194− IconBtn {
217+
218+ // ---- power profile (only with power-profiles-daemon) ----
219+ Segmented {
195220 Layout.fillWidth: true
196− icon: Theme.iSearch
197− label: "ocr"
198− onClicked: { page.closeRequested(); Shot.take("ocr") }
221+ visible: Battery.hasProfiles
222+ options: {
223+ var o = [{ value: "saver", label: "saver", ticks: 1 }, { value: "balanced", label: "bal", ticks: 2 }]
224+ if (Battery.hasPerf) o.push({ value: "performance", label: "perf", ticks: 4 })
225+ if (Battery.hasBat) o.push({ value: "auto", label: "auto" })
226+ return o
227+ }
228+ value: Battery.profile
229+ onPicked: v => Battery.setProfile(v)
199230 }
200− }
201231
202− Item { Layout.fillHeight: true }
232+ MediaCard { Layout.fillWidth: true }
203233
204− SectionLabel { text: "session" }
205−
206− RowLayout {
207− Layout.fillWidth: true
208− spacing: 6
209− IconBtn {
210− Layout.fillWidth: true
211− icon: Theme.iLock
212− onClicked: page.act("lock", function () { Quickshell.execDetached(["loginctl", "lock-session"]) }, false)
234+ // ---- the latest notifications (all of them: NOTIF tab) ----
235+ SectionLabel {
236+ visible: Notifs.count > 0
237+ text: "notifications"
238+ count: String(Notifs.count)
239+ action: "clear all"
240+ onActionClicked: Notifs.clearAll()
213241 }
214− IconBtn {
215− Layout.fillWidth: true
216− icon: Theme.iSleep
217− onClicked: page.act("sleep", function () { Quickshell.execDetached(["systemctl", "suspend"]) }, false)
242+ Repeater {
243+ model: { Notifs.count; return Notifs.history().slice(0, 2) }
244+ delegate: NotifCard {
245+ required property var modelData
246+ n: modelData
247+ }
218248 }
219− IconBtn {
220− Layout.fillWidth: true
221− icon: Theme.iLogout
222− label: page.armed === "logout" ? "sure?" : ""
223− active: page.armed === "logout"
224− onClicked: page.act("logout", function () { Hypr.logout() }, true)
225− }
226− IconBtn {
227− Layout.fillWidth: true
228− icon: Theme.iReboot
229− label: page.armed === "reboot" ? "sure?" : ""
230− active: page.armed === "reboot"
231− onClicked: page.act("reboot", function () { Quickshell.execDetached(["systemctl", "reboot"]) }, true)
232− }
233− IconBtn {
234− Layout.fillWidth: true
235− icon: Theme.iPower
236− label: page.armed === "off" ? "sure?" : ""
237− active: page.armed === "off"
238− onClicked: page.act("off", function () { Quickshell.execDetached(["systemctl", "poweroff"]) }, true)
249+ Chip {
250+ visible: Notifs.count > 2
251+ Layout.alignment: Qt.AlignHCenter
252+ text: "+ " + (Notifs.count - 2) + " more"
253+ onClicked: page.tabRequested("notifs")
239254 }
240255 }
241256 }
A .config/quickshell/aurora/Icon.qml View
@@ -0,0 +1,13 @@
1+import QtQuick
2+
3+// A Nerd Font (Material Design) glyph in a 1em box: the font's line is 1.32em, the padding trims it so icons centre.
4+// Glyphs come from Theme (Theme.iFoo), never pasted into files.
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Icon.qml.
6+Text {
7+ readonly property real lead: -0.16 * font.pixelSize
8+ font.family: Theme.nf
9+ font.pixelSize: 19
10+ topPadding: lead
11+ bottomPadding: lead
12+ color: Theme.fg
13+}
M .config/quickshell/aurora/IconBtn.qml View
@@ -1,38 +1,37 @@
11 import QtQuick
22
3+// Round icon button (icon only) or pill (icon and/or caps label). Off = dark fill, active = white with black content.
34 Rectangle {
45 id: b
56 property string icon: ""
67 property string label: ""
78 property bool active: false
8− property int iconSize: 15
9+ property int iconSize: 16
910 signal clicked()
1011
11− implicitWidth: Math.max(30, content.implicitWidth + 16)
12− implicitHeight: 30
13− color: active ? Theme.accent : (m.containsMouse ? Theme.surfaceHi : "transparent")
14− border.width: 1
15− border.color: (active || m.containsMouse) ? Theme.accent : Theme.line
12+ implicitHeight: 36
13+ implicitWidth: label === "" ? height : content.implicitWidth + 28
14+ radius: height / 2
15+ color: active ? Theme.fill : (m.containsMouse ? Theme.s3 : Theme.s2)
16+ Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
1617
1718 Row {
1819 id: content
1920 anchors.centerIn: parent
20− spacing: 6
21− Text {
21+ spacing: 8
22+ Icon {
2223 visible: b.icon !== ""
24+ anchors.verticalCenter: parent.verticalCenter
2325 text: b.icon
24− color: b.active ? Theme.bg : Theme.fg
25− font.family: Theme.font
2626 font.pixelSize: b.iconSize
27− anchors.verticalCenter: parent.verticalCenter
27+ color: b.active ? Theme.fillFg : Theme.fg
2828 }
29− Text {
29+ Lb {
3030 visible: b.label !== ""
31− text: b.label
32− color: b.active ? Theme.bg : Theme.fg
33− font.family: Theme.font
34− font.pixelSize: Theme.fontSize - 1
3531 anchors.verticalCenter: parent.verticalCenter
32+ text: b.label
33+ px: 11
34+ color: b.active ? Theme.fillFg : Theme.fg
3635 }
3736 }
3837
A .config/quickshell/aurora/Lb.qml View
@@ -0,0 +1,12 @@
1+import QtQuick
2+
3+// Label: Space Mono caps, tracking .14em, grey. Size via `px` (the tracking is derived from it).
4+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Lb.qml.
5+Text {
6+ property real px: 12
7+ font.family: Theme.mono
8+ font.pixelSize: px
9+ font.letterSpacing: px * 0.14
10+ font.capitalization: Font.AllUppercase
11+ color: Theme.grey
12+}
M .config/quickshell/aurora/ListRow.qml View
@@ -1,6 +1,8 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4+// A list row: icon in a circle (white when `current`: connected, default device...), title over a caps sub line,
5+// a caps trailing value. Anything declared inside a ListRow {} lands in the right-hand slot (small round buttons).
46 Rectangle {
57 id: r
68 property string icon: ""
@@ -9,14 +11,12 @@ Rectangle {
911 property string trailing: ""
1012 property bool current: false
1113 signal clicked()
12− // anything declared inside a ListRow{} lands in the right-hand slot
1314 default property alias extra: extraRow.data
1415
1516 Layout.fillWidth: true
16− implicitHeight: 42
17− color: m.containsMouse ? Theme.surfaceHi : (current ? Theme.surface : "transparent")
18− border.width: current ? 1 : 0
19− border.color: Theme.accent
17+ implicitHeight: 56
18+ radius: height / 2
19+ color: m.containsMouse ? Theme.s2 : "transparent"
2020
2121 MouseArea {
2222 id: m
@@ -26,47 +26,61 @@ Rectangle {
2626 onClicked: r.clicked()
2727 }
2828
29− RowLayout {
30− anchors.fill: parent
31− anchors.leftMargin: 10
32− anchors.rightMargin: 6
33− spacing: 10
34−
35− Text {
29+ Rectangle {
30+ id: ic
31+ x: 6
32+ anchors.verticalCenter: parent.verticalCenter
33+ width: 44
34+ height: 44
35+ radius: 22
36+ color: r.current ? Theme.fill : (m.containsMouse ? Theme.s3 : Theme.s2)
37+ Icon {
38+ anchors.centerIn: parent
3639 text: r.icon
37− color: r.current ? Theme.accent : Theme.fg
38− font.family: Theme.font
39− font.pixelSize: 17
40− }
41− ColumnLayout {
42− Layout.fillWidth: true
43− spacing: 0
44− Text {
45− text: r.title
46− Layout.fillWidth: true
47− elide: Text.ElideRight
48− color: Theme.fg
49− font.family: Theme.font
50− font.pixelSize: Theme.fontSize
51− font.bold: r.current
52− }
53− Text {
54− visible: r.subtitle !== ""
55− text: r.subtitle
56− Layout.fillWidth: true
57− elide: Text.ElideRight
58− color: r.current ? Theme.accent : Theme.dim
59− font.family: Theme.font
60− font.pixelSize: Theme.fontSize - 3
61− }
40+ font.pixelSize: 19
41+ color: r.current ? Theme.fillFg : Theme.fg
6242 }
43+ }
44+ Column {
45+ anchors.left: ic.right
46+ anchors.leftMargin: 14
47+ anchors.right: tail.left
48+ anchors.rightMargin: 10
49+ anchors.verticalCenter: parent.verticalCenter
50+ spacing: 2
6351 Text {
52+ width: parent.width
53+ elide: Text.ElideRight
54+ text: r.title
55+ font.family: Theme.ui
56+ font.pixelSize: 15
57+ font.weight: Font.DemiBold
58+ color: Theme.fg
59+ }
60+ Lb {
61+ visible: r.subtitle !== ""
62+ width: parent.width
63+ elide: Text.ElideRight
64+ text: r.subtitle
65+ px: 10
66+ }
67+ }
68+ Row {
69+ id: tail
70+ anchors.right: parent.right
71+ anchors.rightMargin: 10
72+ anchors.verticalCenter: parent.verticalCenter
73+ spacing: 8
74+ Lb {
6475 visible: r.trailing !== ""
76+ anchors.verticalCenter: parent.verticalCenter
6577 text: r.trailing
66− color: Theme.dim
67− font.family: Theme.font
68− font.pixelSize: Theme.fontSize - 2
78+ px: 10
79+ }
80+ Row {
81+ id: extraRow
82+ anchors.verticalCenter: parent.verticalCenter
83+ spacing: 6
6984 }
70− Row { id: extraRow; spacing: 4 }
7185 }
7286 }
A .config/quickshell/aurora/Media.qml View
@@ -0,0 +1,28 @@
1+pragma Singleton
2+import QtQuick
3+import Quickshell
4+import Quickshell.Services.Mpris
5+
6+// The MPRIS player the UI shows: the first one playing, else the first one (control panel, home screen).
7+// MPRIS doesn't push position updates: whoever shows the position pokes positionChanged() on a timer while playing.
8+Singleton {
9+ id: root
10+
11+ function pick() {
12+ var l = Mpris.players.values, first = null
13+ for (var i = 0; i < l.length; i++) {
14+ if (l[i].isPlaying) return l[i]
15+ if (first === null) first = l[i]
16+ }
17+ return first
18+ }
19+ readonly property var player: pick()
20+ readonly property bool playing: player !== null && player.isPlaying
21+ readonly property bool seekable: player !== null && player.lengthSupported && player.length > 0
22+
23+ function fmt(s) { // 75 -> "1:15"
24+ s = Math.max(0, Math.floor(s))
25+ var m = Math.floor(s / 60), r = s % 60
26+ return m + ":" + (r < 10 ? "0" : "") + r
27+ }
28+}
M .config/quickshell/aurora/MediaCard.qml View
@@ -1,152 +1,91 @@
11 import QtQuick
2−import QtQuick.Layouts
3−import Quickshell.Services.Mpris
42
3+// Mini player (control panel): the cover as dots in a circle, title / artist, a dotted seek bar, prev · play · next.
4+// Hidden while no MPRIS player exists. Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml.
55 Rectangle {
66 id: card
7−
8− function pick() {
9− var l = Mpris.players.values, first = null
10− for (var i = 0; i < l.length; i++) {
11− if (l[i].isPlaying) return l[i]
12− if (first === null) first = l[i]
13− }
14− return first
15− }
16− function fmt(s) {
17− s = Math.max(0, Math.floor(s))
18− var m = Math.floor(s / 60), r = s % 60
19− return m + ":" + (r < 10 ? "0" : "") + r
20− }
21−
22− readonly property var player: pick()
7+ readonly property var player: Media.player
238 readonly property bool has: player !== null
249 readonly property bool seekable: has && player.lengthSupported && player.length > 0
2510
26− implicitHeight: has ? (seekable ? 156 : 122) : 56
27− color: Theme.surface
28− border.width: 1
29− border.color: Theme.line
11+ visible: has
12+ implicitHeight: 92
13+ radius: 30
14+ color: Theme.s2
3015
3116 // MPRIS doesn't push position updates, so poke it while playing
3217 Timer {
33− running: card.has && card.player.isPlaying && card.visible
18+ running: card.has && card.player.isPlaying && card.visible && card.seekable
3419 interval: 500
3520 repeat: true
3621 onTriggered: card.player.positionChanged()
3722 }
3823
39− Text {
40− visible: !card.has
41− anchors.centerIn: parent
42− text: Theme.iMusic + " nothing playing"
43− color: Theme.dim
44− font.family: Theme.font
45− font.pixelSize: Theme.fontSize
24+ DotArt {
25+ id: art
26+ x: 14
27+ anchors.verticalCenter: parent.verticalCenter
28+ width: 64
29+ height: 64
30+ step: 4
31+ url: card.has ? card.player.trackArtUrl : ""
4632 }
47−
48− ColumnLayout {
49− visible: card.has
50− anchors.fill: parent
51− anchors.margins: 10
52− spacing: 6
53−
54− RowLayout {
55− spacing: 10
56− Rectangle {
57− Layout.preferredWidth: 60
58− Layout.preferredHeight: 60
59− color: Theme.surfaceHi
60− Text {
61− anchors.centerIn: parent
62− text: Theme.iMusic
63− color: Theme.dim
64− font.family: Theme.font
65− font.pixelSize: 24
66− }
67− Image {
68− id: art
69− anchors.fill: parent
70− source: card.has ? card.player.trackArtUrl : ""
71− fillMode: Image.PreserveAspectCrop
72− asynchronous: true
73− visible: status === Image.Ready
74− }
75− }
76− ColumnLayout {
77− Layout.fillWidth: true
78− spacing: 2
79− Text {
80− Layout.fillWidth: true
81− elide: Text.ElideRight
82− text: card.has ? (card.player.trackTitle || "unknown title") : ""
83− color: Theme.fg
84− font.family: Theme.font
85− font.pixelSize: Theme.fontSize + 1
86− font.bold: true
87− }
88− Text {
89− Layout.fillWidth: true
90− elide: Text.ElideRight
91− text: card.has ? (card.player.trackArtist || "unknown artist") : ""
92− color: Theme.accent
93− font.family: Theme.font
94− font.pixelSize: Theme.fontSize
95− }
96− Text {
97− Layout.fillWidth: true
98− elide: Text.ElideRight
99− text: card.has ? ((card.player.trackAlbum ? card.player.trackAlbum + " · " : "") + card.player.identity) : ""
100− color: Theme.dim
101− font.family: Theme.font
102− font.pixelSize: Theme.fontSize - 2
103− }
104− }
33+ Column {
34+ anchors.left: art.right
35+ anchors.leftMargin: 14
36+ anchors.right: ctl.left
37+ anchors.rightMargin: 14
38+ anchors.verticalCenter: parent.verticalCenter
39+ spacing: 2
40+ Text {
41+ width: parent.width
42+ elide: Text.ElideRight
43+ text: card.has ? (card.player.trackTitle || "Nothing on") : ""
44+ font.family: Theme.ui
45+ font.pixelSize: 15
46+ font.weight: Font.DemiBold
47+ color: Theme.fg
10548 }
106−
107− RowLayout {
108− visible: card.seekable
109− spacing: 8
49+ Item {
50+ width: parent.width
51+ height: artist.implicitHeight
11052 Text {
111− text: card.seekable ? card.fmt(card.player.position) : ""
112− color: Theme.dim
113− font.family: Theme.font
114− font.pixelSize: Theme.fontSize - 2
115− }
116− BarSlider {
117− Layout.fillWidth: true
118− from: 0
119− to: card.seekable ? card.player.length : 1
120− value: card.seekable ? card.player.position : 0
121− onMoved: v => { if (card.player.canSeek) card.player.position = v }
53+ id: artist
54+ anchors.left: parent.left
55+ anchors.right: time.left
56+ anchors.rightMargin: 8
57+ elide: Text.ElideRight
58+ text: card.has ? (card.player.trackArtist || card.player.identity) : ""
59+ font.family: Theme.ui
60+ font.pixelSize: 13
61+ color: Theme.fgDim
12262 }
123− Text {
124− text: card.seekable ? card.fmt(card.player.length) : ""
125− color: Theme.dim
126− font.family: Theme.font
127− font.pixelSize: Theme.fontSize - 2
63+ Lb {
64+ id: time
65+ visible: card.seekable
66+ anchors.right: parent.right
67+ anchors.verticalCenter: parent.verticalCenter
68+ px: 9
69+ text: card.seekable ? Media.fmt(card.player.position) + " / " + Media.fmt(card.player.length) : ""
12870 }
12971 }
130−
131− RowLayout {
132− Layout.alignment: Qt.AlignHCenter
133− spacing: 8
134− IconBtn {
135− icon: Theme.iPrev
136− opacity: card.has && card.player.canGoPrevious ? 1 : 0.4
137− onClicked: if (card.player.canGoPrevious) card.player.previous()
138− }
139− IconBtn {
140− icon: card.has && card.player.isPlaying ? Theme.iPause : Theme.iPlay
141− active: true
142− implicitWidth: 44
143− onClicked: if (card.player.canTogglePlaying) card.player.togglePlaying()
144− }
145− IconBtn {
146− icon: Theme.iNext
147− opacity: card.has && card.player.canGoNext ? 1 : 0.4
148− onClicked: if (card.player.canGoNext) card.player.next()
149− }
72+ BarSlider {
73+ visible: card.seekable
74+ width: parent.width
75+ implicitHeight: 16
76+ from: 0
77+ to: card.seekable ? card.player.length : 1
78+ value: card.seekable ? card.player.position : 0
79+ onMoved: v => { if (card.player.canSeek) card.player.position = v }
15080 }
15181 }
82+ PlayControls {
83+ id: ctl
84+ anchors.right: parent.right
85+ anchors.rightMargin: 16
86+ anchors.verticalCenter: parent.verticalCenter
87+ player: card.player
88+ size: 18
89+ play: 44
90+ }
15291 }
M .config/quickshell/aurora/NetPage.qml View
@@ -1,6 +1,8 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4+// Control panel > net (also the settings app's network section): wired / tethering, Wi-Fi radio + scan, networks.
5+// Click a row = connect (a new secured one asks for its password in a pill under it).
46 ColumnLayout {
57 id: page
68 property bool active: false
@@ -8,13 +10,13 @@ ColumnLayout {
810 // the settings app creates this page already active and destroys it when leaving the section
911 Component.onCompleted: if (active) Net.active = true
1012 Component.onDestruction: if (active) Net.active = false
11− spacing: 8
13+ spacing: 10
1214
1315 // wired / usb tethering (read-only status, plus a disconnect button)
1416 ColumnLayout {
1517 visible: Net.wiredDevs.length > 0
1618 Layout.fillWidth: true
17− spacing: 2
19+ spacing: 4
1820 SectionLabel { text: "wired" }
1921 Repeater {
2022 model: Net.wiredDevs
@@ -23,18 +25,18 @@ ColumnLayout {
2325 icon: modelData.usb ? Theme.iPhone : Theme.iEthernet
2426 title: modelData.name
2527 subtitle: Net.portal && Net.portalNet === modelData.name ? "sign in required"
26− : modelData.dev + (modelData.ip !== "" ? " · " + modelData.ip : "")
28+ : modelData.dev + (modelData.ip !== "" ? " · " + modelData.ip : "")
2729 trailing: modelData.usb ? "usb" : ""
2830 current: true
2931 IconBtn {
3032 visible: Net.portal && Net.portalNet === modelData.name
3133 icon: Theme.iLogin
32− height: 26
34+ implicitHeight: 32
3335 onClicked: Net.openPortal()
3436 }
3537 IconBtn {
3638 icon: Theme.iClose
37− height: 26
39+ implicitHeight: 32
3840 onClicked: Net.disconnectDev(modelData.dev)
3941 }
4042 }
@@ -43,15 +45,14 @@ ColumnLayout {
4345
4446 RowLayout {
4547 Layout.fillWidth: true
46− spacing: 6
47− Text {
48+ Layout.leftMargin: 6
49+ spacing: 8
50+ Lb {
4851 Layout.fillWidth: true
4952 elide: Text.ElideRight
50− text: !Net.wifiEnabled ? "wi-fi off"
51− : (Net.connected !== "" ? "wi-fi · " + Net.connected : "wi-fi not connected")
52− color: Net.connected !== "" && Net.wifiEnabled ? Theme.accent : Theme.dim
53− font.family: Theme.font
54− font.pixelSize: Theme.fontSize
53+ text: !Net.wifiEnabled ? "wi-fi · off"
54+ : (Net.connected !== "" ? "wi-fi · " + Net.connected : "wi-fi · not connected")
55+ color: Net.connected !== "" && Net.wifiEnabled ? Theme.fg : Theme.grey
5556 }
5657 IconBtn {
5758 icon: Theme.iRefresh
@@ -65,22 +66,23 @@ ColumnLayout {
6566 }
6667 }
6768
68− Text {
69+ Lb {
6970 visible: Net.message !== ""
7071 Layout.fillWidth: true
72+ Layout.leftMargin: 6
7173 text: Net.message
7274 wrapMode: Text.WordWrap
75+ px: 10
7376 color: Theme.err
74− font.family: Theme.font
75− font.pixelSize: Theme.fontSize - 2
7677 }
7778
78− SectionLabel { text: Net.busy ? "working…" : "networks" }
79+ SectionLabel { text: Net.busy ? "working…" : "networks"; count: Net.busy || !Net.wifiEnabled ? "" : String(Net.networks.length) }
7980
8081 Flickable {
8182 id: flick
8283 Layout.fillWidth: true
8384 Layout.fillHeight: true
85+ Layout.preferredHeight: list.implicitHeight
8486 clip: true
8587 contentWidth: width
8688 contentHeight: list.implicitHeight
@@ -97,7 +99,7 @@ ColumnLayout {
9799 id: item
98100 required property var modelData
99101 Layout.fillWidth: true
100− spacing: 0
102+ spacing: 4
101103
102104 ListRow {
103105 id: row
@@ -115,62 +117,80 @@ ColumnLayout {
115117 IconBtn {
116118 visible: row.portal
117119 icon: Theme.iLogin
118− height: 26
120+ implicitHeight: 32
119121 onClicked: Net.openPortal()
120122 }
121123 IconBtn {
122124 visible: item.modelData.inUse
123125 icon: Theme.iClose
124− height: 26
126+ implicitHeight: 32
125127 onClicked: Net.disconnect()
126128 }
127129 IconBtn {
128130 visible: item.modelData.saved
129131 icon: Theme.iTrash
130− height: 26
132+ implicitHeight: 32
131133 onClicked: Net.forget(item.modelData.ssid)
132134 }
133135 }
134136
137+ // password for a new secured network: a pill with white dots and the accent caret
135138 Rectangle {
136139 id: pwBox
137140 visible: Net.askFor === item.modelData.ssid
138141 Layout.fillWidth: true
139− Layout.preferredHeight: visible ? 34 : 0
140− color: Theme.surface
142+ Layout.preferredHeight: visible ? 52 : 0
143+ radius: height / 2
144+ color: Theme.s2
141145 border.width: 1
142− border.color: Theme.accent
146+ border.color: pw.activeFocus ? Theme.fill : Theme.s3
143147 onVisibleChanged: if (visible) { pw.text = ""; pw.forceActiveFocus() }
144148
145− RowLayout {
146− anchors.fill: parent
147− anchors.leftMargin: 10
148− anchors.rightMargin: 4
149− TextInput {
150− id: pw
151− Layout.fillWidth: true
152− Layout.alignment: Qt.AlignVCenter
153− echoMode: TextInput.Password
154− color: Theme.fg
155− selectionColor: Theme.accent
156− selectedTextColor: Theme.bg
157− font.family: Theme.font
158− font.pixelSize: Theme.fontSize
159− clip: true
160− onAccepted: Net.connect(item.modelData.ssid, text)
161− Text {
162− visible: !pw.text
163− text: "password…"
164− color: Theme.dim
165− font: pw.font
149+ TextInput {
150+ id: pw
151+ anchors.left: parent.left
152+ anchors.leftMargin: 22
153+ anchors.right: go.left
154+ anchors.rightMargin: 10
155+ anchors.verticalCenter: parent.verticalCenter
156+ echoMode: TextInput.Password
157+ passwordCharacter: "•"
158+ color: Theme.fg
159+ selectionColor: Theme.fill
160+ selectedTextColor: Theme.fillFg
161+ font.family: Theme.mono
162+ font.pixelSize: 15
163+ font.letterSpacing: 2
164+ clip: true
165+ cursorDelegate: Rectangle {
166+ width: 2
167+ color: Theme.live
168+ visible: pw.activeFocus
169+ Timer {
170+ running: pw.activeFocus
171+ interval: 500
172+ repeat: true
173+ onTriggered: parent.opacity = 1 - parent.opacity
166174 }
167175 }
168− IconBtn {
169− icon: Theme.iCheck
170− height: 26
171− onClicked: Net.connect(item.modelData.ssid, pw.text)
176+ onAccepted: Net.connect(item.modelData.ssid, text)
177+ Lb {
178+ visible: !pw.text
179+ anchors.verticalCenter: parent.verticalCenter
180+ text: "password"
181+ color: Theme.mute
172182 }
173183 }
184+ IconBtn {
185+ id: go
186+ anchors.right: parent.right
187+ anchors.rightMargin: 6
188+ anchors.verticalCenter: parent.verticalCenter
189+ implicitHeight: 40
190+ icon: Theme.iCheck
191+ active: pw.text !== ""
192+ onClicked: Net.connect(item.modelData.ssid, pw.text)
193+ }
174194 }
175195 }
176196 }
M .config/quickshell/aurora/NotifCard.qml View
@@ -1,117 +1,149 @@
11 import QtQuick
22 import QtQuick.Layouts
3+import QtQuick.Effects
34 import Quickshell
45 import Quickshell.Services.Notifications
56
7+// A notification (panel history, toasts): the app icon in a circle (dots / mono / colour, Settings.appIcons),
8+// APP · 3 MIN, summary, body, other actions as pills. Click = the default action, if the app sent one.
9+// Critical = accent border. Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml.
610 Rectangle {
711 id: card
812 required property var n // Notification
913 property bool popup: false
14+ readonly property bool critical: n.urgency === NotificationUrgency.Critical
1015
1116 Layout.fillWidth: true
12− implicitHeight: body.implicitHeight + 20
13− color: popup ? Theme.bg : Theme.surface
14− border.width: popup ? 2 : 1
15− border.color: n.urgency === NotificationUrgency.Critical ? Theme.err : (popup ? Theme.accent : Theme.line)
17+ implicitHeight: Math.max(74, body.implicitHeight + 32)
18+ radius: 26
19+ color: popup ? Theme.s1 : Theme.s2
20+ border.width: critical || popup ? 1 : 0
21+ border.color: critical ? Theme.err : Theme.mute
1622
23+ readonly property bool photo: !!n.image && n.image !== "" // avatars, covers: fill the circle
1724 readonly property string imgSource: {
18− if (n.image && n.image !== "") return n.image
25+ if (photo) return n.image
1926 if (n.appIcon && n.appIcon !== "") return Quickshell.iconPath(n.appIcon, true)
2027 return ""
2128 }
29+ readonly property var defaultAction: {
30+ var a = n.actions
31+ for (var i = 0; i < a.length; i++) if (a[i].identifier === "default") return a[i]
32+ return null
33+ }
34+ readonly property var buttons: {
35+ var a = n.actions, out = []
36+ for (var i = 0; i < a.length; i++) if (a[i].identifier !== "default" && a[i].text !== "") out.push(a[i])
37+ return out
38+ }
2239
2340 function close() {
2441 if (popup) Notifs.dropPopup(n)
2542 else n.dismiss()
2643 }
44+ // invoke() can close the notification and destroy this card: read everything first
45+ function run(action) {
46+ var nn = n, p = popup
47+ if (action) action.invoke()
48+ if (p) Notifs.dropPopup(nn)
49+ }
2750
2851 MouseArea {
2952 anchors.fill: parent
30− onClicked: if (card.popup) Notifs.dropPopup(card.n)
53+ cursorShape: card.defaultAction ? Qt.PointingHandCursor : Qt.ArrowCursor
54+ onClicked: card.run(card.defaultAction)
3155 }
3256
33− RowLayout {
34− id: body
35− anchors.fill: parent
36− anchors.margins: 10
37− spacing: 10
38−
57+ Rectangle {
58+ id: ic
59+ x: 16
60+ y: 16
61+ width: 42
62+ height: 42
63+ radius: 21
64+ color: Theme.s3
65+ Icon { anchors.centerIn: parent; visible: !img.visible; text: Theme.iBell; font.pixelSize: 18 }
3966 Image {
40− visible: card.imgSource !== ""
67+ id: img
68+ anchors.centerIn: parent
69+ width: card.photo && Settings.appIcons === "dots" ? 42 : 26
70+ height: width
71+ visible: status === Image.Ready
4172 source: card.imgSource
42− Layout.preferredWidth: 36
43− Layout.preferredHeight: 36
44− Layout.alignment: Qt.AlignTop
45− sourceSize: Qt.size(72, 72)
46− fillMode: Image.PreserveAspectFit
73+ sourceSize: Qt.size(width * 2, height * 2)
74+ fillMode: Image.PreserveAspectCrop
4775 asynchronous: true
76+ layer.enabled: Settings.appIcons !== "colour"
77+ layer.effect: Settings.appIcons === "mono" ? monoFx : dotFx
4878 }
79+ Component { id: dotFx; DotMask { step: card.photo ? 3 : 2; circle: card.photo } }
80+ Component { id: monoFx; MultiEffect { saturation: -1 } }
81+ }
4982
50− ColumnLayout {
83+ ColumnLayout {
84+ id: body
85+ anchors.left: ic.right
86+ anchors.leftMargin: 14
87+ anchors.right: closeBtn.left
88+ anchors.rightMargin: 8
89+ y: 16
90+ spacing: 2
91+ Lb {
5192 Layout.fillWidth: true
52− spacing: 2
53−
54− RowLayout {
55− Layout.fillWidth: true
56− Text {
57− Layout.fillWidth: true
58− elide: Text.ElideRight
59− text: card.n.appName
60− color: Theme.accent
61− font.family: Theme.font
62− font.pixelSize: Theme.fontSize - 3
63− font.letterSpacing: 1
64− }
65− IconBtn {
66− icon: Theme.iClose
67− iconSize: 12
68− implicitWidth: 22
69− implicitHeight: 22
70− onClicked: card.close()
71− }
72− }
73− Text {
74− Layout.fillWidth: true
75− text: card.n.summary
76− wrapMode: Text.Wrap
77− maximumLineCount: 2
78− elide: Text.ElideRight
79− color: Theme.fg
80− font.family: Theme.font
81− font.pixelSize: Theme.fontSize
82− font.bold: true
83− }
84− Text {
85− visible: card.n.body !== ""
86− Layout.fillWidth: true
87− text: card.n.body
88− textFormat: Text.StyledText
89− wrapMode: Text.Wrap
90− maximumLineCount: card.popup ? 4 : 6
91− elide: Text.ElideRight
92− color: Theme.dim
93− font.family: Theme.font
94− font.pixelSize: Theme.fontSize - 1
95− }
96− Flow {
97− visible: card.n.actions.length > 0
98− Layout.fillWidth: true
99− spacing: 6
100− Repeater {
101− model: card.n.actions
102− delegate: IconBtn {
103− required property var modelData
104− label: modelData.text
105− implicitHeight: 24
106− // invoke() can close the notification and destroy this card: read everything first
107− onClicked: {
108− var action = modelData, n = card.n, popup = card.popup
109− action.invoke()
110− if (popup) Notifs.dropPopup(n)
111− }
112− }
93+ elide: Text.ElideRight
94+ px: 10
95+ text: (card.n.appName || "app") + (Notifs.ago(card.n) !== "" ? " · " + Notifs.ago(card.n) : "")
96+ }
97+ Text {
98+ Layout.fillWidth: true
99+ text: card.n.summary
100+ wrapMode: Text.Wrap
101+ maximumLineCount: 2
102+ elide: Text.ElideRight
103+ font.family: Theme.ui
104+ font.pixelSize: 15
105+ font.weight: Font.DemiBold
106+ color: Theme.fg
107+ }
108+ Text {
109+ visible: card.n.body !== ""
110+ Layout.fillWidth: true
111+ text: card.n.body
112+ textFormat: Text.StyledText
113+ wrapMode: Text.Wrap
114+ maximumLineCount: card.popup ? 4 : 6
115+ elide: Text.ElideRight
116+ font.family: Theme.ui
117+ font.pixelSize: 14
118+ color: Theme.fgDim
119+ linkColor: Theme.fill
120+ }
121+ Flow {
122+ visible: card.buttons.length > 0
123+ Layout.fillWidth: true
124+ Layout.topMargin: 8
125+ spacing: 6
126+ Repeater {
127+ model: card.buttons
128+ delegate: Chip {
129+ required property var modelData
130+ text: modelData.text
131+ filled: true
132+ px: 10
133+ onClicked: card.run(modelData)
113134 }
114135 }
115136 }
116137 }
138+
139+ IconBtn {
140+ id: closeBtn
141+ anchors.right: parent.right
142+ anchors.rightMargin: 12
143+ y: 14
144+ implicitHeight: 28
145+ icon: Theme.iClose
146+ iconSize: 13
147+ onClicked: card.close()
148+ }
117149 }
M .config/quickshell/aurora/NotifPage.qml View
@@ -1,19 +1,23 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4+// Control panel > notifications: the count in Doto, do-not-disturb, clear, the history.
45 ColumnLayout {
56 id: page
6− spacing: 8
7+ spacing: 12
78
89 RowLayout {
910 Layout.fillWidth: true
10− spacing: 6
11− Text {
11+ Layout.leftMargin: 6
12+ spacing: 12
13+ Num {
14+ text: Notifs.count
15+ font.pixelSize: 44
16+ }
17+ Lb {
1218 Layout.fillWidth: true
13− text: Notifs.count + (Notifs.count === 1 ? " notification" : " notifications")
14− color: Notifs.count > 0 ? Theme.accent : Theme.dim
15− font.family: Theme.font
16− font.pixelSize: Theme.fontSize
19+ Layout.alignment: Qt.AlignVCenter
20+ text: Notifs.count === 1 ? "notification" : "notifications"
1721 }
1822 IconBtn {
1923 icon: Notifs.dnd ? Theme.iBellOff : Theme.iBell
@@ -22,37 +26,36 @@ ColumnLayout {
2226 onClicked: Notifs.dnd = !Notifs.dnd
2327 }
2428 IconBtn {
29+ visible: Notifs.count > 0
2530 icon: Theme.iTrash
2631 label: "clear"
2732 onClicked: Notifs.clearAll()
2833 }
2934 }
3035
31− SectionLabel { text: "history" }
32−
33− Text {
36+ Lb {
3437 visible: Notifs.count === 0
3538 Layout.alignment: Qt.AlignHCenter
36− Layout.topMargin: 40
37− text: Theme.iBell + " all quiet"
38− color: Theme.dim
39− font.family: Theme.font
40− font.pixelSize: Theme.fontSize + 1
39+ Layout.topMargin: 30
40+ Layout.bottomMargin: 30
41+ text: "all quiet"
42+ color: Theme.mute
4143 }
4244
4345 Flickable {
46+ id: flick
4447 Layout.fillWidth: true
4548 Layout.fillHeight: true
49+ Layout.preferredHeight: list.implicitHeight
4650 clip: true
4751 contentWidth: width
4852 contentHeight: list.implicitHeight
4953 boundsBehavior: Flickable.StopAtBounds
50− id: flick
5154
5255 ColumnLayout {
5356 id: list
5457 width: flick.width
55− spacing: 6
58+ spacing: 10
5659 Repeater {
5760 model: { Notifs.count; return Notifs.history() }
5861 delegate: NotifCard {
M .config/quickshell/aurora/Notifs.qml View
@@ -14,6 +14,8 @@ Singleton {
1414 property string soundFile: Quickshell.env("HOME") + "/.config/quickshell/aurora/sounds/Blow.aiff"
1515 property real soundVolume: Settings.notifVolume
1616 property var popups: [] // [{n, until}]
17+ property var times: ({}) // notification id -> arrival (ms), for "3 MIN" labels
18+ property double now: Date.now() // ticks while there are notifications, so the labels age
1719 readonly property var model: server.trackedNotifications
1820 readonly property int count: server.trackedNotifications.values.length
1921
@@ -28,6 +30,13 @@ Singleton {
2830 for (var j = 0; j < all.length; j++) all[j].dismiss()
2931 popups = []
3032 }
33+ // "now" / "3 min" / "2 h" since it arrived; "" if it came before a restart
34+ function ago(n) {
35+ var t = times[n.id]
36+ if (!t) return ""
37+ var m = Math.floor((now - t) / 60000)
38+ return m < 1 ? "now" : m < 60 ? m + " min" : Math.floor(m / 60) + " h"
39+ }
3140 function dropPopup(n) {
3241 popups = popups.filter(function (p) { return p.n !== n })
3342 }
@@ -55,10 +64,19 @@ Singleton {
5564 imageSupported: true
5665 onNotification: n => {
5766 n.tracked = true
67+ root.times[n.id] = Date.now()
68+ root.now = Date.now()
5869 if (!root.dnd || n.urgency === NotificationUrgency.Critical) root.push(n)
5970 }
6071 }
6172
73+ Timer {
74+ interval: 30000
75+ running: root.count > 0
76+ repeat: true
77+ onTriggered: root.now = Date.now()
78+ }
79+
6280 Timer {
6381 interval: 500
6482 running: root.popups.length > 0
A .config/quickshell/aurora/Num.qml View
@@ -0,0 +1,16 @@
1+import QtQuick
2+
3+// Numbers and short words in Doto (dot matrix, round dots, black weight). Never smaller than 26px.
4+// Doto's line box is 1.2em; the negative padding trims it to ~.85em (lineHeight does nothing on a one-line Text).
5+// Extra space goes on top of it: `topPadding: lead + 8`.
6+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Num.qml.
7+Text {
8+ readonly property real lead: -0.175 * font.pixelSize
9+ font.family: Theme.dot
10+ font.pixelSize: 44
11+ font.weight: Font.Black
12+ font.variableAxes: ({ "ROND": 100, "wght": 900 })
13+ topPadding: lead
14+ bottomPadding: lead
15+ color: Theme.fg
16+}
A .config/quickshell/aurora/PlayControls.qml View
@@ -0,0 +1,44 @@
1+import QtQuick
2+
3+// previous · play/pause in the accent circle (the live thing on a media card) · next.
4+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Controls.qml.
5+Row {
6+ id: root
7+
8+ property var player: Media.player
9+ property real size: 22 // prev/next glyphs
10+ property real play: 52 // play circle
11+
12+ spacing: size
13+
14+ Icon {
15+ anchors.verticalCenter: parent.verticalCenter
16+ text: Theme.iPrev
17+ font.pixelSize: root.size
18+ color: root.player && root.player.canGoPrevious ? Theme.fg : Theme.mute
19+ HoverHandler { cursorShape: Qt.PointingHandCursor }
20+ TapHandler { onTapped: if (root.player && root.player.canGoPrevious) root.player.previous() }
21+ }
22+ Rectangle {
23+ width: root.play
24+ height: root.play
25+ radius: root.play / 2
26+ color: Theme.live
27+ Icon {
28+ anchors.centerIn: parent
29+ text: root.player && root.player.isPlaying ? Theme.iPause : Theme.iPlay
30+ color: Theme.liveFg
31+ font.pixelSize: root.play * 0.46
32+ }
33+ HoverHandler { cursorShape: Qt.PointingHandCursor }
34+ TapHandler { onTapped: if (root.player && root.player.canTogglePlaying) root.player.togglePlaying() }
35+ }
36+ Icon {
37+ anchors.verticalCenter: parent.verticalCenter
38+ text: Theme.iNext
39+ font.pixelSize: root.size
40+ color: root.player && root.player.canGoNext ? Theme.fg : Theme.mute
41+ HoverHandler { cursorShape: Qt.PointingHandCursor }
42+ TapHandler { onTapped: if (root.player && root.player.canGoNext) root.player.next() }
43+ }
44+}
A .config/quickshell/aurora/Ring.qml View
@@ -0,0 +1,34 @@
1+import QtQuick
2+
3+// A ring of n LED dots on the item's edge; the fraction f lights clockwise from 12 o'clock, in a wave (8 ms a dot).
4+// Dot centres sit on the edge, so leave dot/2 of room around it.
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Ring.qml.
6+Item {
7+ id: root
8+
9+ property int n: 44
10+ property real f: 1
11+ property real dot: 6
12+ property color on: Theme.fill
13+ property color off: Theme.s3
14+ readonly property int lit: Math.round(n * Math.max(0, Math.min(1, f)))
15+
16+ Repeater {
17+ model: root.n
18+
19+ Rectangle {
20+ required property int index
21+ readonly property real a: index / root.n * 2 * Math.PI - Math.PI / 2
22+ x: root.width / 2 * (1 + Math.cos(a)) - width / 2
23+ y: root.height / 2 * (1 + Math.sin(a)) - height / 2
24+ width: root.dot
25+ height: root.dot
26+ radius: root.dot / 2
27+ color: index < root.lit ? root.on : root.off
28+ Behavior on color {
29+ enabled: Theme.motion
30+ SequentialAnimation { PauseAnimation { duration: index * 8 } ColorAnimation { duration: 120 } }
31+ }
32+ }
33+ }
34+}
M .config/quickshell/aurora/SectionLabel.qml View
@@ -1,17 +1,34 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4+// Section heading: grey caps label, an optional count after a middle dot, an optional action on the right (CLEAR ALL).
45 RowLayout {
56 id: r
67 property string text: ""
8+ property string count: ""
9+ property string action: ""
10+ signal actionClicked()
11+
712 Layout.fillWidth: true
813 spacing: 8
9− Text {
10− text: r.text.toUpperCase()
11− color: Theme.accent
12− font.family: Theme.font
13− font.pixelSize: Theme.fontSize - 2
14− font.letterSpacing: 1.5
14+
15+ Lb {
16+ Layout.leftMargin: 6
17+ text: r.text + (r.count !== "" ? " · " + r.count : "")
18+ }
19+ Item { Layout.fillWidth: true }
20+ Lb {
21+ visible: r.action !== ""
22+ Layout.rightMargin: 6
23+ text: r.action
24+ color: am.containsMouse ? Theme.fg : Theme.fill
25+ MouseArea {
26+ id: am
27+ anchors.fill: parent
28+ anchors.margins: -6
29+ hoverEnabled: true
30+ cursorShape: Qt.PointingHandCursor
31+ onClicked: r.actionClicked()
32+ }
1533 }
16− Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Theme.line }
1734 }
A .config/quickshell/aurora/Seg.qml View
@@ -0,0 +1,31 @@
1+import QtQuick
2+
3+// Segment bar, like the Glyph strips on the phone: the fraction f is lit. vertical = fills bottom-up.
4+// Give it a width and a height; segments share the length.
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), components/Seg.qml.
6+Grid {
7+ id: root
8+
9+ property int n: 12
10+ property real f: 0
11+ property bool vertical: false
12+ property color on: Theme.fill
13+ property color off: Theme.s3
14+ readonly property int lit: Math.round(n * Math.max(0, Math.min(1, f)))
15+
16+ columns: vertical ? 1 : n
17+ spacing: 4
18+
19+ Repeater {
20+ model: root.n
21+
22+ Rectangle {
23+ required property int index
24+ readonly property int k: root.vertical ? root.n - 1 - index : index
25+ width: root.vertical ? root.width : (root.width - (root.n - 1) * root.spacing) / root.n
26+ height: root.vertical ? (root.height - (root.n - 1) * root.spacing) / root.n : root.height
27+ radius: Math.min(width, height) / 2
28+ color: k < root.lit ? root.on : root.off
29+ }
30+ }
31+}
A .config/quickshell/aurora/Segmented.qml View
@@ -0,0 +1,76 @@
1+import QtQuick
2+
3+// One-of-n choice: a pill track where the white cell slides to the selected option.
4+// options: [{value, label, ticks}] (ticks = optional strength marks, how many of 4 are lit, like glyph's power modes).
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml (power modes).
6+Rectangle {
7+ id: root
8+
9+ property var options: []
10+ property var value
11+ signal picked(var v)
12+
13+ readonly property int at: {
14+ for (var i = 0; i < options.length; i++) if (options[i].value === value) return i
15+ return -1
16+ }
17+ readonly property real cell: (width - 12) / Math.max(1, options.length)
18+
19+ implicitHeight: 54
20+ radius: height / 2
21+ color: Theme.s2
22+
23+ Rectangle {
24+ visible: root.at >= 0
25+ x: 6 + root.cell * Math.max(0, root.at)
26+ y: 6
27+ width: root.cell
28+ height: root.height - 12
29+ radius: height / 2
30+ color: Theme.fill
31+ Behavior on x {
32+ enabled: Theme.motion
33+ NumberAnimation { duration: Theme.t; easing.type: Easing.BezierSpline; easing.bezierCurve: Theme.glide }
34+ }
35+ }
36+ Repeater {
37+ model: root.options
38+ Item {
39+ id: c
40+ required property var modelData
41+ required property int index
42+ readonly property bool on: root.at === index
43+ x: 6 + root.cell * index
44+ width: root.cell
45+ height: root.height
46+ Row {
47+ anchors.centerIn: parent
48+ spacing: 9
49+ Row {
50+ visible: c.modelData.ticks !== undefined
51+ anchors.verticalCenter: parent.verticalCenter
52+ spacing: 3
53+ Repeater {
54+ model: 4
55+ Rectangle {
56+ required property int index
57+ anchors.bottom: parent.bottom
58+ width: 4
59+ height: 6 + index * 4
60+ radius: 2
61+ color: index < (c.modelData.ticks || 0) ? (c.on ? Theme.fillFg : Theme.fg) : (c.on ? Theme.fade(Theme.fillFg, 0.3) : Theme.s3)
62+ }
63+ }
64+ }
65+ Lb {
66+ anchors.verticalCenter: parent.verticalCenter
67+ text: c.modelData.label
68+ px: 11
69+ color: c.on ? Theme.fillFg : hov.hovered ? Theme.fg : Theme.grey
70+ }
71+ }
72+ HoverHandler { id: hov; cursorShape: Qt.PointingHandCursor }
73+ TapHandler { onTapped: root.picked(c.modelData.value) }
74+ }
75+ }
76+}
M .config/quickshell/aurora/Settings.qml View
@@ -11,13 +11,14 @@ Singleton {
1111
1212 // defaults live here; reset() puts them back
1313 readonly property var defaults: ({
14− fontSize: 12, wsFontSize: 11, barHeight: 22, barAlpha: 1.0, panelAlpha: 0.88, matuScheme: "scheme-content", matuContrast: 0.0,
14+ fontSize: 12, wsFontSize: 11, barHeight: 22, barAlpha: 1.0, panelAlpha: 1.0, matuScheme: "scheme-content", matuContrast: 0.0,
1515 osdTimeout: 1500, desktopClock: true, desktopViz: true, vizBars: 64,
1616 dock: true, dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"], wallFolder: "dusky-dark",
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, powerAuto: false, powerAc: "balanced", powerBat: "saver"
20+ privacyIndicators: true, powerAuto: false, powerAc: "balanced", powerBat: "saver",
21+ motion: true, clock24: true, appIcons: "dots"
2122 })
2223
2324 property alias fontSize: a.fontSize
@@ -50,6 +51,9 @@ Singleton {
5051 property alias powerAuto: a.powerAuto
5152 property alias powerAc: a.powerAc
5253 property alias powerBat: a.powerBat
54+ property alias motion: a.motion
55+ property alias clock24: a.clock24
56+ property alias appIcons: a.appIcons
5357
5458 function reset() {
5559 for (var k in defaults) a[k] = defaults[k]
@@ -78,7 +82,7 @@ Singleton {
7882 property int wsFontSize: 11
7983 property int barHeight: 22
8084 property real barAlpha: 1.0
81− property real panelAlpha: 0.88
85+ property real panelAlpha: 1.0 // control panel / popup opacity (settings > appearance)
8286 property string matuScheme: "scheme-content" // matugen -t (Wall.qml), accent = its primary_container (= wallpaper colour)
8387 property real matuContrast: 0.0 // matugen --contrast, -1..1
8488 property int osdTimeout: 1500
@@ -104,6 +108,9 @@ Singleton {
104108 property bool powerAuto: false // power profile follows AC/battery (Battery.qml), off = whatever ppd has
105109 property string powerAc: "balanced" // saver | balanced | performance
106110 property string powerBat: "saver"
111+ property bool motion: true // shell animations (Theme.t)
112+ property bool clock24: true
113+ property string appIcons: "dots" // dots | mono | colour: how app icons are drawn
107114 }
108115 }
109116 // first run: create the file once the directory exists
M .config/quickshell/aurora/SliderRow.qml View
@@ -1,30 +1,77 @@
11 import QtQuick
2−import QtQuick.Layouts
32
4−RowLayout {
3+// Level slider (control panel): a pill whose filled part is white with the icon in it, the rest a row of dots,
4+// the value in a chip on the right. Click/drag sets, the wheel steps, the icon end toggles mute (iconClicked).
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml (Slider).
6+Item {
57 id: r
68 property string icon: ""
79 property real value: 0
810 property bool muted: false
9− property string valueText: Math.round(value * 100) + "%"
11+ property string valueText: Math.round(shown * 100) + "%"
1012 signal moved(real v)
1113 signal iconClicked()
1214
13− spacing: 8
15+ readonly property real shown: area.pressed ? area.v : value
1416
15− IconBtn { icon: r.icon; onClicked: r.iconClicked() }
16− BarSlider {
17− Layout.fillWidth: true
18− value: r.value
19− muted: r.muted
20− onMoved: v => r.moved(v)
17+ implicitWidth: 300
18+ implicitHeight: 52
19+
20+ Rectangle {
21+ anchors.fill: parent
22+ radius: height / 2
23+ color: Theme.s2
24+ }
25+ // the empty part: a dot every 11px (the fill covers the rest)
26+ Row {
27+ x: 10
28+ anchors.verticalCenter: parent.verticalCenter
29+ spacing: 11 - 2.8
30+ Repeater {
31+ model: Math.max(0, Math.floor((r.width - 20) / 11) + 1)
32+ Rectangle { width: 2.8; height: 2.8; radius: 1.4; color: Theme.mute }
33+ }
34+ }
35+ Rectangle {
36+ width: Math.max(height, r.width * Math.max(0, Math.min(1, r.shown)))
37+ height: parent.height
38+ radius: height / 2
39+ color: r.muted ? Theme.grey : Theme.fill
40+ Icon {
41+ x: 17
42+ anchors.verticalCenter: parent.verticalCenter
43+ text: r.icon
44+ font.pixelSize: 20
45+ color: Theme.fillFg
46+ }
47+ }
48+ Rectangle {
49+ anchors.right: parent.right
50+ anchors.rightMargin: 10
51+ anchors.verticalCenter: parent.verticalCenter
52+ width: val.implicitWidth + 16
53+ height: val.implicitHeight + 8
54+ radius: 10
55+ color: Theme.s2
56+ Lb { id: val; anchors.centerIn: parent; text: r.valueText }
57+ }
58+
59+ MouseArea {
60+ id: area
61+ property real v: 0
62+ anchors.fill: parent
63+ preventStealing: true
64+ cursorShape: Qt.PointingHandCursor
65+ function set(x) { v = Math.max(0, Math.min(1, x / width)); r.moved(v) }
66+ onPressed: e => set(e.x)
67+ onPositionChanged: e => { if (pressed) set(e.x) }
68+ onWheel: w => r.moved(Math.max(0, Math.min(1, r.value + (w.angleDelta.y > 0 ? 0.02 : -0.02))))
2169 }
22− Text {
23− text: r.valueText
24− Layout.preferredWidth: 40
25− horizontalAlignment: Text.AlignRight
26− color: r.muted ? Theme.dim : Theme.fg
27− font.family: Theme.font
28− font.pixelSize: Theme.fontSize
70+ // the icon end: mute
71+ MouseArea {
72+ width: r.height
73+ height: r.height
74+ cursorShape: Qt.PointingHandCursor
75+ onClicked: r.iconClicked()
2976 }
3077 }
M .config/quickshell/aurora/Spark.qml View
@@ -1,32 +1,32 @@
11 import QtQuick
22
3−// Bar-graph history, same flavour as the visualizer bars in your screenshot.
3+// History as a dot matrix (cpu): one column per sample, the top lit dot white, the ones under it grey, the rest unlit.
4+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), home/Home.qml (system widget).
45 Item {
56 id: s
67 property var values: []
78 property int slots: 40
9+ readonly property real gap: 3
10+ readonly property real d: Math.max(2, (width - (slots - 1) * gap) / slots)
11+ readonly property int rows: Math.max(3, Math.floor((height + gap) / (d + gap)))
812 implicitHeight: 44
913
10− Row {
11− anchors.fill: parent
12− spacing: 2
14+ Grid {
15+ columns: s.slots
16+ spacing: s.gap
1317 Repeater {
14− model: s.slots
15− delegate: Item {
18+ model: s.slots * s.rows
19+ Rectangle {
1620 required property int index
17− readonly property int vi: s.values.length - s.slots + index
21+ readonly property int col: index % s.slots
22+ readonly property int up: s.rows - Math.floor(index / s.slots) // rows .. 1, top to bottom
23+ readonly property int vi: s.values.length - s.slots + col
1824 readonly property real v: vi >= 0 && vi < s.values.length ? s.values[vi] : 0
19− width: (s.width - (s.slots - 1) * 2) / s.slots
20− height: s.height
21− Rectangle {
22− anchors.bottom: parent.bottom
23− width: parent.width
24− height: Math.max(2, parent.height * parent.v)
25− gradient: Gradient {
26− GradientStop { position: 0; color: Theme.accent }
27− GradientStop { position: 1; color: Theme.accentDim }
28− }
29− }
25+ readonly property int h: Math.max(1, Math.round(v * s.rows))
26+ width: s.d
27+ height: s.d
28+ radius: s.d / 2
29+ color: up === h ? Theme.fill : up < h ? Theme.fade(Theme.fill, 0.45) : Theme.s2
3030 }
3131 }
3232 }
M .config/quickshell/aurora/StatBar.qml View
@@ -1,39 +1,36 @@
11 import QtQuick
22 import QtQuick.Layouts
33
4+// caps label · segment bar · value. fill = the lit segments (white; the accent when it needs attention).
45 RowLayout {
56 id: r
67 property string label: ""
78 property real frac: 0
89 property string valueText: ""
9− property color fill: Theme.accent
10+ property color fill: Theme.fill
1011 Layout.fillWidth: true
11− spacing: 10
12+ spacing: 12
1213
13− Text {
14− text: r.label
15− Layout.preferredWidth: 72
14+ Lb {
15+ Layout.preferredWidth: 78
1616 elide: Text.ElideRight
17− color: Theme.dim
18− font.family: Theme.font
19− font.pixelSize: Theme.fontSize - 1
17+ text: r.label
18+ px: 10
2019 }
21− Rectangle {
20+ Seg {
2221 Layout.fillWidth: true
22+ Layout.preferredWidth: 100
2323 Layout.preferredHeight: 6
24− color: Theme.surfaceHi
25− Rectangle {
26− width: parent.width * Math.max(0, Math.min(1, r.frac))
27− height: parent.height
28− color: r.fill
29− }
24+ n: 24
25+ f: r.frac
26+ on: r.fill
3027 }
31− Text {
32− text: r.valueText
33− Layout.preferredWidth: 96
28+ Lb {
29+ Layout.preferredWidth: 112
3430 horizontalAlignment: Text.AlignRight
31+ elide: Text.ElideLeft
32+ text: r.valueText
33+ px: 10
3534 color: Theme.fg
36− font.family: Theme.font
37− font.pixelSize: Theme.fontSize - 1
3835 }
3936 }
M .config/quickshell/aurora/SysPage.qml View
@@ -2,11 +2,14 @@ import QtQuick
22 import QtQuick.Layouts
33 import Quickshell.Services.UPower
44
5+// Control panel > sys: cpu (Doto value + dot-matrix history), memory and disks, removable drives,
6+// battery, power profile, graphics mode, pending updates, uptime.
57 Flickable {
68 id: flick
79 clip: true
810 contentWidth: width
911 contentHeight: col.implicitHeight
12+ implicitHeight: col.implicitHeight
1013 boundsBehavior: Flickable.StopAtBounds
1114
1215 // GiB -> "812G" / "1.8T"
@@ -18,42 +21,49 @@ Flickable {
1821 ColumnLayout {
1922 id: col
2023 width: flick.width
21− spacing: 8
24+ spacing: 10
2225
23− SectionLabel { text: "cpu" }
26+ SectionLabel { text: "cpu · 40 s" }
2427 RowLayout {
2528 Layout.fillWidth: true
26− spacing: 10
27− Text {
28− text: Theme.iChip + " " + Math.round(SysStats.cpu * 100) + "%"
29− Layout.preferredWidth: 80
30− color: Theme.fg
31− font.family: Theme.font
32− font.pixelSize: Theme.fontSize + 2
33− font.bold: true
29+ Layout.leftMargin: 6
30+ spacing: 18
31+ Num {
32+ Layout.preferredWidth: 96
33+ text: Math.round(SysStats.cpu * 100) + "%"
34+ font.pixelSize: 44
35+ }
36+ Spark {
37+ Layout.fillWidth: true
38+ Layout.preferredWidth: 100
39+ Layout.preferredHeight: 44
40+ values: SysStats.history
3441 }
35− Spark { Layout.fillWidth: true; values: SysStats.history }
3642 }
3743
38− SectionLabel { text: "memory & storage"; Layout.topMargin: 6 }
44+ SectionLabel { text: "memory · storage"; Layout.topMargin: 8 }
3945 StatBar {
4046 label: "memory"
4147 frac: SysStats.memTotal > 0 ? SysStats.memUsed / SysStats.memTotal : 0
4248 valueText: flick.size(SysStats.memUsed) + " / " + flick.size(SysStats.memTotal)
49+ Layout.leftMargin: 6
50+ Layout.rightMargin: 6
4351 }
4452 Repeater {
4553 model: SysStats.disks
4654 delegate: StatBar {
4755 required property var modelData
56+ Layout.leftMargin: 6
57+ Layout.rightMargin: 6
4858 label: modelData.label
4959 frac: modelData.used / modelData.size
50− fill: frac > 0.9 ? Theme.err : Theme.accent
60+ fill: frac > 0.9 ? Theme.err : Theme.fill
5161 valueText: flick.size(modelData.used) + " / " + flick.size(modelData.size)
5262 }
5363 }
5464
5565 // removable drives + phones (Devices): mount / open / unmount, power-off once the disk is idle
56− SectionLabel { text: "removable"; Layout.topMargin: 6; visible: Devices.drives.length + Devices.phones.length > 0 }
66+ SectionLabel { text: "removable"; Layout.topMargin: 8; visible: Devices.drives.length + Devices.phones.length > 0 }
5767 Repeater {
5868 model: Devices.drives.concat(Devices.phones)
5969 delegate: ListRow {
@@ -67,88 +77,68 @@ Flickable {
6777 icon: phone ? Theme.iPhone : (modelData.locked ? Theme.iLock : Theme.iUsb)
6878 title: phone ? modelData.name : Devices.title(modelData)
6979 subtitle: phone ? (mounted ? modelData.path : "not mounted")
70− : modelData.size + " " + modelData.fstype + " " + (mounted ? modelData.mount : (modelData.locked ? "locked" : "not mounted"))
80+ : modelData.size + " · " + modelData.fstype + " · " + (mounted ? modelData.mount : (modelData.locked ? "locked" : "not mounted"))
7181 current: mounted
7282 onClicked: if (mounted) Devices.open(modelData.key); else Devices.mount(modelData.key, true)
7383 IconBtn {
7484 icon: Theme.iFolderOpen
85+ implicitHeight: 32
7586 onClicked: if (dr.mounted) Devices.open(dr.modelData.key); else Devices.mount(dr.modelData.key, true)
7687 }
7788 IconBtn {
7889 icon: Theme.iEject
90+ implicitHeight: 32
7991 visible: dr.mounted || (!dr.phone && dr.modelData.luksParent !== "")
8092 onClicked: Devices.eject(dr.modelData.key)
8193 }
8294 IconBtn {
8395 icon: Theme.iPower
96+ implicitHeight: 32
8497 visible: dr.firstOfDisk && !Devices.diskBusy(dr.modelData.disk)
8598 onClicked: Devices.powerOff(dr.modelData.disk)
8699 }
87100 }
88101 }
89102
90− BatteryInfo { Layout.topMargin: 6 }
103+ BatteryInfo { Layout.topMargin: 8 }
91104
92− ColumnLayout {
93− visible: Battery.hasProfiles
105+ SectionLabel { text: "power profile"; Layout.topMargin: 8; visible: Battery.hasProfiles }
106+ Segmented {
94107 Layout.fillWidth: true
95− Layout.topMargin: 6
96− spacing: 8
97− SectionLabel { text: "power profile" }
98− RowLayout {
99− Layout.fillWidth: true
100− spacing: 6
101− IconBtn {
102− Layout.fillWidth: true
103− icon: Theme.iLeaf
104− label: "saver"
105− active: Battery.profile === "saver"
106− onClicked: Battery.setProfile("saver")
107− }
108− IconBtn {
109− Layout.fillWidth: true
110− icon: Theme.iBalance
111− label: "balanced"
112− active: Battery.profile === "balanced"
113− onClicked: Battery.setProfile("balanced")
114− }
115− IconBtn {
116− Layout.fillWidth: true
117− visible: Battery.hasPerf
118− icon: Theme.iSpeed
119− label: "perf"
120− active: Battery.profile === "performance"
121− onClicked: Battery.setProfile("performance")
122− }
123− IconBtn {
124− Layout.fillWidth: true
125− visible: Battery.hasBat
126− icon: Theme.iAuto
127− label: "auto"
128− active: Battery.profile === "auto"
129− onClicked: Battery.setProfile("auto")
130− }
108+ visible: Battery.hasProfiles
109+ options: {
110+ var o = [{ value: "saver", label: "saver", ticks: 1 }, { value: "balanced", label: "bal", ticks: 2 }]
111+ if (Battery.hasPerf) o.push({ value: "performance", label: "perf", ticks: 4 })
112+ if (Battery.hasBat) o.push({ value: "auto", label: "auto" })
113+ return o
131114 }
115+ value: Battery.profile
116+ onPicked: v => Battery.setProfile(v)
132117 }
133118
134− GpuSwitch { Layout.topMargin: 6 }
119+ GpuSwitch { Layout.topMargin: 8 }
135120
136121 // pending pacman + AUR updates (Updates)
137− SectionLabel { text: "updates"; Layout.topMargin: 6; visible: Updates.available }
122+ SectionLabel {
123+ visible: Updates.available
124+ Layout.topMargin: 8
125+ text: "updates"
126+ count: Updates.count > 0 ? String(Updates.count) : ""
127+ }
138128 RowLayout {
139129 visible: Updates.available
140130 Layout.fillWidth: true
141− spacing: 6
142− Text {
131+ Layout.leftMargin: 6
132+ spacing: 8
133+ Lb {
143134 Layout.fillWidth: true
144− text: Updates.checking ? "checking..."
135+ elide: Text.ElideRight
136+ px: 10
137+ text: Updates.checking ? "checking…"
145138 : Updates.error !== "" ? Updates.error
146− : (Updates.count === 0 ? "up to date" : Updates.repo + " repo, " + Updates.aur + " aur")
147− + (Updates.lastCheck.getTime() > 0 ? " · " + Qt.formatDateTime(Updates.lastCheck, "HH:mm") : "")
139+ : (Updates.count === 0 ? "up to date" : Updates.repo + " repo · " + Updates.aur + " aur")
140+ + (Updates.lastCheck.getTime() > 0 ? " · " + Qt.formatDateTime(Updates.lastCheck, "HH:mm") : "")
148141 color: Updates.error !== "" ? Theme.err : Theme.fg
149− font.family: Theme.font
150− font.pixelSize: Theme.fontSize
151− elide: Text.ElideRight
152142 }
153143 IconBtn { icon: Theme.iRefresh; label: "check"; onClicked: Updates.check(true) }
154144 IconBtn {
@@ -169,22 +159,20 @@ Flickable {
169159 trailing: modelData.aur ? "aur" : ""
170160 }
171161 }
172− Text {
162+ Lb {
173163 visible: Updates.available && Updates.list.length > 6
164+ Layout.leftMargin: 6
165+ px: 10
174166 text: "+ " + (Updates.list.length - 6) + " more"
175− color: Theme.dim
176− font.family: Theme.font
177− font.pixelSize: Theme.fontSize - 2
178167 }
179168
180− Text {
169+ Lb {
181170 Layout.topMargin: 10
171+ Layout.rightMargin: 6
182172 Layout.alignment: Qt.AlignRight
173+ px: 10
183174 text: "up " + Theme.fmtTime(SysStats.uptime)
184− color: Theme.dim
185− font.family: Theme.font
186− font.pixelSize: Theme.fontSize - 2
187175 }
188− Item { Layout.preferredHeight: 6 }
176+ Item { Layout.preferredHeight: 4 }
189177 }
190178 }
M .config/quickshell/aurora/Theme.qml View
@@ -3,61 +3,112 @@ import QtQuick
33 import Quickshell
44 import Quickshell.Io
55
6−// matugen colours (~/.cache/matugen/aurora.json, written by Wall.apply/recolor), hot-reloaded on every run.
6+// The design system: Nothing OS's shapes, dots and type (after glyph-rice) in Android Material You colours from matugen
7+// (~/.cache/matugen/aurora.json, written by Wall.apply/recolor, hot-reloaded), plus fonts, motion and all icon glyphs.
78 Singleton {
89 id: root
910
11+ // ---- fonts ----
12+ readonly property string dot: "Doto" // numbers and short words (Num), never under 26px
13+ readonly property string ui: "Space Grotesk" // UI text: titles DemiBold, body Regular, greeting Light
14+ readonly property string mono: "Space Mono" // caps labels (Lb)
15+ readonly property string nf: "JetBrainsMono Nerd Font" // icons only (Icon)
16+ property string font: nf // legacy: text not restyled yet (goes away in the cleanup)
17+
1018 // ---- knobs (persisted in ~/.config/aurora/settings.json, edited in the settings app) ----
11− property string font: "JetBrainsMono Nerd Font" // any Nerd Font mono
1219 readonly property int fontSize: Settings.fontSize
1320 readonly property int wsFontSize: Settings.wsFontSize // workspace icons only; everything else uses fontSize
1421 readonly property real panelAlpha: Settings.panelAlpha
1522 readonly property int barHeight: Settings.barHeight
1623 readonly property real barAlpha: Settings.barAlpha
1724
25+ // ---- motion: ease-out-quart without overshoot (glyph's "glide"); Settings.motion off = instant ----
26+ readonly property bool motion: Settings.motion
27+ readonly property int t: motion ? 350 : 0
28+ readonly property var glide: [0.25, 1, 0.5, 1, 1, 1]
29+
1830 readonly property string matFile: Quickshell.env("HOME") + "/.cache/matugen/aurora.json"
1931
20− // flat keys from ~/.config/matugen/templates/aurora.json; defaults = a dark teal scheme for a fresh install
32+ // Material 3 roles from ~/.config/matugen/templates/aurora.json ("on_x" is spelled "xFg": JsonAdapter keys must not
33+ // start with "on" + uppercase or be "error"). Defaults = a dark teal scheme for a fresh install.
2134 FileView {
2235 path: root.matFile
2336 watchChanges: true
2437 onFileChanged: reload()
2538 adapter: JsonAdapter {
2639 id: mat
27− property string bg: "#0e1514"
28− property string fg: "#dde4e2"
2940 property string primary: "#80d5cb"
3041 property string primaryFg: "#003733"
3142 property string primaryContainer: "#00504a"
43+ property string primaryContainerFg: "#9cf2e7"
3244 property string secondary: "#b1ccc7"
45+ property string secondaryFg: "#1c3532"
46+ property string secondaryContainer: "#334b48"
47+ property string secondaryContainerFg: "#cce8e3"
3348 property string tertiary: "#adcae6"
49+ property string tertiaryFg: "#153349"
50+ property string tertiaryContainer: "#2d4961"
51+ property string tertiaryContainerFg: "#cce5ff"
52+ property string surface: "#0e1514"
53+ property string surfaceLowest: "#090f0f"
3454 property string surfaceLow: "#161d1c"
35− property string surface: "#1a2120"
55+ property string surfaceContainer: "#1a2120"
3656 property string surfaceHigh: "#252b2a"
3757 property string surfaceHighest: "#2f3635"
58+ property string surfaceFg: "#dde4e2"
59+ property string surfaceVariantFg: "#bec9c6"
3860 property string outline: "#889391"
3961 property string outlineVariant: "#3f4947"
40− property string surfaceVariantFg: "#bec9c6"
41− property string err: "#ffb4ab" // no "error"/"onX" names: JsonAdapter signal / QML handler syntax
62+ property string err: "#ffb4ab"
63+ property string errFg: "#690005"
4264 }
4365 }
4466
4567 // ---- palette ------------------------------------------------------
46− readonly property color bg: mat.bg
47− readonly property color fg: mat.fg
48− readonly property color accent: mat.primary
49− readonly property color accentFg: mat.primaryFg
50− readonly property color accentDim: mat.primaryContainer
68+ // Nothing's ladder of steps, filled with Material roles, so every wallpaper re-themes everything.
69+ readonly property color bg: mat.surfaceLowest // wallpaper backdrop, fullscreen overlays
70+ readonly property color s1: mat.surfaceLow // bar, panels, cards
71+ readonly property color s2: mat.surfaceHigh // tiles, rows, "off" fills, inputs
72+ readonly property color s3: mat.surfaceHighest // lines, unlit dots, hover
73+ readonly property color mute: mat.outlineVariant // inactive
74+ readonly property color grey: mat.outline // caps labels
75+ readonly property color fg: mat.surfaceFg // text
76+ readonly property color fgDim: mat.surfaceVariantFg // secondary text (artist, notification body)
77+ // "on" states, Material You style: active tiles/pills, slider fills, selected chips/rows, lit dots and segments
78+ readonly property color fill: mat.primary
79+ readonly property color fillFg: mat.primaryFg
80+ readonly property color container: mat.primaryContainer // tonal emphasis
81+ readonly property color containerFg: mat.primaryContainerFg
82+ // live things (Nothing's one red thing): play button, recording, mic/cam/share, caret, selected dot, newest notification
83+ readonly property color live: mat.tertiary
84+ readonly property color liveFg: mat.tertiaryFg
85+ readonly property color err: mat.err // failures, critical, destructive confirms
86+ readonly property color errFg: mat.errFg
87+ readonly property color glow: fade(fill, 0.6) // glow on lit segments, the only "shadow"
88+ readonly property color scrim: fade(bg, 0.74) // dim behind fullscreen overlays
89+
90+ function fade(c, a) { return Qt.rgba(c.r, c.g, c.b, a) }
91+ function hex(c) { return c.toString().slice(1, 7).toUpperCase() }
92+ // hours for the 12/24h setting: "09" or "9"
93+ function hh(d) {
94+ var h = d.getHours()
95+ return Settings.clock24 ? (h < 10 ? "0" : "") + h : String(h % 12 || 12)
96+ }
97+
98+ // legacy names for files not restyled yet (removed in the cleanup phase)
99+ readonly property color accent: fill
100+ readonly property color accentFg: fillFg
101+ readonly property color accentDim: container
102+ readonly property color ink: fillFg
51103 readonly property color secondary: mat.secondary
52104 readonly property color tertiary: mat.tertiary
53− readonly property color surface: mat.surface
54− readonly property color surfaceHi: mat.surfaceHigh
55− readonly property color line: mat.outlineVariant
56− readonly property color dim: mat.surfaceVariantFg
57− readonly property color err: mat.err
105+ readonly property color surface: s2
106+ readonly property color surfaceHi: s3
107+ readonly property color line: mute
108+ readonly property color dim: fgDim
109+ readonly property color cursorC: fill
58110 readonly property color barBg: Qt.rgba(bg.r, bg.g, bg.b, barAlpha)
59− readonly property color cursorC: mat.primary
60− readonly property color bgA: Qt.rgba(bg.r, bg.g, bg.b, panelAlpha)
111+ readonly property color bgA: Qt.rgba(s1.r, s1.g, s1.b, panelAlpha)
61112
62113 // ---- hyprlock -----------------------------------------------------
63114 // hyprlock stays a separate process (crash safety); it sources ~/.config/aurora/hyprlock-colors.conf,
@@ -173,6 +224,16 @@ Singleton {
173224 readonly property string iShell: cp(0xF07B7)
174225 readonly property string iCalc: cp(0xF00EC)
175226 readonly property string iPalette: cp(0xF03D8)
227+ readonly property string iChevron: cp(0xF0142) // right
228+ readonly property string iChevronLeft: cp(0xF0141)
229+ readonly property string iChevronDown: cp(0xF0140)
230+ readonly property string iChevronUp: cp(0xF0143)
231+ readonly property string iNight: cp(0xF0594) // weather-night (night light)
232+ readonly property string iShot: cp(0xF0E51) // monitor-screenshot
233+ readonly property string iOcr: cp(0xF113D) // text-recognition
234+ readonly property string iBolt: cp(0xF140B) // lightning-bolt (charging)
235+ readonly property string iDots: cp(0xF01D8) // dots-horizontal
236+ readonly property string iDashboard: cp(0xF056E) // view-dashboard (overview)
176237
177238 // workspace icons: the exact Font Awesome glyphs from your waybar config.
178239 // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image
A .config/quickshell/aurora/Tile.qml View
@@ -0,0 +1,51 @@
1+import QtQuick
2+
3+// Round toggle with a caption under it (control panel). Off = dark circle, on = white circle with a black icon,
4+// live = the accent circle (recording). Right click = menu (`picked` = its options are open: caption turns white).
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml (Tile).
6+Column {
7+ id: tile
8+
9+ property string icon
10+ property string name
11+ property bool on: false
12+ property bool live: false
13+ property bool picked: false
14+ property real size: 68
15+ signal clicked()
16+ signal menu()
17+
18+ spacing: 8
19+
20+ Rectangle {
21+ anchors.horizontalCenter: parent.horizontalCenter
22+ width: tile.size
23+ height: tile.size
24+ radius: tile.size / 2
25+ color: tile.live ? Theme.live : tile.on ? Theme.fill : m.containsMouse ? Theme.s3 : Theme.s2
26+ Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
27+ Icon {
28+ anchors.centerIn: parent
29+ text: tile.icon
30+ font.pixelSize: Math.round(tile.size * 0.35)
31+ color: tile.live ? Theme.liveFg : tile.on ? Theme.fillFg : Theme.fg
32+ }
33+ MouseArea {
34+ id: m
35+ anchors.fill: parent
36+ hoverEnabled: true
37+ cursorShape: Qt.PointingHandCursor
38+ acceptedButtons: Qt.LeftButton | Qt.RightButton
39+ onClicked: mouse => { if (mouse.button === Qt.RightButton) tile.menu(); else tile.clicked() }
40+ }
41+ }
42+ Lb {
43+ anchors.horizontalCenter: parent.horizontalCenter
44+ width: Math.min(implicitWidth, Math.max(tile.width, tile.size + 16))
45+ horizontalAlignment: Text.AlignHCenter
46+ elide: Text.ElideRight
47+ text: tile.name
48+ px: 10
49+ color: tile.picked || tile.on || tile.live ? Theme.fg : Theme.grey
50+ }
51+}
A .config/quickshell/aurora/TogglePill.qml View
@@ -0,0 +1,101 @@
1+import QtQuick
2+
3+// Wide pill toggle (Wi-Fi, Bluetooth). On = white with black text, icon in a circle, name over a caps sub line.
4+// Click = toggle (`clicked`); the chevron or a right click = details (`menu`).
5+// Ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0), quick/Quick.qml (Pill).
6+Rectangle {
7+ id: pill
8+
9+ property bool on: false
10+ property string icon
11+ property string name
12+ property string sub
13+ property bool alert: false // sub line needs attention (captive portal): accent dot in front of it
14+ signal clicked()
15+ signal menu()
16+
17+ implicitHeight: 76
18+ radius: height / 2
19+ color: on ? Theme.fill : body.containsMouse ? Theme.s3 : Theme.s2
20+ Behavior on color { enabled: Theme.motion; ColorAnimation { duration: Theme.t } }
21+
22+ MouseArea {
23+ id: body
24+ anchors.fill: parent
25+ hoverEnabled: true
26+ cursorShape: Qt.PointingHandCursor
27+ acceptedButtons: Qt.LeftButton | Qt.RightButton
28+ onClicked: mouse => { if (mouse.button === Qt.RightButton) pill.menu(); else pill.clicked() }
29+ }
30+
31+ Rectangle {
32+ id: ic
33+ x: 12
34+ anchors.verticalCenter: parent.verticalCenter
35+ width: 52
36+ height: 52
37+ radius: 26
38+ color: pill.on ? Theme.fillFg : Theme.s3
39+ Icon { anchors.centerIn: parent; text: pill.icon; font.pixelSize: 22; color: pill.on ? Theme.fill : Theme.fg }
40+ }
41+ Column {
42+ anchors.left: ic.right
43+ anchors.leftMargin: 12
44+ anchors.right: chev.left
45+ anchors.verticalCenter: parent.verticalCenter
46+ Text {
47+ width: parent.width
48+ elide: Text.ElideRight
49+ text: pill.name
50+ font.family: Theme.ui
51+ font.pixelSize: 16
52+ font.weight: Font.DemiBold
53+ color: pill.on ? Theme.fillFg : Theme.fg
54+ }
55+ Row {
56+ width: parent.width
57+ spacing: 6
58+ Rectangle {
59+ visible: pill.alert
60+ anchors.verticalCenter: parent.verticalCenter
61+ width: 6
62+ height: 6
63+ radius: 3
64+ color: Theme.live
65+ }
66+ Text {
67+ width: parent.width - (pill.alert ? 12 : 0)
68+ elide: Text.ElideRight
69+ text: pill.sub
70+ font.family: Theme.mono
71+ font.pixelSize: 11
72+ font.letterSpacing: 0.9
73+ font.capitalization: Font.AllUppercase
74+ color: pill.on ? Theme.fillFg : Theme.fg
75+ opacity: 0.6
76+ }
77+ }
78+ }
79+ // the chevron has its own hit area on top: details without toggling the radio
80+ Item {
81+ id: chev
82+ anchors.right: parent.right
83+ width: 46
84+ height: parent.height
85+ Icon {
86+ anchors.centerIn: parent
87+ anchors.horizontalCenterOffset: -6
88+ text: Theme.iChevron
89+ font.pixelSize: 18
90+ color: pill.on ? Theme.fillFg : Theme.fg
91+ opacity: cm.containsMouse ? 1 : 0.4
92+ }
93+ MouseArea {
94+ id: cm
95+ anchors.fill: parent
96+ hoverEnabled: true
97+ cursorShape: Qt.PointingHandCursor
98+ onClicked: pill.menu()
99+ }
100+ }
101+}
A .config/quickshell/aurora/shaders/dots.frag View
@@ -0,0 +1,28 @@
1+#version 440
2+// Dot mask, ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0).
3+// Turns an item into a matrix of LED dots. Each dot takes the colour at the centre of its cell, so every dot has the
4+// same size and an even tone, like the Glyph lights on the back of the phone. Luminance only: the result is monochrome.
5+// Rebuild after editing: qsb --qt6 -o dots.frag.qsb dots.frag (qt6-shadertools)
6+
7+layout(location = 0) in vec2 qt_TexCoord0;
8+layout(location = 0) out vec4 fragColor;
9+
10+layout(std140, binding = 0) uniform buf {
11+ mat4 qt_Matrix;
12+ float qt_Opacity;
13+ vec2 size; // item size, px
14+ float pitch; // grid step, px. Not "step": that is a GLSL builtin, SPIRV-Cross renames it and Qt can't find the uniform
15+ float circ; // 1 = only the dots inside the inscribed circle (album covers)
16+};
17+layout(binding = 1) uniform sampler2D source;
18+
19+void main() {
20+ vec2 p = qt_TexCoord0 * size;
21+ vec2 c = (floor(p / pitch) + 0.5) * pitch;
22+ float r = pitch * 0.3; // dot radius
23+ float m = 1.0 - smoothstep(r - 0.6, r + 0.6, distance(p, c));
24+ if (circ > 0.5 && distance(c, size * 0.5) > min(size.x, size.y) * 0.5) m = 0.0;
25+ vec4 t = texture(source, c / size);
26+ float l = dot(t.rgb, vec3(0.299, 0.587, 0.114)); // monochrome: coloured covers turn grey too
27+ fragColor = vec4(vec3(l), t.a) * m * qt_Opacity;
28+}
A .config/quickshell/aurora/shaders/dots.frag.qsb View

[Binary file]

A .config/quickshell/aurora/shaders/wall.frag View
@@ -0,0 +1,59 @@
1+#version 440
2+// Wallpaper, ported from glyph-rice (github.com/Nebelung-cloud/glyph-rice, GPL-3.0).
3+// The image as a matrix of LED dots (dots 1) or as a photo (dots 0); mono 1 = in the theme's tone (lo..hi), 0 = own colours.
4+// A change runs as a wave from origin: each cell decides as a whole, srcA before the front, srcB behind it. On the crest a
5+// cell is always a dot that swells and brightens, so the photo dissolves into the matrix and comes back as the new one.
6+// The side without an image is transparent (the dot grid and orb show through), the crest still runs over it.
7+// Rebuild after editing: qsb --qt6 -o wall.frag.qsb wall.frag (qt6-shadertools)
8+
9+layout(location = 0) in vec2 qt_TexCoord0;
10+layout(location = 0) out vec4 fragColor;
11+
12+layout(std140, binding = 0) uniform buf {
13+ mat4 qt_Matrix;
14+ float qt_Opacity;
15+ vec2 size; // px
16+ vec2 origin; // px, where the wave starts
17+ vec2 cropA; // visible part of the texture in x/y (PreserveAspectCrop)
18+ vec2 cropB;
19+ vec4 lo; // dark end of the tone
20+ vec4 hi; // light end
21+ vec4 bg;
22+ float pitch; // dot step, px
23+ float dots; // 0 photo .. 1 dots
24+ float mono; // 0 colour .. 1 tone
25+ float bright; // 0 .. 1: from the background to the full image
26+ float wave; // 0 .. 1
27+ float hasA;
28+ float hasB;
29+};
30+layout(binding = 1) uniform sampler2D srcA;
31+layout(binding = 2) uniform sampler2D srcB;
32+
33+void main() {
34+ vec2 p = qt_TexCoord0 * size;
35+ vec2 c = (floor(p / pitch) + 0.5) * pitch;
36+
37+ // a front `band` wide travels from origin to the farthest corner; k 0 = still old .. 1 = already new
38+ float band = pitch * 14.0;
39+ float far = length(max(origin, size - origin)) + band;
40+ float k = clamp((wave * far - distance(c, origin)) / band, 0.0, 1.0);
41+ float crest = 1.0 - abs(2.0 * k - 1.0);
42+ bool isB = k > 0.5;
43+ float has = isB ? hasB : hasA;
44+
45+ // d 0 = own pixel, 1 = cell centre: in between the image shrinks towards the centre (smooth pixelation)
46+ float d = max(dots, crest);
47+ vec2 uv = mix(p, c, d) / size - 0.5;
48+ vec4 t = isB ? texture(srcB, uv * cropB + 0.5) : texture(srcA, uv * cropA + 0.5);
49+ vec3 rgb = t.rgb + bg.rgb * (1.0 - t.a); // transparent parts of the image go onto the background
50+ float l = dot(rgb, vec3(0.299, 0.587, 0.114));
51+ vec3 col = has > 0.5 ? mix(bg.rgb, mix(rgb, mix(lo.rgb, hi.rgb, l), mono), bright) : bg.rgb;
52+ col = mix(col, hi.rgb, 0.4 * crest);
53+
54+ // dot radius from "the whole cell" (photo) to .3 of the step (like the dot mask), swelling on the crest
55+ float r = pitch * mix(0.75, 0.3, d) * (1.0 + 0.55 * crest);
56+ float m = 1.0 - smoothstep(r - 0.6, r + 0.6, distance(p, c));
57+ float a = has > 0.5 ? 1.0 : m * crest;
58+ fragColor = vec4(mix(bg.rgb, col, m) * a, a) * qt_Opacity;
59+}
A .config/quickshell/aurora/shaders/wall.frag.qsb View

[Binary file]

A .local/share/fonts/glyph/Doto-Variable.ttf View

[Binary file]

A .local/share/fonts/glyph/OFL.txt View
@@ -0,0 +1,95 @@
1+Copyright 2024 The Doto Project Authors (https://github.com/oliverlalan/Doto)
2+Copyright 2016 The Space Mono Project Authors (https://github.com/googlefonts/spacemono)
3+Copyright 2016 Google Inc. & Colophon Foundry. Copyright 2017 Florian Karsten (https://github.com/floriankarsten/space-grotesk)
4+
5+This Font Software is licensed under the SIL Open Font License, Version 1.1.
6+This license is copied below, and is also available with a FAQ at:
7+https://openfontlicense.org
8+
9+
10+-----------------------------------------------------------
11+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
12+-----------------------------------------------------------
13+
14+PREAMBLE
15+The goals of the Open Font License (OFL) are to stimulate worldwide
16+development of collaborative font projects, to support the font creation
17+efforts of academic and linguistic communities, and to provide a free and
18+open framework in which fonts may be shared and improved in partnership
19+with others.
20+
21+The OFL allows the licensed fonts to be used, studied, modified and
22+redistributed freely as long as they are not sold by themselves. The
23+fonts, including any derivative works, can be bundled, embedded,
24+redistributed and/or sold with any software provided that any reserved
25+names are not used by derivative works. The fonts and derivatives,
26+however, cannot be released under any other type of license. The
27+requirement for fonts to remain under this license does not apply
28+to any document created using the fonts or their derivatives.
29+
30+DEFINITIONS
31+"Font Software" refers to the set of files released by the Copyright
32+Holder(s) under this license and clearly marked as such. This may
33+include source files, build scripts and documentation.
34+
35+"Reserved Font Name" refers to any names specified as such after the
36+copyright statement(s).
37+
38+"Original Version" refers to the collection of Font Software components as
39+distributed by the Copyright Holder(s).
40+
41+"Modified Version" refers to any derivative made by adding to, deleting,
42+or substituting -- in part or in whole -- any of the components of the
43+Original Version, by changing formats or by porting the Font Software to a
44+new environment.
45+
46+"Author" refers to any designer, engineer, programmer, technical
47+writer or other person who contributed to the Font Software.
48+
49+PERMISSION & CONDITIONS
50+Permission is hereby granted, free of charge, to any person obtaining
51+a copy of the Font Software, to use, study, copy, merge, embed, modify,
52+redistribute, and sell modified and unmodified copies of the Font
53+Software, subject to the following conditions:
54+
55+1) Neither the Font Software nor any of its individual components,
56+in Original or Modified Versions, may be sold by itself.
57+
58+2) Original or Modified Versions of the Font Software may be bundled,
59+redistributed and/or sold with any software, provided that each copy
60+contains the above copyright notice and this license. These can be
61+included either as stand-alone text files, human-readable headers or
62+in the appropriate machine-readable metadata fields within text or
63+binary files as long as those fields can be easily viewed by the user.
64+
65+3) No Modified Version of the Font Software may use the Reserved Font
66+Name(s) unless explicit written permission is granted by the corresponding
67+Copyright Holder. This restriction only applies to the primary font name as
68+presented to the users.
69+
70+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
71+Software shall not be used to promote, endorse or advertise any
72+Modified Version, except to acknowledge the contribution(s) of the
73+Copyright Holder(s) and the Author(s) or with their explicit written
74+permission.
75+
76+5) The Font Software, modified or unmodified, in part or in whole,
77+must be distributed entirely under this license, and must not be
78+distributed under any other license. The requirement for fonts to
79+remain under this license does not apply to any document created
80+using the Font Software.
81+
82+TERMINATION
83+This license becomes null and void if any of the above conditions are
84+not met.
85+
86+DISCLAIMER
87+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
88+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
89+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
90+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
91+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
92+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
93+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
94+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
95+OTHER DEALINGS IN THE FONT SOFTWARE.
A .local/share/fonts/glyph/SpaceGrotesk-Bold.otf View

[Binary file]

A .local/share/fonts/glyph/SpaceGrotesk-Light.otf View

[Binary file]

A .local/share/fonts/glyph/SpaceGrotesk-Medium.otf View

[Binary file]

A .local/share/fonts/glyph/SpaceGrotesk-Regular.otf View

[Binary file]

A .local/share/fonts/glyph/SpaceGrotesk-SemiBold.otf View

[Binary file]

A .local/share/fonts/glyph/SpaceMono-Bold.ttf View

[Binary file]

A .local/share/fonts/glyph/SpaceMono-Regular.ttf View

[Binary file]

M .stow-local-ignore View
@@ -6,3 +6,4 @@ packages
66 install.sh
77 LICENSE
88 CLAUDE\.md
9+glyph-rice
M README.md View
@@ -87,4 +87,13 @@ The lists live in `packages/*.txt`: one package per line, `#` for comments, and
8787 | Launcher / menus | aurora's runner (`aurora-run`, also a dmenu replacement for scripts) |
8888 | Files | lf, pcmanfm |
8989 | Music / video | mpd + ncmpcpp, mpv |
90−| Font / icons | JetBrainsMono Nerd Font, Papirus-Dark |
90+| Font / icons | Doto, Space Grotesk, Space Mono (shell UI, vendored in `.local/share/fonts/glyph`), JetBrainsMono Nerd Font, Papirus-Dark |
91+
92+## Credits
93+- aurora's look follows Nothing OS via [glyph-rice](https://github.com/Nebelung-cloud/glyph-rice) by Nebelung-cloud
94+ (GPL-3.0): the design language, the dot-matrix shaders (`.config/quickshell/aurora/shaders/`) and several components
95+ are ported from it; ported files say so in their header. Nothing, Nothing OS and Glyph are trademarks of Nothing
96+ Technology Ltd; this is a fan setup, not affiliated with Nothing.
97+- Fonts in `.local/share/fonts/glyph/`: [Doto](https://github.com/oliverlalan/Doto),
98+ [Space Grotesk](https://github.com/floriankarsten/space-grotesk) and [Space Mono](https://github.com/googlefonts/spacemono),
99+ under the SIL Open Font License (`OFL.txt` next to them).