aurora53e4da3
aurora: add settings tab persisted to ~/.config/aurora/settings.json
Theme knobs, accent, desktop widgets, visualizer bars and OSD timeout are now live-editable. Also adds a `tab <name>` IPC alias since the qs CLI swallows `show`.
+229 −16
9 files changed
- M .config/quickshell/aurora/Cava.qml +2 −2
- M .config/quickshell/aurora/ControlPanel.qml +3 −1
- M .config/quickshell/aurora/Desktop.qml +2 −2
- M .config/quickshell/aurora/Osd.qml +1 −1
- A .config/quickshell/aurora/Settings.qml +72 −0
- A .config/quickshell/aurora/SettingsPage.qml +134 −0
- M .config/quickshell/aurora/Theme.qml +10 −8
- M .config/quickshell/aurora/Wall.qml +2 −1
- M .config/quickshell/aurora/shell.qml +3 −1
| @@ -8,8 +8,8 @@ import Quickshell.Services.Mpris | |||
| 8 | 8 | // playing and something wants it (`wanted`), so it costs nothing when idle. Hidden if cava is missing. | |
| 9 | 9 | Singleton { | |
| 10 | 10 | id: root | |
| 11 | − | property int bars: 64 | |
| 12 | − | property bool wanted: true // desktop visualizer enabled | |
| 11 | + | readonly property int bars: Math.max(8, Settings.vizBars) | |
| 12 | + | readonly property bool wanted: Settings.desktopViz | |
| 13 | 13 | property bool available: false | |
| 14 | 14 | property var values: [] // 0..1 per bar | |
| 15 | 15 | ||
| @@ -36,7 +36,8 @@ PanelWindow { | |||
| 36 | 36 | { icon: Theme.iWifi, name: "net" }, | |
| 37 | 37 | { icon: Theme.iBt, name: "bt" }, | |
| 38 | 38 | { icon: Theme.iChip, name: "system" }, | |
| 39 | − | { icon: Theme.iBell, name: "notifs" } | |
| 39 | + | { icon: Theme.iBell, name: "notifs" }, | |
| 40 | + | { icon: Theme.iCog, name: "settings" } | |
| 40 | 41 | ] | |
| 41 | 42 | ||
| 42 | 43 | Rectangle { | |
| @@ -113,6 +114,7 @@ PanelWindow { | |||
| 113 | 114 | BtPage { active: win.opened && win.tab === 3 } | |
| 114 | 115 | SysPage {} | |
| 115 | 116 | NotifPage {} | |
| 117 | + | SettingsPage {} | |
| 116 | 118 | } | |
| 117 | 119 | } | |
| 118 | 120 | } | |
| @@ -9,8 +9,8 @@ PanelWindow { | |||
| 9 | 9 | id: win | |
| 10 | 10 | required property var modelData | |
| 11 | 11 | screen: modelData | |
| 12 | − | property bool showClock: true | |
| 13 | − | property bool showViz: true | |
| 12 | + | readonly property bool showClock: Settings.desktopClock | |
| 13 | + | readonly property bool showViz: Settings.desktopViz | |
| 14 | 14 | ||
| 15 | 15 | anchors { top: true; bottom: true; left: true; right: true } | |
| 16 | 16 | exclusionMode: ExclusionMode.Ignore | |
| @@ -36,7 +36,7 @@ PanelWindow { | |||
| 36 | 36 | WlrLayershell.namespace: "aurora-osd" | |
| 37 | 37 | WlrLayershell.keyboardFocus: WlrKeyboardFocus.None | |
| 38 | 38 | ||
| 39 | − | Timer { id: hide; interval: 1500; onTriggered: win.shown = false } | |
| 39 | + | Timer { id: hide; interval: Settings.osdTimeout; onTriggered: win.shown = false } | |
| 40 | 40 | ||
| 41 | 41 | // ---- pipewire watchers ---- | |
| 42 | 42 | // ignore the burst of change signals on startup and when the default device switches | |
| @@ -0,0 +1,72 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | ||
| 6 | + | // User knobs persisted to ~/.config/aurora/settings.json (outside the shell dir, so syncing the | |
| 7 | + | // config never clobbers it). Written on every change, re-read when edited by hand. | |
| 8 | + | Singleton { | |
| 9 | + | id: root | |
| 10 | + | readonly property string file: Quickshell.env("HOME") + "/.config/aurora/settings.json" | |
| 11 | + | ||
| 12 | + | // defaults live here; reset() puts them back | |
| 13 | + | readonly property var defaults: ({ | |
| 14 | + | fontSize: 12, wsFontSize: 11, barHeight: 22, barAlpha: 1.0, panelAlpha: 0.88, accentIndex: 14, | |
| 15 | + | osdTimeout: 1500, desktopClock: true, desktopViz: true, vizBars: 64, | |
| 16 | + | dock: true, dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"], wallFolder: "dusky-dark" | |
| 17 | + | }) | |
| 18 | + | ||
| 19 | + | property alias fontSize: a.fontSize | |
| 20 | + | property alias wsFontSize: a.wsFontSize | |
| 21 | + | property alias barHeight: a.barHeight | |
| 22 | + | property alias barAlpha: a.barAlpha | |
| 23 | + | property alias panelAlpha: a.panelAlpha | |
| 24 | + | property alias accentIndex: a.accentIndex | |
| 25 | + | property alias osdTimeout: a.osdTimeout | |
| 26 | + | property alias desktopClock: a.desktopClock | |
| 27 | + | property alias desktopViz: a.desktopViz | |
| 28 | + | property alias vizBars: a.vizBars | |
| 29 | + | property alias dock: a.dock | |
| 30 | + | property alias dockPinned: a.dockPinned | |
| 31 | + | property alias wallFolder: a.wallFolder | |
| 32 | + | ||
| 33 | + | function reset() { | |
| 34 | + | for (var k in defaults) a[k] = defaults[k] | |
| 35 | + | } | |
| 36 | + | function togglePin(id) { | |
| 37 | + | var l = dockPinned.slice(), i = l.indexOf(id) | |
| 38 | + | if (i >= 0) l.splice(i, 1); else l.push(id) | |
| 39 | + | dockPinned = l | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | FileView { | |
| 43 | + | id: fv | |
| 44 | + | path: root.file | |
| 45 | + | watchChanges: true | |
| 46 | + | onFileChanged: reload() | |
| 47 | + | onAdapterUpdated: writeAdapter() | |
| 48 | + | onLoadFailed: err => { | |
| 49 | + | if (err === FileViewError.FileNotFound) | |
| 50 | + | Quickshell.execDetached(["sh", "-c", 'mkdir -p "${1%/*}"', "sh", root.file]) | |
| 51 | + | writeLater.start() | |
| 52 | + | } | |
| 53 | + | adapter: JsonAdapter { | |
| 54 | + | id: a | |
| 55 | + | property int fontSize: 12 | |
| 56 | + | property int wsFontSize: 11 | |
| 57 | + | property int barHeight: 22 | |
| 58 | + | property real barAlpha: 1.0 | |
| 59 | + | property real panelAlpha: 0.88 | |
| 60 | + | property int accentIndex: 14 | |
| 61 | + | property int osdTimeout: 1500 | |
| 62 | + | property bool desktopClock: true | |
| 63 | + | property bool desktopViz: true | |
| 64 | + | property int vizBars: 64 | |
| 65 | + | property bool dock: true | |
| 66 | + | property list<string> dockPinned: ["foot", "librewolf", "pcmanfm", "obsidian"] | |
| 67 | + | property string wallFolder: "dusky-dark" | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | // first run: create the file once the directory exists | |
| 71 | + | Timer { id: writeLater; interval: 300; onTriggered: fv.writeAdapter() } | |
| 72 | + | } | |
| @@ -0,0 +1,134 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // Theme / shell knobs. Everything writes straight to Settings (persisted, applied live). | |
| 5 | + | ColumnLayout { | |
| 6 | + | id: page | |
| 7 | + | spacing: 10 | |
| 8 | + | ||
| 9 | + | // label + slider + value, for an integer or real setting | |
| 10 | + | component Knob: RowLayout { | |
| 11 | + | id: k | |
| 12 | + | property string label: "" | |
| 13 | + | property real value: 0 | |
| 14 | + | property real from: 0 | |
| 15 | + | property real to: 1 | |
| 16 | + | property int decimals: 0 | |
| 17 | + | signal moved(real v) | |
| 18 | + | Layout.fillWidth: true | |
| 19 | + | spacing: 8 | |
| 20 | + | Text { | |
| 21 | + | Layout.preferredWidth: 96 | |
| 22 | + | text: k.label | |
| 23 | + | color: Theme.fg | |
| 24 | + | font.family: Theme.font | |
| 25 | + | font.pixelSize: Theme.fontSize | |
| 26 | + | } | |
| 27 | + | BarSlider { | |
| 28 | + | Layout.fillWidth: true | |
| 29 | + | from: k.from | |
| 30 | + | to: k.to | |
| 31 | + | step: 1 / Math.max(1, (k.to - k.from) * (k.decimals ? 100 : 1)) | |
| 32 | + | value: k.value | |
| 33 | + | onMoved: v => k.moved(k.decimals ? Math.round(v * 100) / 100 : Math.round(v)) | |
| 34 | + | } | |
| 35 | + | Text { | |
| 36 | + | Layout.preferredWidth: 40 | |
| 37 | + | horizontalAlignment: Text.AlignRight | |
| 38 | + | text: k.decimals ? k.value.toFixed(2) : String(k.value) | |
| 39 | + | color: Theme.fg | |
| 40 | + | font.family: Theme.font | |
| 41 | + | font.pixelSize: Theme.fontSize | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | SectionLabel { text: "appearance" } | |
| 46 | + | ||
| 47 | + | Knob { label: "font size"; from: 9; to: 18; value: Settings.fontSize; onMoved: v => Settings.fontSize = v } | |
| 48 | + | Knob { label: "ws icons"; from: 8; to: 18; value: Settings.wsFontSize; onMoved: v => Settings.wsFontSize = v } | |
| 49 | + | Knob { label: "bar height"; from: 18; to: 36; value: Settings.barHeight; onMoved: v => Settings.barHeight = v } | |
| 50 | + | Knob { label: "bar alpha"; from: 0.3; to: 1; decimals: 2; value: Settings.barAlpha; onMoved: v => Settings.barAlpha = v } | |
| 51 | + | Knob { label: "panel alpha"; from: 0.3; to: 1; decimals: 2; value: Settings.panelAlpha; onMoved: v => Settings.panelAlpha = v } | |
| 52 | + | ||
| 53 | + | SectionLabel { text: "accent" } | |
| 54 | + | ||
| 55 | + | GridLayout { | |
| 56 | + | Layout.fillWidth: true | |
| 57 | + | columns: 9 | |
| 58 | + | columnSpacing: 4 | |
| 59 | + | rowSpacing: 4 | |
| 60 | + | Repeater { | |
| 61 | + | model: 17 // "auto" + color0..color15 | |
| 62 | + | delegate: Rectangle { | |
| 63 | + | id: sw | |
| 64 | + | required property int index | |
| 65 | + | readonly property int ci: index - 1 // -1 = auto | |
| 66 | + | readonly property bool current: Settings.accentIndex === ci | |
| 67 | + | Layout.fillWidth: true | |
| 68 | + | Layout.preferredHeight: 26 | |
| 69 | + | color: ci < 0 ? Theme.surface : Theme.pal[ci] | |
| 70 | + | border.width: current ? 2 : 1 | |
| 71 | + | border.color: current ? Theme.fg : Theme.line | |
| 72 | + | Text { | |
| 73 | + | anchors.centerIn: parent | |
| 74 | + | visible: sw.ci < 0 || sw.current | |
| 75 | + | text: sw.ci < 0 ? "auto" : Theme.iCheck | |
| 76 | + | color: sw.ci < 0 ? Theme.fg : Theme.bg | |
| 77 | + | font.family: Theme.font | |
| 78 | + | font.pixelSize: Theme.fontSize - 2 | |
| 79 | + | font.bold: true | |
| 80 | + | } | |
| 81 | + | MouseArea { | |
| 82 | + | anchors.fill: parent | |
| 83 | + | cursorShape: Qt.PointingHandCursor | |
| 84 | + | onClicked: Settings.accentIndex = sw.ci | |
| 85 | + | } | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | SectionLabel { text: "desktop" } | |
| 91 | + | ||
| 92 | + | RowLayout { | |
| 93 | + | Layout.fillWidth: true | |
| 94 | + | spacing: 6 | |
| 95 | + | IconBtn { | |
| 96 | + | Layout.fillWidth: true | |
| 97 | + | icon: Theme.iClock | |
| 98 | + | label: "clock" | |
| 99 | + | active: Settings.desktopClock | |
| 100 | + | onClicked: Settings.desktopClock = !Settings.desktopClock | |
| 101 | + | } | |
| 102 | + | IconBtn { | |
| 103 | + | Layout.fillWidth: true | |
| 104 | + | icon: Theme.iMusic | |
| 105 | + | label: "visualizer" | |
| 106 | + | active: Settings.desktopViz | |
| 107 | + | onClicked: Settings.desktopViz = !Settings.desktopViz | |
| 108 | + | } | |
| 109 | + | IconBtn { | |
| 110 | + | Layout.fillWidth: true | |
| 111 | + | icon: Theme.iDock | |
| 112 | + | label: "dock" | |
| 113 | + | active: Settings.dock | |
| 114 | + | onClicked: Settings.dock = !Settings.dock | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | Knob { label: "viz bars"; from: 16; to: 128; value: Settings.vizBars; onMoved: v => Settings.vizBars = v } | |
| 118 | + | Knob { label: "osd ms"; from: 500; to: 4000; value: Settings.osdTimeout; onMoved: v => Settings.osdTimeout = Math.round(v / 100) * 100 } | |
| 119 | + | ||
| 120 | + | Item { Layout.fillHeight: true } | |
| 121 | + | ||
| 122 | + | RowLayout { | |
| 123 | + | Layout.fillWidth: true | |
| 124 | + | Text { | |
| 125 | + | Layout.fillWidth: true | |
| 126 | + | text: "~/.config/aurora/settings.json" | |
| 127 | + | elide: Text.ElideLeft | |
| 128 | + | color: Theme.dim | |
| 129 | + | font.family: Theme.font | |
| 130 | + | font.pixelSize: Theme.fontSize - 2 | |
| 131 | + | } | |
| 132 | + | IconBtn { icon: Theme.iRefresh; label: "reset"; onClicked: Settings.reset() } | |
| 133 | + | } | |
| 134 | + | } | |
| @@ -7,16 +7,16 @@ import Quickshell.Io | |||
| 7 | 7 | Singleton { | |
| 8 | 8 | id: root | |
| 9 | 9 | ||
| 10 | − | // ---- knobs -------------------------------------------------------- | |
| 10 | + | // ---- knobs (persisted in ~/.config/aurora/settings.json, edited in the Settings tab) ---- | |
| 11 | 11 | property string font: "JetBrainsMono Nerd Font" // any Nerd Font mono | |
| 12 | − | property int fontSize: 12 | |
| 13 | − | property int wsFontSize: 11 // workspace icons only; everything else uses fontSize | |
| 14 | − | property real panelAlpha: 0.88 | |
| 12 | + | readonly property int fontSize: Settings.fontSize | |
| 13 | + | readonly property int wsFontSize: Settings.wsFontSize // workspace icons only; everything else uses fontSize | |
| 14 | + | readonly property real panelAlpha: Settings.panelAlpha | |
| 15 | 15 | // your waybar uses @color14 for underlines/active workspace, so that is the default. | |
| 16 | − | // set -1 to auto-pick the most vivid of color1..color6 instead | |
| 17 | − | property int accentIndex: 14 | |
| 18 | − | property int barHeight: 22 | |
| 19 | − | property real barAlpha: 1.0 | |
| 16 | + | // -1 auto-picks the most vivid of color1..color6 instead | |
| 17 | + | readonly property int accentIndex: Settings.accentIndex | |
| 18 | + | readonly property int barHeight: Settings.barHeight | |
| 19 | + | readonly property real barAlpha: Settings.barAlpha | |
| 20 | 20 | ||
| 21 | 21 | readonly property string walFile: Quickshell.env("HOME") + "/.cache/wal/colors.json" | |
| 22 | 22 | ||
| @@ -156,6 +156,8 @@ Singleton { | |||
| 156 | 156 | readonly property string iPicker: cp(0xF020A) | |
| 157 | 157 | readonly property string iRec: cp(0xF044A) | |
| 158 | 158 | readonly property string iStop: cp(0xF04DB) | |
| 159 | + | readonly property string iDock: cp(0xF10A9) | |
| 160 | + | readonly property string iCog: cp(0xF0493) | |
| 159 | 161 | ||
| 160 | 162 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 161 | 163 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |
| @@ -10,7 +10,7 @@ Singleton { | |||
| 10 | 10 | ||
| 11 | 11 | readonly property string home: Quickshell.env("HOME") | |
| 12 | 12 | property string rootDir: home + "/pix/wallpapers" | |
| 13 | − | property string folder: "dusky-dark" // same default as setwall's WALL_DIR | |
| 13 | + | property string folder: Settings.wallFolder // default dusky-dark, same as setwall's WALL_DIR | |
| 14 | 14 | readonly property string setwall: home + "/.local/bin/setwall" | |
| 15 | 15 | readonly property string thumbDir: home + "/.cache/aurora/thumbs" | |
| 16 | 16 | ||
| @@ -29,6 +29,7 @@ Singleton { | |||
| 29 | 29 | } | |
| 30 | 30 | function listFolder(name) { | |
| 31 | 31 | folder = name | |
| 32 | + | Settings.wallFolder = name | |
| 32 | 33 | thumbProc.running = false | |
| 33 | 34 | listProc.running = false | |
| 34 | 35 | loading = true | |
| @@ -18,7 +18,7 @@ ShellRoot { | |||
| 18 | 18 | // centered overlay windows (wallpaper picker, clipboard, ...): one at a time, on the focused monitor | |
| 19 | 19 | property string overlay: "" | |
| 20 | 20 | property var overlayScreen: focusedScreen() | |
| 21 | − | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif"] | |
| 21 | + | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif", "settings"] | |
| 22 | 22 | ||
| 23 | 23 | // screen of the monitor that currently has focus (falls back to the first screen) | |
| 24 | 24 | function focusedScreen() { | |
| @@ -137,6 +137,8 @@ ShellRoot { | |||
| 137 | 137 | function open(): void { root.openTab(root.tabNames[root.tab]) } | |
| 138 | 138 | function close(): void { root.close() } | |
| 139 | 139 | function show(name: string): void { root.openTab(name) } | |
| 140 | + | // `qs ipc call shell show x` is swallowed by the qs CLI (it has its own `show`), so use this | |
| 141 | + | function tab(name: string): void { root.openTab(name) } | |
| 140 | 142 | function dnd(): void { Notifs.dnd = !Notifs.dnd } | |
| 141 | 143 | function shot(mode: string): void { root.close(); Shot.take(mode) } // region | window | output | ocr | |
| 142 | 144 | function shotsave(): void { Shot.save = !Shot.save } | |