aurorae1c1567
aurora: add wallpaper picker (thumbnail grid, applies via setwall)
+409 −1
6 files changed
- M .config/quickshell/aurora/ControlPanel.qml +5 −1
- M .config/quickshell/aurora/HomePage.qml +20 −0
- M .config/quickshell/aurora/Theme.qml +2 −0
- A .config/quickshell/aurora/Wall.qml +127 −0
- A .config/quickshell/aurora/WallPicker.qml +237 −0
- M .config/quickshell/aurora/shell.qml +18 −0
| @@ -10,6 +10,7 @@ PanelWindow { | |||
| 10 | 10 | property int tab: 0 | |
| 11 | 11 | signal tabRequested(int index) | |
| 12 | 12 | signal closeRequested() | |
| 13 | + | signal overlayRequested(string name) | |
| 13 | 14 | ||
| 14 | 15 | visible: opened | |
| 15 | 16 | anchors { top: true; right: true } | |
| @@ -103,7 +104,10 @@ PanelWindow { | |||
| 103 | 104 | Layout.fillHeight: true | |
| 104 | 105 | currentIndex: win.tab | |
| 105 | 106 | ||
| 106 | − | HomePage { onCloseRequested: win.closeRequested() } | |
| 107 | + | HomePage { | |
| 108 | + | onCloseRequested: win.closeRequested() | |
| 109 | + | onOverlayRequested: name => win.overlayRequested(name) | |
| 110 | + | } | |
| 107 | 111 | AudioPage {} | |
| 108 | 112 | NetPage { active: win.opened && win.tab === 2 } | |
| 109 | 113 | BtPage { active: win.opened && win.tab === 3 } | |
| @@ -8,6 +8,7 @@ ColumnLayout { | |||
| 8 | 8 | id: page | |
| 9 | 9 | spacing: 10 | |
| 10 | 10 | signal closeRequested() | |
| 11 | + | signal overlayRequested(string name) | |
| 11 | 12 | ||
| 12 | 13 | property string armed: "" | |
| 13 | 14 | Timer { id: disarm; interval: 3000; onTriggered: page.armed = "" } | |
| @@ -95,6 +96,25 @@ ColumnLayout { | |||
| 95 | 96 | } | |
| 96 | 97 | } | |
| 97 | 98 | ||
| 99 | + | SectionLabel { text: "desktop" } | |
| 100 | + | ||
| 101 | + | RowLayout { | |
| 102 | + | Layout.fillWidth: true | |
| 103 | + | spacing: 6 | |
| 104 | + | IconBtn { | |
| 105 | + | Layout.fillWidth: true | |
| 106 | + | icon: Theme.iImage | |
| 107 | + | label: "wallpaper" | |
| 108 | + | onClicked: page.overlayRequested("wall") | |
| 109 | + | } | |
| 110 | + | IconBtn { | |
| 111 | + | Layout.fillWidth: true | |
| 112 | + | icon: Theme.iShuffle | |
| 113 | + | label: "random" | |
| 114 | + | onClicked: Wall.random() | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | ||
| 98 | 118 | SectionLabel { text: "capture" } | |
| 99 | 119 | ||
| 100 | 120 | RowLayout { | |
| @@ -149,6 +149,8 @@ Singleton { | |||
| 149 | 149 | readonly property string iEthernet: cp(0xF0200) | |
| 150 | 150 | readonly property string iPlug: cp(0xF06A5) | |
| 151 | 151 | readonly property string iBatCharge: cp(0xF0084) | |
| 152 | + | readonly property string iShuffle: cp(0xF049D) | |
| 153 | + | readonly property string iImage: cp(0xF02E9) | |
| 152 | 154 | ||
| 153 | 155 | // workspace icons: the exact Font Awesome glyphs from your waybar config. | |
| 154 | 156 | // 1 browser, 2 code, 3 book, 4 cog, 5 chat, 6 file, 7 folder, 8 terminal, 9 flask, 10 image | |
| @@ -0,0 +1,127 @@ | |||
| 1 | + | pragma Singleton | |
| 2 | + | import QtQuick | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Io | |
| 5 | + | ||
| 6 | + | // Wallpapers: folder listing, cached thumbnails, apply through ~/.local/bin/setwall | |
| 7 | + | // (setwall writes hyprpaper.conf, switches hyprpaper live and runs `wal -i`; Theme then recolours itself). | |
| 8 | + | Singleton { | |
| 9 | + | id: root | |
| 10 | + | ||
| 11 | + | readonly property string home: Quickshell.env("HOME") | |
| 12 | + | property string rootDir: home + "/pix/wallpapers" | |
| 13 | + | property string folder: "dusky-dark" // same default as setwall's WALL_DIR | |
| 14 | + | readonly property string setwall: home + "/.local/bin/setwall" | |
| 15 | + | readonly property string thumbDir: home + "/.cache/aurora/thumbs" | |
| 16 | + | ||
| 17 | + | property var folders: [] // subfolder names; "" = everything under rootDir | |
| 18 | + | property var files: [] // absolute paths of the current folder | |
| 19 | + | property var ready: ({}) // md5(path) -> true once its thumbnail exists | |
| 20 | + | property string current: "" // wallpaper hyprpaper is showing | |
| 21 | + | property bool loading: false | |
| 22 | + | ||
| 23 | + | function dirOf(name) { return name === "" ? rootDir : rootDir + "/" + name } | |
| 24 | + | function thumbOf(path) { return thumbDir + "/" + Qt.md5(path) + ".jpg" } | |
| 25 | + | ||
| 26 | + | function refresh() { | |
| 27 | + | dirsProc.running = true | |
| 28 | + | listFolder(folder) | |
| 29 | + | } | |
| 30 | + | function listFolder(name) { | |
| 31 | + | folder = name | |
| 32 | + | thumbProc.running = false | |
| 33 | + | listProc.running = false | |
| 34 | + | loading = true | |
| 35 | + | listProc.command = ["find", dirOf(name), "-type", "f", "-not", "-path", "*/.*", | |
| 36 | + | "-regextype", "posix-extended", "-iregex", ".*\\.(png|jpe?g|webp)"] | |
| 37 | + | listProc.running = true | |
| 38 | + | } | |
| 39 | + | function apply(path) { | |
| 40 | + | if (!path) return | |
| 41 | + | current = path | |
| 42 | + | Quickshell.execDetached([setwall, "-p", path]) | |
| 43 | + | } | |
| 44 | + | // random from the folder shown last; before the first listing let setwall pick (its own WALL_DIR) | |
| 45 | + | function random() { | |
| 46 | + | if (files.length) apply(files[Math.floor(Math.random() * files.length)]) | |
| 47 | + | else Quickshell.execDetached([setwall]) | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | Process { | |
| 51 | + | id: dirsProc | |
| 52 | + | command: ["find", root.rootDir, "-mindepth", "1", "-maxdepth", "1", "-type", "d", "-not", "-name", ".*"] | |
| 53 | + | stdout: StdioCollector { | |
| 54 | + | onStreamFinished: { | |
| 55 | + | var out = [], l = text.split("\n") | |
| 56 | + | for (var i = 0; i < l.length; i++) { | |
| 57 | + | var s = l[i].trim() | |
| 58 | + | if (s !== "") out.push(s.substring(s.lastIndexOf("/") + 1)) | |
| 59 | + | } | |
| 60 | + | out.sort() | |
| 61 | + | root.folders = out | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | Process { | |
| 67 | + | id: listProc | |
| 68 | + | stdout: StdioCollector { | |
| 69 | + | onStreamFinished: { | |
| 70 | + | var out = [], l = text.split("\n") | |
| 71 | + | for (var i = 0; i < l.length; i++) if (l[i] !== "") out.push(l[i]) | |
| 72 | + | out.sort() | |
| 73 | + | root.files = out | |
| 74 | + | root.loading = false | |
| 75 | + | root.makeThumbs() | |
| 76 | + | } | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // ---- thumbnails ---- | |
| 81 | + | // one background job per folder: 4 parallel `magick` runs, prints each hash when its thumb exists | |
| 82 | + | readonly property string thumbJob: | |
| 83 | + | 'f="$1"; h=$(printf %s "$f" | md5sum | cut -c1-32); t="$0/$h.jpg"; ' + | |
| 84 | + | 'if [ ! -f "$t" ]; then ' + | |
| 85 | + | ' magick "$f[0]" -thumbnail 320x180^ -gravity center -extent 320x180 -quality 82 "$t.part.jpg" 2>/dev/null ' + | |
| 86 | + | ' && mv "$t.part.jpg" "$t"; ' + | |
| 87 | + | 'fi; [ -f "$t" ] && echo "$h"' | |
| 88 | + | function makeThumbs() { | |
| 89 | + | if (!files.length) return | |
| 90 | + | thumbProc.command = ["sh", "-c", | |
| 91 | + | 'j="$1"; d="$2"; shift 2; mkdir -p "$d"; printf "%s\\0" "$@" | xargs -0 -n1 -P4 sh -c "$j" "$d"', | |
| 92 | + | "sh", thumbJob, thumbDir].concat(files) | |
| 93 | + | thumbProc.running = true | |
| 94 | + | } | |
| 95 | + | property var pendingReady: ({}) | |
| 96 | + | Timer { | |
| 97 | + | id: flush | |
| 98 | + | interval: 250 | |
| 99 | + | onTriggered: { | |
| 100 | + | var r = root.ready | |
| 101 | + | for (var k in root.pendingReady) r[k] = true | |
| 102 | + | root.pendingReady = {} | |
| 103 | + | root.ready = r | |
| 104 | + | root.readyChanged() | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | Process { | |
| 108 | + | id: thumbProc | |
| 109 | + | stdout: SplitParser { | |
| 110 | + | onRead: line => { | |
| 111 | + | root.pendingReady[line.trim()] = true | |
| 112 | + | if (!flush.running) flush.start() | |
| 113 | + | } | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // current wallpaper, straight from the config setwall writes | |
| 118 | + | FileView { | |
| 119 | + | path: root.home + "/.config/hypr/hyprpaper.conf" | |
| 120 | + | watchChanges: true | |
| 121 | + | onFileChanged: reload() | |
| 122 | + | onLoaded: { | |
| 123 | + | var m = text().match(/^\s*path\s*=\s*(.+)$/m) | |
| 124 | + | if (m) root.current = m[1].trim() | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | } | |
| @@ -0,0 +1,237 @@ | |||
| 1 | + | import QtQuick | |
| 2 | + | import QtQuick.Layouts | |
| 3 | + | import Quickshell | |
| 4 | + | import Quickshell.Wayland | |
| 5 | + | import Quickshell.Hyprland | |
| 6 | + | ||
| 7 | + | // Wallpaper picker: folder chips, filter, thumbnail grid. Enter / double-click applies (stays open | |
| 8 | + | // so you can try a few), Esc or click outside closes. Tab / Shift+Tab cycles folders. | |
| 9 | + | PanelWindow { | |
| 10 | + | id: win | |
| 11 | + | property bool opened: false | |
| 12 | + | signal closeRequested() | |
| 13 | + | ||
| 14 | + | visible: opened | |
| 15 | + | implicitWidth: screen ? Math.round(screen.width * 0.7) : 1200 | |
| 16 | + | implicitHeight: screen ? Math.round(screen.height * 0.7) : 700 | |
| 17 | + | exclusiveZone: 0 | |
| 18 | + | color: "transparent" | |
| 19 | + | WlrLayershell.layer: WlrLayer.Overlay | |
| 20 | + | WlrLayershell.namespace: "aurora-wallpaper" | |
| 21 | + | WlrLayershell.keyboardFocus: opened ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None | |
| 22 | + | ||
| 23 | + | HyprlandFocusGrab { | |
| 24 | + | windows: [win] | |
| 25 | + | active: win.opened | |
| 26 | + | onCleared: win.closeRequested() | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | onOpenedChanged: { | |
| 30 | + | if (!opened) return | |
| 31 | + | filter.text = "" | |
| 32 | + | Wall.refresh() | |
| 33 | + | filter.forceActiveFocus() | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | readonly property var shown: { | |
| 37 | + | var q = filter.text.toLowerCase(), out = [], l = Wall.files | |
| 38 | + | for (var i = 0; i < l.length; i++) | |
| 39 | + | if (q === "" || l[i].substring(l[i].lastIndexOf("/") + 1).toLowerCase().indexOf(q) >= 0) out.push(l[i]) | |
| 40 | + | return out | |
| 41 | + | } | |
| 42 | + | readonly property var chips: [""].concat(Wall.folders) | |
| 43 | + | ||
| 44 | + | function cycleFolder(d) { | |
| 45 | + | var i = chips.indexOf(Wall.folder) | |
| 46 | + | Wall.listFolder(chips[(i + d + chips.length) % chips.length]) | |
| 47 | + | } | |
| 48 | + | function name(p) { return p.substring(p.lastIndexOf("/") + 1) } | |
| 49 | + | ||
| 50 | + | Rectangle { | |
| 51 | + | anchors.fill: parent | |
| 52 | + | color: Theme.bgA | |
| 53 | + | border.width: 2 | |
| 54 | + | border.color: Theme.accent | |
| 55 | + | ||
| 56 | + | ColumnLayout { | |
| 57 | + | anchors.fill: parent | |
| 58 | + | anchors.margins: 12 | |
| 59 | + | spacing: 10 | |
| 60 | + | ||
| 61 | + | // folder chips | |
| 62 | + | Flow { | |
| 63 | + | Layout.fillWidth: true | |
| 64 | + | spacing: 4 | |
| 65 | + | Repeater { | |
| 66 | + | model: win.chips | |
| 67 | + | delegate: Rectangle { | |
| 68 | + | id: chip | |
| 69 | + | required property var modelData | |
| 70 | + | readonly property bool current: Wall.folder === modelData | |
| 71 | + | width: cl.implicitWidth + 18 | |
| 72 | + | height: 24 | |
| 73 | + | color: current ? Theme.accent : (cm.containsMouse ? Theme.surfaceHi : "transparent") | |
| 74 | + | border.width: 1 | |
| 75 | + | border.color: current ? Theme.accent : Theme.line | |
| 76 | + | Text { | |
| 77 | + | id: cl | |
| 78 | + | anchors.centerIn: parent | |
| 79 | + | text: chip.modelData === "" ? "all" : chip.modelData | |
| 80 | + | color: chip.current ? Theme.bg : Theme.fg | |
| 81 | + | font.family: Theme.font | |
| 82 | + | font.pixelSize: Theme.fontSize - 1 | |
| 83 | + | font.bold: chip.current | |
| 84 | + | } | |
| 85 | + | MouseArea { | |
| 86 | + | id: cm | |
| 87 | + | anchors.fill: parent | |
| 88 | + | hoverEnabled: true | |
| 89 | + | cursorShape: Qt.PointingHandCursor | |
| 90 | + | onClicked: Wall.listFolder(chip.modelData) | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | // filter + actions | |
| 97 | + | RowLayout { | |
| 98 | + | Layout.fillWidth: true | |
| 99 | + | spacing: 6 | |
| 100 | + | Rectangle { | |
| 101 | + | Layout.fillWidth: true | |
| 102 | + | implicitHeight: 30 | |
| 103 | + | color: Theme.surface | |
| 104 | + | border.width: 1 | |
| 105 | + | border.color: filter.activeFocus ? Theme.accent : Theme.line | |
| 106 | + | Text { | |
| 107 | + | anchors.left: parent.left | |
| 108 | + | anchors.leftMargin: 10 | |
| 109 | + | anchors.verticalCenter: parent.verticalCenter | |
| 110 | + | text: Theme.iSearch | |
| 111 | + | color: Theme.dim | |
| 112 | + | font.family: Theme.font | |
| 113 | + | font.pixelSize: Theme.fontSize | |
| 114 | + | } | |
| 115 | + | TextInput { | |
| 116 | + | id: filter | |
| 117 | + | anchors.fill: parent | |
| 118 | + | anchors.leftMargin: 32 | |
| 119 | + | anchors.rightMargin: 10 | |
| 120 | + | verticalAlignment: TextInput.AlignVCenter | |
| 121 | + | color: Theme.fg | |
| 122 | + | selectionColor: Theme.accent | |
| 123 | + | selectedTextColor: Theme.bg | |
| 124 | + | font.family: Theme.font | |
| 125 | + | font.pixelSize: Theme.fontSize | |
| 126 | + | clip: true | |
| 127 | + | onTextChanged: grid.currentIndex = 0 | |
| 128 | + | Keys.onEscapePressed: win.closeRequested() | |
| 129 | + | Keys.onReturnPressed: if (grid.currentIndex >= 0) Wall.apply(win.shown[grid.currentIndex]) | |
| 130 | + | Keys.onEnterPressed: if (grid.currentIndex >= 0) Wall.apply(win.shown[grid.currentIndex]) | |
| 131 | + | Keys.onLeftPressed: event => { if (text === "") grid.moveCurrentIndexLeft(); else event.accepted = false } | |
| 132 | + | Keys.onRightPressed: event => { if (text === "") grid.moveCurrentIndexRight(); else event.accepted = false } | |
| 133 | + | Keys.onUpPressed: grid.moveCurrentIndexUp() | |
| 134 | + | Keys.onDownPressed: grid.moveCurrentIndexDown() | |
| 135 | + | Keys.onTabPressed: win.cycleFolder(1) | |
| 136 | + | Keys.onBacktabPressed: win.cycleFolder(-1) | |
| 137 | + | Keys.onPressed: event => { | |
| 138 | + | if (event.key === Qt.Key_PageDown) { grid.currentIndex = Math.min(win.shown.length - 1, grid.currentIndex + grid.perPage); event.accepted = true } | |
| 139 | + | else if (event.key === Qt.Key_PageUp) { grid.currentIndex = Math.max(0, grid.currentIndex - grid.perPage); event.accepted = true } | |
| 140 | + | } | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | Text { | |
| 144 | + | text: Wall.loading ? "..." : win.shown.length + " / " + Wall.files.length | |
| 145 | + | color: Theme.dim | |
| 146 | + | font.family: Theme.font | |
| 147 | + | font.pixelSize: Theme.fontSize - 1 | |
| 148 | + | } | |
| 149 | + | IconBtn { icon: Theme.iShuffle; label: "random"; onClicked: Wall.random() } | |
| 150 | + | IconBtn { icon: Theme.iClose; onClicked: win.closeRequested() } | |
| 151 | + | } | |
| 152 | + | ||
| 153 | + | GridView { | |
| 154 | + | id: grid | |
| 155 | + | Layout.fillWidth: true | |
| 156 | + | Layout.fillHeight: true | |
| 157 | + | clip: true | |
| 158 | + | readonly property int cols: Math.max(1, Math.floor(width / 240)) | |
| 159 | + | readonly property int perPage: cols * Math.max(1, Math.floor(height / cellHeight)) | |
| 160 | + | cellWidth: Math.floor(width / cols) | |
| 161 | + | cellHeight: Math.round(cellWidth * 9 / 16) + 22 | |
| 162 | + | model: win.shown | |
| 163 | + | currentIndex: 0 | |
| 164 | + | boundsBehavior: Flickable.StopAtBounds | |
| 165 | + | highlightMoveDuration: 0 | |
| 166 | + | cacheBuffer: cellHeight * 2 | |
| 167 | + | ||
| 168 | + | delegate: Item { | |
| 169 | + | id: cell | |
| 170 | + | required property var modelData | |
| 171 | + | required property int index | |
| 172 | + | readonly property string hash: Qt.md5(modelData) | |
| 173 | + | readonly property bool isCurrent: Wall.current === modelData | |
| 174 | + | readonly property bool selected: grid.currentIndex === index | |
| 175 | + | width: grid.cellWidth | |
| 176 | + | height: grid.cellHeight | |
| 177 | + | ||
| 178 | + | Rectangle { | |
| 179 | + | anchors.fill: parent | |
| 180 | + | anchors.margins: 4 | |
| 181 | + | color: Theme.surface | |
| 182 | + | border.width: cell.selected || cell.isCurrent ? 2 : 1 | |
| 183 | + | border.color: cell.selected ? Theme.fg : (cell.isCurrent ? Theme.accent : Theme.line) | |
| 184 | + | ||
| 185 | + | Image { | |
| 186 | + | id: img | |
| 187 | + | anchors.fill: parent | |
| 188 | + | anchors.margins: 2 | |
| 189 | + | anchors.bottomMargin: 20 | |
| 190 | + | fillMode: Image.PreserveAspectCrop | |
| 191 | + | asynchronous: true | |
| 192 | + | cache: false | |
| 193 | + | sourceSize.width: 320 | |
| 194 | + | sourceSize.height: 180 | |
| 195 | + | source: Wall.ready[cell.hash] ? "file://" + Wall.thumbDir + "/" + cell.hash + ".jpg" : "" | |
| 196 | + | } | |
| 197 | + | Text { | |
| 198 | + | anchors.centerIn: img | |
| 199 | + | visible: img.status !== Image.Ready | |
| 200 | + | text: Theme.iImage | |
| 201 | + | color: Theme.dim | |
| 202 | + | font.family: Theme.font | |
| 203 | + | font.pixelSize: 22 | |
| 204 | + | } | |
| 205 | + | Text { | |
| 206 | + | anchors.left: parent.left | |
| 207 | + | anchors.right: parent.right | |
| 208 | + | anchors.bottom: parent.bottom | |
| 209 | + | anchors.margins: 4 | |
| 210 | + | height: 14 | |
| 211 | + | text: (cell.isCurrent ? Theme.iCheck + " " : "") + win.name(cell.modelData) | |
| 212 | + | elide: Text.ElideMiddle | |
| 213 | + | color: cell.isCurrent ? Theme.accent : Theme.dim | |
| 214 | + | font.family: Theme.font | |
| 215 | + | font.pixelSize: Theme.fontSize - 2 | |
| 216 | + | } | |
| 217 | + | } | |
| 218 | + | MouseArea { | |
| 219 | + | anchors.fill: parent | |
| 220 | + | cursorShape: Qt.PointingHandCursor | |
| 221 | + | onClicked: grid.currentIndex = cell.index | |
| 222 | + | onDoubleClicked: Wall.apply(cell.modelData) | |
| 223 | + | } | |
| 224 | + | } | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | Text { | |
| 228 | + | Layout.fillWidth: true | |
| 229 | + | text: "enter apply tab folder arrows move esc close " + Theme.iImage + " " + win.name(Wall.current) | |
| 230 | + | elide: Text.ElideRight | |
| 231 | + | color: Theme.dim | |
| 232 | + | font.family: Theme.font | |
| 233 | + | font.pixelSize: Theme.fontSize - 2 | |
| 234 | + | } | |
| 235 | + | } | |
| 236 | + | } | |
| 237 | + | } | |
| @@ -15,6 +15,9 @@ ShellRoot { | |||
| 15 | 15 | property string lastScreen: "" | |
| 16 | 16 | property var panelScreen: focusedScreen() | |
| 17 | 17 | readonly property var focusedScr: focusedScreen() | |
| 18 | + | // centered overlay windows (wallpaper picker, clipboard, ...): one at a time, on the focused monitor | |
| 19 | + | property string overlay: "" | |
| 20 | + | property var overlayScreen: focusedScreen() | |
| 18 | 21 | readonly property var tabNames: ["home", "audio", "net", "bt", "sys", "notif"] | |
| 19 | 22 | ||
| 20 | 23 | // screen of the monitor that currently has focus (falls back to the first screen) | |
| @@ -31,6 +34,12 @@ ShellRoot { | |||
| 31 | 34 | lastTab = tabNames[tab] | |
| 32 | 35 | lastScreen = panelScreen ? panelScreen.name : "" | |
| 33 | 36 | } | |
| 37 | + | function toggleOverlay(name) { | |
| 38 | + | if (overlay === name) { overlay = ""; return } | |
| 39 | + | close() | |
| 40 | + | overlayScreen = focusedScreen() | |
| 41 | + | overlay = name | |
| 42 | + | } | |
| 34 | 43 | function openTab(name) { | |
| 35 | 44 | var i = tabNames.indexOf(name) | |
| 36 | 45 | if (i >= 0) tab = i | |
| @@ -64,6 +73,13 @@ ShellRoot { | |||
| 64 | 73 | tab: root.tab | |
| 65 | 74 | onTabRequested: i => root.tab = i | |
| 66 | 75 | onCloseRequested: root.close() | |
| 76 | + | onOverlayRequested: name => { root.close(); root.toggleOverlay(name) } | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | WallPicker { | |
| 80 | + | screen: root.overlayScreen | |
| 81 | + | opened: root.overlay === "wall" | |
| 82 | + | onCloseRequested: root.overlay = "" | |
| 67 | 83 | } | |
| 68 | 84 | ||
| 69 | 85 | NotifPopups { screen: root.focusedScr; suppressed: root.opened } | |
| @@ -88,6 +104,8 @@ ShellRoot { | |||
| 88 | 104 | function dnd(): void { Notifs.dnd = !Notifs.dnd } | |
| 89 | 105 | function shot(mode: string): void { root.close(); Shot.take(mode) } // region | window | output | ocr | |
| 90 | 106 | function shotsave(): void { Shot.save = !Shot.save } | |
| 107 | + | function wall(): void { root.toggleOverlay("wall") } | |
| 108 | + | function wallrandom(): void { Wall.random() } | |
| 91 | 109 | function bright(dir: string): void { Bright.step(dir === "down" ? -1 : 1) } // up | down | |
| 92 | 110 | } | |
| 93 | 111 | } | |