diff --git a/dotfiles/quickshell/LeftFlyout.qml b/dotfiles/quickshell/LeftFlyout.qml index 37b3f15..7728368 100644 --- a/dotfiles/quickshell/LeftFlyout.qml +++ b/dotfiles/quickshell/LeftFlyout.qml @@ -1100,6 +1100,16 @@ Rectangle { } var url = "https://danbooru.donmai.us/autocomplete.json?search%5Bquery%5D=" + encodeURIComponent(partial) + "&search%5Btype%5D=tag_query&limit=8"; + // If a previous keystroke's request is still in flight (curl + // hasn't returned yet), reassigning .command and setting + // .running = true on an already-running Process is a no-op + // — running:true -> true is not a change, so nothing + // restarts and the newer query silently never gets sent. + // That's the "sometimes works, sometimes not" flakiness: + // explicitly stop it first so the new request actually + // fires. + if (autocompleteProc.running) autocompleteProc.running = false; + autocompleteProc.requestedPartial = partial; autocompleteProc.command = ["curl", "-s", "-A", booruCol.userAgent, url]; autocompleteProc.running = true; } @@ -1211,8 +1221,15 @@ Rectangle { } Process { id: autocompleteProc + property string requestedPartial: "" stdout: StdioCollector { onStreamFinished: { + // Drop stale responses: even with the running-guard + // above, a slow request can still resolve after a + // faster, newer one — without this check whichever + // curl happens to finish last wins, regardless of + // which one actually matches what's typed now. + if (autocompleteProc.requestedPartial !== booruCol.currentPartialTag()) return; try { var d = JSON.parse(text); booruCol.suggestions = Array.isArray(d) ? d : []; @@ -1354,7 +1371,20 @@ Rectangle { anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor - onClicked: { booruSettings.lewds = booruCol.lewds ? "0" : "1"; booruCol.doSearch(); } + onClicked: { + // save() here, not just the property assignment: + // this was the actual persistence bug — toggling + // Lewds never wrote to disk on its own, only the + // source-switcher below called save(). So Lewds + // only ever "stuck" as a side effect of also + // switching source afterward, and turning it + // back off never stuck at all — reopening Booru + // always reloaded whatever was last written that + // way instead of the toggle's actual state. + booruSettings.lewds = booruCol.lewds ? "0" : "1"; + booruSettings.save(); + booruCol.doSearch(); + } } } } @@ -1468,7 +1498,16 @@ Rectangle { } // -- Pagination -- - Row { + // Flow, not Row: Row lays out children at their natural + // width regardless of the width assigned to the Row + // itself, so when Prev + "Page" + the page field + "of + // N" + Next added up to more than booruCol.width (which + // happened often enough — "of N" varies in width with + // page count, and it was already close to the edge) the + // Next button rendered past the flyout's right edge, + // off the visible screen. Flow wraps overflow onto a + // second line instead of spilling past the edge. + Flow { visible: !booruCol.searching && (booruCol.page > 1 || booruCol.hasNextPage) width: booruCol.width spacing: 8 diff --git a/dotfiles/quickshell/RightBar.qml b/dotfiles/quickshell/RightBar.qml index 58008cc..03400fb 100644 --- a/dotfiles/quickshell/RightBar.qml +++ b/dotfiles/quickshell/RightBar.qml @@ -76,7 +76,19 @@ PanelWindow { delegate: IconImage { id: trayIcon implicitSize: 18 - source: Quickshell.iconPath(modelData.icon, "") + // Only resolve through the icon theme when this is + // actually a themed icon name (e.g. Steam's + // "steam_tray_mono"). Apps that only ever supply a raw + // IconPixmap over the SNI protocol and no IconName at + // all (confirmed via busctl: Vesktop's tray item has no + // IconName property) get an already-usable image:// URL + // back from modelData.icon instead — running that + // through iconPath(), a name/path resolver, mangled it + // into nothing, which is what rendered as a broken- + // texture checkerboard instead of the actual icon. + source: modelData.icon.includes("://") + ? modelData.icon + : Quickshell.iconPath(modelData.icon, "") MouseArea { anchors.fill: parent hoverEnabled: true diff --git a/hyprland/hyprland.conf b/hyprland/hyprland.conf index 90fa09d..7a1a105 100644 --- a/hyprland/hyprland.conf +++ b/hyprland/hyprland.conf @@ -29,6 +29,10 @@ source = ~/.config/hypr/display.conf # TTY) since only one agent can be registered at a time. exec-once = mako exec-once = hyprpaper +# Reapplies whatever wallpaper was last set — hyprpaper/mpvpaper start every +# session with nothing loaded, and wallpaper-set never persisted anything +# on its own (see modules/scripts/wallpaper-set.sh, wallpaper-restore.sh). +exec-once = wallpaper-restore exec-once = quickshell # GTK/Qt apps native-Wayland where possible, X11 fallback otherwise diff --git a/modules/desktop.nix b/modules/desktop.nix index 107911d..d4c3cf3 100644 --- a/modules/desktop.nix +++ b/modules/desktop.nix @@ -11,7 +11,13 @@ services.greetd = { enable = true; settings.default_session = { - command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --remember-session --cmd Hyprland"; + # start-hyprland, not the raw Hyprland binary: it's Hyprland's own + # watchdog-wrapped launcher (ships alongside Hyprland in the same + # package) that also auto-restarts the compositor if it hangs. + # Launching the raw binary directly works but logs a startup warning + # ("Hyprland is being launched without start-hyprland. This is highly + # advised against.") and skips that recovery. + command = "${pkgs.tuigreet}/bin/tuigreet --time --remember --remember-session --cmd start-hyprland"; user = "greeter"; }; }; diff --git a/modules/scripts.nix b/modules/scripts.nix index 1a02d8c..fa6d770 100644 --- a/modules/scripts.nix +++ b/modules/scripts.nix @@ -8,6 +8,7 @@ let showDesktopToggle = pkgs.writeShellScriptBin "show-desktop-toggle" (builtins.readFile ./scripts/show-desktop-toggle.sh); wallpaperSet = pkgs.writeShellScriptBin "wallpaper-set" (builtins.readFile ./scripts/wallpaper-set.sh); wallpaperDelete = pkgs.writeShellScriptBin "wallpaper-delete" (builtins.readFile ./scripts/wallpaper-delete.sh); + wallpaperRestore = pkgs.writeShellScriptBin "wallpaper-restore" (builtins.readFile ./scripts/wallpaper-restore.sh); displaySet = pkgs.writeShellScriptBin "display-set" (builtins.readFile ./scripts/display-set.sh); hyprThemeSet = pkgs.writeShellScriptBin "hypr-theme-set" (builtins.readFile ./scripts/hypr-theme-set.sh); gtkThemeSet = pkgs.writeShellScriptBin "gtk-theme-set" (builtins.readFile ./scripts/gtk-theme-set.sh); @@ -31,6 +32,7 @@ in showDesktopToggle wallpaperSet wallpaperDelete + wallpaperRestore displaySet hyprThemeSet gtkThemeSet diff --git a/modules/scripts/wallpaper-restore.sh b/modules/scripts/wallpaper-restore.sh new file mode 100644 index 0000000..e7fbbbb --- /dev/null +++ b/modules/scripts/wallpaper-restore.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Reapplies the last-set wallpaper on login/reboot. hyprpaper and mpvpaper +# both start every session with nothing loaded, and wallpaper-set only ever +# ran through live IPC with no persistence of its own — without this the +# desktop silently reverted to blank on every fresh login. Run via +# exec-once in hyprland.conf, after hyprpaper. +set -euo pipefail + +state_file="$HOME/.config/hypr/wallpaper.conf" +[ -f "$state_file" ] || exit 0 + +# hyprpaper's IPC socket isn't guaranteed ready the instant exec-once fires +# it, since exec-once entries all start back-to-back with no ordering +# guarantee beyond process spawn order. +for _ in $(seq 1 50); do + hyprctl hyprpaper listactive > /dev/null 2>&1 && break + sleep 0.1 +done + +bash "$state_file" diff --git a/modules/scripts/wallpaper-set.sh b/modules/scripts/wallpaper-set.sh index 6ff7e3a..b5e6a4d 100755 --- a/modules/scripts/wallpaper-set.sh +++ b/modules/scripts/wallpaper-set.sh @@ -58,3 +58,12 @@ case "${image,,}" in hyprctl hyprpaper wallpaper ",$image,$fit_mode" > /dev/null ;; esac + +# Persist so the wallpaper survives a reboot/relogin. hyprpaper/mpvpaper +# both start every session with nothing loaded — this was previously only +# ever set at runtime via IPC, so the desktop silently reverted to blank +# every fresh login. wallpaper-restore (exec-once in hyprland.conf, after +# hyprpaper starts) re-runs the exact command saved here. +state_file="$HOME/.config/hypr/wallpaper.conf" +mkdir -p "$(dirname "$state_file")" +printf 'wallpaper-set %q %q %q %q %q\n' "$image" "$fit_mode" "$muted" "$speed" "$auto_pause" > "$state_file"