aurorae5c3c69
aurora: settings app windows + input pages (live hyprland options)
HyprConf applies changes with `hyprctl eval` and persists them as overrides in ~/.config/aurora/hypr.lua, loaded last by hyprland.lua.
+435 −1
7 files changed
- M .config/hypr/hyprland.lua +5 −0
- M .config/quickshell/aurora/Choice.qml +1 −0
- A .config/quickshell/aurora/Field.qml +55 −0
- A .config/quickshell/aurora/HyprConf.qml +175 −0
- A .config/quickshell/aurora/SetInput.qml +78 −0
- A .config/quickshell/aurora/SetWindows.qml +114 −0
- M .config/quickshell/aurora/SettingsApp.qml +7 −1
| @@ -49,3 +49,8 @@ require("source.appearance") | |||
| 49 | 49 | require("source.input") | |
| 50 | 50 | require("source.keybinds") | |
| 51 | 51 | require("source.window_rules") | |
| 52 | + | ||
| 53 | + | -- aurora's settings app: overrides on top of everything above (~/.config/aurora/hypr.lua, generated, not in the repo). | |
| 54 | + | -- Missing or broken file = the defaults from the files above. | |
| 55 | + | local ok, overrides = pcall(dofile, HOME .. "/.config/aurora/hypr.lua") | |
| 56 | + | if ok and type(overrides) == "table" then hl.config(overrides) end | |
| @@ -26,6 +26,7 @@ RowLayout { | |||
| 26 | 26 | delegate: IconBtn { | |
| 27 | 27 | required property var modelData | |
| 28 | 28 | Layout.fillWidth: true | |
| 29 | + | Layout.preferredWidth: 1 // equal widths, whatever the label length | |
| 29 | 30 | implicitHeight: 26 | |
| 30 | 31 | label: modelData.label | |
| 31 | 32 | active: c.value === modelData.value | |
| @@ -0,0 +1,55 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // label + one-line text input; `submitted` fires on Enter or when focus leaves with a changed value (settings app) | |
| 5 | + | RowLayout { | |
| 6 | + | id: f | |
| 7 | + | property string label: "" | |
| 8 | + | property string value: "" | |
| 9 | + | property string placeholder: "" | |
| 10 | + | signal submitted(string v) | |
| 11 | + | Layout.fillWidth: true | |
| 12 | + | spacing: 8 | |
| 13 | + | Text { | |
| 14 | + | Layout.preferredWidth: 150 | |
| 15 | + | text: f.label | |
| 16 | + | color: Theme.fg | |
| 17 | + | elide: Text.ElideRight | |
| 18 | + | font.family: Theme.font | |
| 19 | + | font.pixelSize: Theme.fontSize | |
| 20 | + | } | |
| 21 | + | Rectangle { | |
| 22 | + | Layout.fillWidth: true | |
| 23 | + | implicitHeight: 28 | |
| 24 | + | color: Theme.surface | |
| 25 | + | border.width: 1 | |
| 26 | + | border.color: input.activeFocus ? Theme.accent : Theme.line | |
| 27 | + | TextInput { | |
| 28 | + | id: input | |
| 29 | + | anchors.fill: parent | |
| 30 | + | anchors.leftMargin: 8 | |
| 31 | + | anchors.rightMargin: 8 | |
| 32 | + | verticalAlignment: TextInput.AlignVCenter | |
| 33 | + | clip: true | |
| 34 | + | text: f.value | |
| 35 | + | color: Theme.fg | |
| 36 | + | selectionColor: Theme.accent | |
| 37 | + | selectedTextColor: Theme.bg | |
| 38 | + | font.family: Theme.font | |
| 39 | + | font.pixelSize: Theme.fontSize | |
| 40 | + | onAccepted: { if (text !== f.value) f.submitted(text); focus = false } | |
| 41 | + | onActiveFocusChanged: if (!activeFocus && text !== f.value) f.submitted(text) | |
| 42 | + | Keys.onEscapePressed: e => { text = f.value; focus = false; e.accepted = true } | |
| 43 | + | } | |
| 44 | + | Text { | |
| 45 | + | anchors.fill: parent | |
| 46 | + | anchors.leftMargin: 8 | |
| 47 | + | verticalAlignment: Text.AlignVCenter | |
| 48 | + | visible: input.text === "" && !input.activeFocus | |
| 49 | + | text: f.placeholder | |
| 50 | + | color: Theme.dim | |
| 51 | + | font.family: Theme.font | |
| 52 | + | font.pixelSize: Theme.fontSize | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | } | |
| @@ -0,0 +1,175 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | ||
| 6 | + | // Hyprland options edited from the settings app. Only what the user changed is stored, as overrides: | |
| 7 | + | // ~/.config/aurora/hypr.json {"general:gaps_in": 5, ...} (state, read back here) | |
| 8 | + | // ~/.config/aurora/hypr.lua return { general = { gaps_in = 5 } } (dofile'd at the end of hyprland.lua) | |
| 9 | + | // A change is applied live with `hyprctl eval 'hl.config({...})'` and survives reloads/logins through hypr.lua. | |
| 10 | + | // reset(opt) drops the override and reloads Hyprland's config, so the repo value comes back. | |
| 11 | + | Singleton { | |
| 12 | + | id: root | |
| 13 | + | readonly property string dir: Quickshell.env("HOME") + "/.config/aurora" | |
| 14 | + | ||
| 15 | + | // every option the settings pages show: path -> type (int | float | bool | str) | |
| 16 | + | readonly property var types: ({ | |
| 17 | + | "general:gaps_in": "int", "general:gaps_out": "int", "general:border_size": "int", | |
| 18 | + | "general:layout": "str", "general:resize_on_border": "bool", | |
| 19 | + | "decoration:rounding": "int", "decoration:active_opacity": "float", "decoration:inactive_opacity": "float", | |
| 20 | + | "decoration:dim_inactive": "bool", "decoration:shadow:enabled": "bool", | |
| 21 | + | "decoration:blur:enabled": "bool", "decoration:blur:size": "int", "decoration:blur:passes": "int", | |
| 22 | + | "decoration:blur:xray": "bool", | |
| 23 | + | "animations:enabled": "bool", | |
| 24 | + | "master:mfact": "float", "master:new_status": "str", "master:new_on_top": "bool", "master:orientation": "str", | |
| 25 | + | "dwindle:preserve_split": "bool", "dwindle:smart_split": "bool", | |
| 26 | + | "misc:on_focus_under_fullscreen": "int", "misc:exit_window_retains_fullscreen": "bool", | |
| 27 | + | "misc:focus_on_activate": "bool", | |
| 28 | + | "input:kb_layout": "str", "input:kb_variant": "str", "input:kb_options": "str", | |
| 29 | + | "input:repeat_rate": "int", "input:repeat_delay": "int", "input:sensitivity": "float", | |
| 30 | + | "input:accel_profile": "str", "input:follow_mouse": "int", "input:natural_scroll": "bool", | |
| 31 | + | "input:left_handed": "bool", | |
| 32 | + | "input:touchpad:natural_scroll": "bool", "input:touchpad:tap_to_click": "bool", | |
| 33 | + | "input:touchpad:disable_while_typing": "bool", "input:touchpad:scroll_factor": "float", | |
| 34 | + | "cursor:inactive_timeout": "int", "cursor:hide_on_key_press": "bool" | |
| 35 | + | }) | |
| 36 | + | ||
| 37 | + | property var values: ({}) // current value of every option in `types` (from getoption) | |
| 38 | + | property var overrides: ({}) // only the changed ones | |
| 39 | + | property string message: "" // last hyprctl error, shown by the pages | |
| 40 | + | property bool loaded: false | |
| 41 | + | ||
| 42 | + | function get(opt) { return values[opt] } | |
| 43 | + | function isSet(opt) { return overrides.hasOwnProperty(opt) } | |
| 44 | + | ||
| 45 | + | function set(opt, v) { | |
| 46 | + | if (!types.hasOwnProperty(opt)) return | |
| 47 | + | var o = Object.assign({}, overrides), c = Object.assign({}, values) | |
| 48 | + | o[opt] = v | |
| 49 | + | c[opt] = v | |
| 50 | + | overrides = o | |
| 51 | + | values = c | |
| 52 | + | pending[opt] = v | |
| 53 | + | applyTimer.restart() // coalesces slider drags into one hyprctl call | |
| 54 | + | saveTimer.restart() | |
| 55 | + | } | |
| 56 | + | function reset(opt) { | |
| 57 | + | if (!isSet(opt)) return | |
| 58 | + | var o = Object.assign({}, overrides) | |
| 59 | + | delete o[opt] | |
| 60 | + | overrides = o | |
| 61 | + | save() | |
| 62 | + | reloadProc.running = true // config-only reload re-reads the repo files + the new hypr.lua | |
| 63 | + | } | |
| 64 | + | function resetAll(prefixes) { // e.g. ["input:"]: reset every override under those sections | |
| 65 | + | var o = Object.assign({}, overrides), changed = false | |
| 66 | + | for (var k in o) | |
| 67 | + | for (var i = 0; i < prefixes.length; i++) | |
| 68 | + | if (k.indexOf(prefixes[i]) === 0) { delete o[k]; changed = true; break } | |
| 69 | + | if (!changed) return | |
| 70 | + | overrides = o | |
| 71 | + | save() | |
| 72 | + | reloadProc.running = true | |
| 73 | + | } | |
| 74 | + | function refresh() { | |
| 75 | + | readProc.command = ["sh", "-c", 'for o; do hyprctl -j getoption "$o" | tr -d "\\n"; echo; done', "sh"] | |
| 76 | + | .concat(Object.keys(types)) | |
| 77 | + | readProc.running = true | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // ---- lua generation: keys only ever come from `types`, values are typed + escaped ---- | |
| 81 | + | function luaValue(opt, v) { | |
| 82 | + | var t = types[opt] | |
| 83 | + | if (t === "bool") return v ? "true" : "false" | |
| 84 | + | if (t === "int") return String(Math.round(Number(v)) || 0) | |
| 85 | + | if (t === "float") { var n = Number(v); return String(isFinite(n) ? n : 0) } | |
| 86 | + | return '"' + String(v).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]/g, " ") + '"' | |
| 87 | + | } | |
| 88 | + | function luaTable(map) { // {"a:b:c": v} -> "{ a = { b = { c = v } } }" | |
| 89 | + | var tree = {} | |
| 90 | + | for (var opt in map) { | |
| 91 | + | if (!types.hasOwnProperty(opt)) continue | |
| 92 | + | var parts = opt.split(":"), node = tree | |
| 93 | + | for (var i = 0; i < parts.length - 1; i++) { | |
| 94 | + | if (typeof node[parts[i]] !== "object") node[parts[i]] = {} | |
| 95 | + | node = node[parts[i]] | |
| 96 | + | } | |
| 97 | + | node[parts[parts.length - 1]] = { lua: luaValue(opt, map[opt]) } | |
| 98 | + | } | |
| 99 | + | function emit(n, ind) { | |
| 100 | + | var keys = Object.keys(n).sort(), out = [] | |
| 101 | + | for (var j = 0; j < keys.length; j++) { | |
| 102 | + | var x = n[keys[j]] | |
| 103 | + | out.push(ind + " " + keys[j] + " = " + (x.hasOwnProperty("lua") ? x.lua : emit(x, ind + " "))) | |
| 104 | + | } | |
| 105 | + | return out.length ? "{\n" + out.join(",\n") + "\n" + ind + "}" : "{}" | |
| 106 | + | } | |
| 107 | + | return emit(tree, "") | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | function save() { | |
| 111 | + | jsonFile.setText(JSON.stringify(overrides, null, 2) + "\n") | |
| 112 | + | luaFile.setText("-- generated by aurora's settings app (HyprConf.qml); edit there, not here\n" | |
| 113 | + | + "return " + luaTable(overrides) + "\n") | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | property var pending: ({}) | |
| 117 | + | Timer { | |
| 118 | + | id: applyTimer | |
| 119 | + | interval: 60 | |
| 120 | + | onTriggered: { | |
| 121 | + | var code = "hl.config(" + root.luaTable(root.pending) + ")" | |
| 122 | + | root.pending = {} | |
| 123 | + | applyProc.command = ["hyprctl", "eval", code] | |
| 124 | + | applyProc.running = true | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | Timer { id: saveTimer; interval: 400; onTriggered: root.save() } | |
| 128 | + | ||
| 129 | + | Process { | |
| 130 | + | id: applyProc | |
| 131 | + | stdout: StdioCollector { | |
| 132 | + | onStreamFinished: root.message = text.trim() === "ok" || text.trim() === "" ? "" : text.trim() | |
| 133 | + | } | |
| 134 | + | } | |
| 135 | + | Process { | |
| 136 | + | id: reloadProc | |
| 137 | + | command: ["hyprctl", "reload", "config-only"] | |
| 138 | + | onExited: root.refresh() | |
| 139 | + | } | |
| 140 | + | Process { | |
| 141 | + | id: readProc | |
| 142 | + | stdout: StdioCollector { | |
| 143 | + | onStreamFinished: { | |
| 144 | + | var c = {}, lines = text.split("\n") | |
| 145 | + | for (var i = 0; i < lines.length; i++) { | |
| 146 | + | if (lines[i].trim() === "") continue | |
| 147 | + | try { | |
| 148 | + | var j = JSON.parse(lines[i]), v | |
| 149 | + | if (j.hasOwnProperty("css")) v = parseInt(String(j.css).split(" ")[0]) // gaps: "2 2 2 2" | |
| 150 | + | else if (j.hasOwnProperty("int")) v = j.int | |
| 151 | + | else if (j.hasOwnProperty("float")) v = j.float | |
| 152 | + | else if (j.hasOwnProperty("bool")) v = j.bool | |
| 153 | + | else if (j.hasOwnProperty("str")) v = j.str === "[[EMPTY]]" ? "" : j.str // unset string option | |
| 154 | + | if (v !== undefined) c[j.option] = root.types[j.option] === "bool" ? !!v : v | |
| 155 | + | } catch (e) {} | |
| 156 | + | } | |
| 157 | + | root.values = c | |
| 158 | + | root.loaded = true | |
| 159 | + | } | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | FileView { | |
| 164 | + | id: jsonFile | |
| 165 | + | path: root.dir + "/hypr.json" | |
| 166 | + | blockLoading: true | |
| 167 | + | printErrors: false // no file until the first change | |
| 168 | + | onLoaded: { | |
| 169 | + | try { root.overrides = JSON.parse(text()) || {} } catch (e) { root.overrides = {} } | |
| 170 | + | } | |
| 171 | + | } | |
| 172 | + | FileView { id: luaFile; path: root.dir + "/hypr.lua"; preload: false; printErrors: false } // write-only | |
| 173 | + | ||
| 174 | + | Component.onCompleted: refresh() | |
| 175 | + | } | |
| @@ -0,0 +1,78 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // Settings app > Input: keyboard, mouse, touchpad, cursor, through HyprConf (live + persisted). | |
| 5 | + | Flickable { | |
| 6 | + | id: flick | |
| 7 | + | clip: true | |
| 8 | + | contentWidth: width | |
| 9 | + | contentHeight: col.implicitHeight | |
| 10 | + | boundsBehavior: Flickable.StopAtBounds | |
| 11 | + | ||
| 12 | + | Component.onCompleted: HyprConf.refresh() | |
| 13 | + | function v(o, d) { var x = HyprConf.values[o]; return x === undefined ? d : x } | |
| 14 | + | ||
| 15 | + | ColumnLayout { | |
| 16 | + | id: col | |
| 17 | + | width: flick.width | |
| 18 | + | spacing: 8 | |
| 19 | + | ||
| 20 | + | Text { | |
| 21 | + | Layout.fillWidth: true | |
| 22 | + | visible: HyprConf.message !== "" | |
| 23 | + | text: HyprConf.message | |
| 24 | + | color: Theme.err | |
| 25 | + | wrapMode: Text.WordWrap | |
| 26 | + | font.family: Theme.font | |
| 27 | + | font.pixelSize: Theme.fontSize - 1 | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | SectionLabel { text: "keyboard" } | |
| 31 | + | Field { label: "layouts"; placeholder: "us,de"; value: flick.v("input:kb_layout", ""); onSubmitted: x => HyprConf.set("input:kb_layout", x) } | |
| 32 | + | Field { label: "variants"; placeholder: "(none)"; value: flick.v("input:kb_variant", ""); onSubmitted: x => HyprConf.set("input:kb_variant", x) } | |
| 33 | + | Field { label: "xkb options"; placeholder: "grp:alt_shift_toggle"; value: flick.v("input:kb_options", ""); onSubmitted: x => HyprConf.set("input:kb_options", x) } | |
| 34 | + | Knob { label: "repeat rate"; unit: "/s"; from: 10; to: 100; value: flick.v("input:repeat_rate", 25); onMoved: x => HyprConf.set("input:repeat_rate", x) } | |
| 35 | + | Knob { label: "repeat delay"; unit: "ms"; from: 150; to: 1000; value: flick.v("input:repeat_delay", 600); onMoved: x => HyprConf.set("input:repeat_delay", Math.round(x / 10) * 10) } | |
| 36 | + | ||
| 37 | + | SectionLabel { text: "mouse" } | |
| 38 | + | Knob { label: "sensitivity"; from: -1; to: 1; decimals: 2; value: flick.v("input:sensitivity", 0); onMoved: x => HyprConf.set("input:sensitivity", x) } | |
| 39 | + | Choice { | |
| 40 | + | label: "acceleration" | |
| 41 | + | options: [{ value: "", label: "default" }, { value: "flat", label: "flat" }, { value: "adaptive", label: "adaptive" }] | |
| 42 | + | value: flick.v("input:accel_profile", "") | |
| 43 | + | onPicked: x => HyprConf.set("input:accel_profile", x) | |
| 44 | + | } | |
| 45 | + | Choice { | |
| 46 | + | label: "focus follows mouse" | |
| 47 | + | options: [{ value: 0, label: "off" }, { value: 1, label: "always" }, { value: 2, label: "loose" }, { value: 3, label: "hover only" }] | |
| 48 | + | value: flick.v("input:follow_mouse", -1) | |
| 49 | + | onPicked: x => HyprConf.set("input:follow_mouse", x) | |
| 50 | + | } | |
| 51 | + | Switch { label: "natural scroll"; checked: flick.v("input:natural_scroll", false); onToggled: on => HyprConf.set("input:natural_scroll", on) } | |
| 52 | + | Switch { label: "left handed"; checked: flick.v("input:left_handed", false); onToggled: on => HyprConf.set("input:left_handed", on) } | |
| 53 | + | ||
| 54 | + | SectionLabel { text: "touchpad" } | |
| 55 | + | Switch { label: "natural scroll"; checked: flick.v("input:touchpad:natural_scroll", false); onToggled: on => HyprConf.set("input:touchpad:natural_scroll", on) } | |
| 56 | + | Switch { label: "tap to click"; checked: flick.v("input:touchpad:tap_to_click", false); onToggled: on => HyprConf.set("input:touchpad:tap_to_click", on) } | |
| 57 | + | Switch { label: "disable while typing"; checked: flick.v("input:touchpad:disable_while_typing", false); onToggled: on => HyprConf.set("input:touchpad:disable_while_typing", on) } | |
| 58 | + | Knob { label: "scroll speed"; from: 0.1; to: 3; decimals: 2; value: flick.v("input:touchpad:scroll_factor", 1); onMoved: x => HyprConf.set("input:touchpad:scroll_factor", x) } | |
| 59 | + | ||
| 60 | + | SectionLabel { text: "cursor" } | |
| 61 | + | Knob { label: "hide when idle"; unit: "s"; from: 0; to: 30; value: flick.v("cursor:inactive_timeout", 0); onMoved: x => HyprConf.set("cursor:inactive_timeout", x) } | |
| 62 | + | Switch { label: "hide while typing"; checked: flick.v("cursor:hide_on_key_press", false); onToggled: on => HyprConf.set("cursor:hide_on_key_press", on) } | |
| 63 | + | ||
| 64 | + | Item { Layout.preferredHeight: 6 } | |
| 65 | + | RowLayout { | |
| 66 | + | Layout.fillWidth: true | |
| 67 | + | Text { | |
| 68 | + | Layout.fillWidth: true | |
| 69 | + | text: "per-device settings (hl.device) stay in source/input.lua" | |
| 70 | + | elide: Text.ElideRight | |
| 71 | + | color: Theme.dim | |
| 72 | + | font.family: Theme.font | |
| 73 | + | font.pixelSize: Theme.fontSize - 2 | |
| 74 | + | } | |
| 75 | + | IconBtn { icon: Theme.iRefresh; label: "reset to config"; onClicked: HyprConf.resetAll(["input:", "cursor:"]) } | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | } | |
| @@ -0,0 +1,114 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | ||
| 4 | + | // Settings app > Windows: Hyprland look + layout + fullscreen behaviour, through HyprConf (live + persisted). | |
| 5 | + | Flickable { | |
| 6 | + | id: flick | |
| 7 | + | clip: true | |
| 8 | + | contentWidth: width | |
| 9 | + | contentHeight: col.implicitHeight | |
| 10 | + | boundsBehavior: Flickable.StopAtBounds | |
| 11 | + | ||
| 12 | + | Component.onCompleted: HyprConf.refresh() | |
| 13 | + | // current value, or d while getoption hasn't answered yet | |
| 14 | + | function v(o, d) { var x = HyprConf.values[o]; return x === undefined ? d : x } | |
| 15 | + | ||
| 16 | + | ColumnLayout { | |
| 17 | + | id: col | |
| 18 | + | width: flick.width | |
| 19 | + | spacing: 8 | |
| 20 | + | ||
| 21 | + | Text { | |
| 22 | + | Layout.fillWidth: true | |
| 23 | + | visible: HyprConf.message !== "" | |
| 24 | + | text: HyprConf.message | |
| 25 | + | color: Theme.err | |
| 26 | + | wrapMode: Text.WordWrap | |
| 27 | + | font.family: Theme.font | |
| 28 | + | font.pixelSize: Theme.fontSize - 1 | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | SectionLabel { text: "gaps & borders" } | |
| 32 | + | Knob { label: "inner gaps"; unit: "px"; from: 0; to: 30; value: flick.v("general:gaps_in", 0); onMoved: x => HyprConf.set("general:gaps_in", x) } | |
| 33 | + | Knob { label: "outer gaps"; unit: "px"; from: 0; to: 40; value: flick.v("general:gaps_out", 0); onMoved: x => HyprConf.set("general:gaps_out", x) } | |
| 34 | + | Knob { label: "border"; unit: "px"; from: 0; to: 8; value: flick.v("general:border_size", 0); onMoved: x => HyprConf.set("general:border_size", x) } | |
| 35 | + | Knob { label: "rounding"; unit: "px"; from: 0; to: 20; value: flick.v("decoration:rounding", 0); onMoved: x => HyprConf.set("decoration:rounding", x) } | |
| 36 | + | Switch { label: "resize on border"; hint: "drag window edges to resize"; checked: flick.v("general:resize_on_border", false); onToggled: on => HyprConf.set("general:resize_on_border", on) } | |
| 37 | + | ||
| 38 | + | SectionLabel { text: "opacity" } | |
| 39 | + | Knob { label: "focused"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:active_opacity", 1); onMoved: x => HyprConf.set("decoration:active_opacity", x) } | |
| 40 | + | Knob { label: "unfocused"; from: 0.3; to: 1; decimals: 2; value: flick.v("decoration:inactive_opacity", 1); onMoved: x => HyprConf.set("decoration:inactive_opacity", x) } | |
| 41 | + | Switch { label: "dim unfocused"; checked: flick.v("decoration:dim_inactive", false); onToggled: on => HyprConf.set("decoration:dim_inactive", on) } | |
| 42 | + | ||
| 43 | + | SectionLabel { text: "effects" } | |
| 44 | + | Switch { label: "blur"; hint: "behind transparent windows and layers"; checked: flick.v("decoration:blur:enabled", false); onToggled: on => HyprConf.set("decoration:blur:enabled", on) } | |
| 45 | + | Knob { label: "blur size"; from: 1; to: 15; value: flick.v("decoration:blur:size", 1); onMoved: x => HyprConf.set("decoration:blur:size", x) } | |
| 46 | + | Knob { label: "blur passes"; from: 1; to: 6; value: flick.v("decoration:blur:passes", 1); onMoved: x => HyprConf.set("decoration:blur:passes", x) } | |
| 47 | + | Switch { label: "blur x-ray"; hint: "floating windows blur the wallpaper, not what's under them"; checked: flick.v("decoration:blur:xray", false); onToggled: on => HyprConf.set("decoration:blur:xray", on) } | |
| 48 | + | Switch { label: "shadows"; checked: flick.v("decoration:shadow:enabled", false); onToggled: on => HyprConf.set("decoration:shadow:enabled", on) } | |
| 49 | + | Switch { label: "animations"; checked: flick.v("animations:enabled", false); onToggled: on => HyprConf.set("animations:enabled", on) } | |
| 50 | + | ||
| 51 | + | SectionLabel { text: "layout" } | |
| 52 | + | Choice { | |
| 53 | + | label: "tiling layout" | |
| 54 | + | options: [{ value: "master", label: "master" }, { value: "dwindle", label: "dwindle" }] | |
| 55 | + | value: flick.v("general:layout", "") | |
| 56 | + | onPicked: x => HyprConf.set("general:layout", x) | |
| 57 | + | } | |
| 58 | + | ColumnLayout { | |
| 59 | + | Layout.fillWidth: true | |
| 60 | + | visible: flick.v("general:layout", "") === "master" | |
| 61 | + | spacing: 8 | |
| 62 | + | Knob { label: "master width"; from: 0.1; to: 0.9; decimals: 2; value: flick.v("master:mfact", 0.55); onMoved: x => HyprConf.set("master:mfact", x) } | |
| 63 | + | Choice { | |
| 64 | + | label: "new window" | |
| 65 | + | options: [{ value: "master", label: "master" }, { value: "slave", label: "stack" }, { value: "inherit", label: "inherit" }] | |
| 66 | + | value: flick.v("master:new_status", "") | |
| 67 | + | onPicked: x => HyprConf.set("master:new_status", x) | |
| 68 | + | } | |
| 69 | + | Choice { | |
| 70 | + | label: "master side" | |
| 71 | + | options: [{ value: "left", label: "left" }, { value: "right", label: "right" }, { value: "top", label: "top" }, | |
| 72 | + | { value: "bottom", label: "bottom" }, { value: "center", label: "center" }] | |
| 73 | + | value: flick.v("master:orientation", "") | |
| 74 | + | onPicked: x => HyprConf.set("master:orientation", x) | |
| 75 | + | } | |
| 76 | + | Switch { label: "new windows on top of the stack"; checked: flick.v("master:new_on_top", false); onToggled: on => HyprConf.set("master:new_on_top", on) } | |
| 77 | + | } | |
| 78 | + | ColumnLayout { | |
| 79 | + | Layout.fillWidth: true | |
| 80 | + | visible: flick.v("general:layout", "") === "dwindle" | |
| 81 | + | spacing: 8 | |
| 82 | + | Switch { label: "preserve split"; hint: "keep the split direction when windows move"; checked: flick.v("dwindle:preserve_split", false); onToggled: on => HyprConf.set("dwindle:preserve_split", on) } | |
| 83 | + | Switch { label: "smart split"; hint: "split by where the cursor is"; checked: flick.v("dwindle:smart_split", false); onToggled: on => HyprConf.set("dwindle:smart_split", on) } | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | SectionLabel { text: "fullscreen & focus" } | |
| 87 | + | Choice { | |
| 88 | + | label: "focus under fullscreen" | |
| 89 | + | options: [{ value: 0, label: "ignore" }, { value: 1, label: "take over" }, { value: 2, label: "exit fullscreen" }] | |
| 90 | + | value: flick.v("misc:on_focus_under_fullscreen", -1) | |
| 91 | + | onPicked: x => HyprConf.set("misc:on_focus_under_fullscreen", x) | |
| 92 | + | } | |
| 93 | + | Switch { label: "closing keeps fullscreen"; hint: "the next window takes over when the fullscreen one closes (dwm monocle)"; checked: flick.v("misc:exit_window_retains_fullscreen", false); onToggled: on => HyprConf.set("misc:exit_window_retains_fullscreen", on) } | |
| 94 | + | Switch { label: "focus on activate"; hint: "apps asking for attention get focused"; checked: flick.v("misc:focus_on_activate", false); onToggled: on => HyprConf.set("misc:focus_on_activate", on) } | |
| 95 | + | ||
| 96 | + | Item { Layout.preferredHeight: 6 } | |
| 97 | + | RowLayout { | |
| 98 | + | Layout.fillWidth: true | |
| 99 | + | Text { | |
| 100 | + | Layout.fillWidth: true | |
| 101 | + | text: "saved to ~/.config/aurora/hypr.lua (loaded last by hyprland.lua)" | |
| 102 | + | elide: Text.ElideLeft | |
| 103 | + | color: Theme.dim | |
| 104 | + | font.family: Theme.font | |
| 105 | + | font.pixelSize: Theme.fontSize - 2 | |
| 106 | + | } | |
| 107 | + | IconBtn { | |
| 108 | + | icon: Theme.iRefresh | |
| 109 | + | label: "reset to config" | |
| 110 | + | onClicked: HyprConf.resetAll(["general:", "decoration:", "animations:", "master:", "dwindle:", "misc:"]) | |
| 111 | + | } | |
| 112 | + | } | |
| 113 | + | } | |
| 114 | + | } | |
| @@ -26,7 +26,9 @@ FloatingWindow { | |||
| 26 | 26 | { id: "bluetooth", icon: Theme.iBt, name: "bluetooth", group: "connections" }, | |
| 27 | 27 | { id: "sound", icon: Theme.iVolHigh, name: "sound", group: "connections" }, | |
| 28 | 28 | { id: "appearance", icon: Theme.iPalette, name: "appearance", group: "look" }, | |
| 29 | − | { id: "shell", icon: Theme.iDock, name: "shell", group: "look" } | |
| 29 | + | { id: "shell", icon: Theme.iDock, name: "shell", group: "look" }, | |
| 30 | + | { id: "windows", icon: Theme.iWindow, name: "windows", group: "hyprland" }, | |
| 31 | + | { id: "input", icon: Theme.iKeyboard, name: "input", group: "hyprland" } | |
| 30 | 32 | ] | |
| 31 | 33 | readonly property int index: { | |
| 32 | 34 | for (var i = 0; i < sections.length; i++) if (sections[i].id === section) return i | |
| @@ -136,6 +138,8 @@ FloatingWindow { | |||
| 136 | 138 | case "sound": return soundC | |
| 137 | 139 | case "appearance": return appearanceC | |
| 138 | 140 | case "shell": return shellC | |
| 141 | + | case "windows": return windowsC | |
| 142 | + | case "input": return inputC | |
| 139 | 143 | default: return netC | |
| 140 | 144 | } | |
| 141 | 145 | } | |
| @@ -149,4 +153,6 @@ FloatingWindow { | |||
| 149 | 153 | Component { id: soundC; AudioPage {} } | |
| 150 | 154 | Component { id: appearanceC; SetAppearance { onOverlayRequested: name => win.overlayRequested(name) } } | |
| 151 | 155 | Component { id: shellC; SetShell {} } | |
| 156 | + | Component { id: windowsC; SetWindows {} } | |
| 157 | + | Component { id: inputC; SetInput {} } | |
| 152 | 158 | } | |