From 5c6dad3e91d94e1825f985d2690cb36eed345021 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 22 Oct 2026 07:23:24 +0100 Subject: [PATCH] Feature: Run system updater in Ghostty with floating window rule Open the update terminal in Ghostty, offering to install it via pkexec when missing and falling back to foot if declined. Add the system-update-terminal window rule the script already referred to, so the window floats centred at 1000x650 instead of tiling. Co-Authored-By: Claude Opus 5.5 --- dotfiles/.config/hypr/layout/windowrules.lua | 6 ++ .../quickshell/scripts/update-system.sh | 69 +++++++++++++++---- 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/dotfiles/.config/hypr/layout/windowrules.lua b/dotfiles/.config/hypr/layout/windowrules.lua index 791ea00..bc8d8e3 100644 --- a/dotfiles/.config/hypr/layout/windowrules.lua +++ b/dotfiles/.config/hypr/layout/windowrules.lua @@ -100,6 +100,12 @@ hl.window_rule({ name = "windowrule-47", tile = true, match = { class = "^([Cc]h hl.window_rule({ name = "windowrule-48", size = "monitor_w*0.25 monitor_h*0.55", float = true, match = { class = "^(com.network.manager)$" } }) hl.window_rule({ name = "windowrule-49", workspace = "special:nyx", match = { class = "^([Vv]esktop|[Dd]iscord|[Ss]potify)$" } }) hl.window_rule({ name = "windowrule-255", size = "monitor_w*0.75 monitor_h*0.80", float = true, match = { class = "^([Bb]itwarden)$" } }) +-- System updater terminal (quickshell/scripts/update-system.sh) — float + center it instead of +-- tiling into the current workspace. Matches the title the script launches it with, so it works +-- for Ghostty and the foot fallback alike; initial_title so a later rename can't dodge it. +-- Fixed pixel size rather than monitor_w/h fractions: HDMI-A-1 is rotated portrait (1080 wide), +-- where a fraction of the width made it too narrow for package output; 1000x650 fits every screen. +hl.window_rule({ name = "system-update-terminal", float = true, center = true, size = "1000 650", match = { initial_title = "^(update)$" } }) -- viewer tags hl.window_rule({ name = "windowrule-50", tag = "+viewer", match = { class = "^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$" } }) -- system monitor diff --git a/dotfiles/.config/quickshell/scripts/update-system.sh b/dotfiles/.config/quickshell/scripts/update-system.sh index c531175..b7f66e6 100755 --- a/dotfiles/.config/quickshell/scripts/update-system.sh +++ b/dotfiles/.config/quickshell/scripts/update-system.sh @@ -6,38 +6,37 @@ # this machine is Arch, since these dotfiles aren't. # # The window it opens is floated + centered by a matching Hyprland rule -# (system-update-terminal, in .config/hypr/UserConfigs/WindowRules.conf), +# (system-update-terminal, in .config/hypr/layout/windowrules.lua), # same treatment as Bitwarden's window, rather than tiling into whatever # workspace you happened to be on. iDIR="$HOME/.config/swaync/images" -if ! command -v foot &> /dev/null; then - notify-send -i "$iDIR/error.png" "Need foot:" "foot terminal not found. Please install foot terminal." - exit 1 -fi - # Printed inside the terminal before the actual update command runs, so it # reads as "updating" from the first frame rather than looking like an # empty/stuck window while pacman/apt/etc. take a moment to start printing. # -# foot runs this in a fresh `bash -c`, which doesn't inherit shell functions -# from this script's own environment (export -f only reaches bash processes -# forked from *this* one, not whatever foot execs) — so instead, the -# already-colored text is built once here with real escape bytes ($'...'), -# then %q-quoted into a self-contained `echo '...'` that reproduces those -# same bytes verbatim when the child bash parses it. +# The terminal runs this in a fresh `bash -c`, which doesn't inherit shell +# functions from this script's own environment (export -f only reaches bash +# processes forked from *this* one, not whatever the terminal execs) — so +# instead, the already-colored text is built once here with real escape +# bytes ($'...'), then %q-quoted into a self-contained `echo '...'` that +# reproduces those same bytes verbatim when the child bash parses it. msg=" Updating System — please wait " line=$(printf '=%.0s' $(seq 1 ${#msg})) banner_text=$'\033[1;36m'"$line"$'\033[0m\n\033[1;36m'"$msg"$'\033[0m\n\033[1;36m'"$line"$'\033[0m\n\n' BANNER="echo $(printf '%q' "$banner_text")" +# GHOSTTY_INSTALL is how to install the terminal on this distro, if it's +# packaged there at all — only Arch ships it in the official repos. if command -v paru &> /dev/null; then CMD="paru -Syu" LABEL="Arch-based system" + GHOSTTY_INSTALL="pacman -S --needed --noconfirm ghostty" elif command -v yay &> /dev/null; then CMD="yay -Syu" LABEL="Arch-based system" + GHOSTTY_INSTALL="pacman -S --needed --noconfirm ghostty" elif command -v dnf &> /dev/null; then CMD="sudo dnf update --refresh -y" LABEL="Fedora system" @@ -52,15 +51,55 @@ else exit 1 fi +# --- Terminal: Ghostty, offering to install it if missing --- +# This runs from the bar with no terminal of its own, so there's nowhere to +# type a sudo password — the install goes through pkexec instead, which +# pops up the polkit agent's graphical password prompt. Declining (or a +# failed install) falls back to foot for this run, so updating never gets +# blocked on the terminal choice. +if ! command -v ghostty &> /dev/null; then + if [ -n "$GHOSTTY_INSTALL" ]; then + # -A waits for a click and prints the chosen action's name (nothing if + # the notification is dismissed instead). + choice=$(notify-send -i "$iDIR/error.png" -u critical \ + -A install="Install Ghostty" -A foot="Use foot this time" \ + "Ghostty not installed" "The system updater runs in Ghostty. Install it now?") + if [ "$choice" = "install" ]; then + if pkexec $GHOSTTY_INSTALL; then + notify-send -i "$iDIR/profile-pic.jpg" -u low "Ghostty installed" "Starting the update..." + else + notify-send -i "$iDIR/error.png" "Ghostty install failed" "Using foot for this update." + fi + fi + else + notify-send -i "$iDIR/error.png" "Ghostty not installed" \ + "Install Ghostty for the system updater — using foot for now." + fi +fi + +if command -v ghostty &> /dev/null; then + # --gtk-single-instance=false: otherwise, with a Ghostty window already + # open, this would just hand the window off to that running instance and + # return immediately — SystemUpdates.qml relies on this script blocking + # until the update window is closed (that's when it re-checks the count), + # and the "has been updated" notification below would fire up front. + TERMINAL=(ghostty --gtk-single-instance=false --title=update -e) +elif command -v foot &> /dev/null; then + TERMINAL=(foot -T update) +else + notify-send -i "$iDIR/error.png" "Need a terminal:" "Neither Ghostty nor foot is installed." + exit 1 +fi + # Keeps the terminal open (whether the update succeeded or failed — read # always runs regardless, since it's ; not &&) until you press Enter, -# instead of foot closing the instant the update command exits and taking -# any output/errors with it. +# instead of the terminal closing the instant the update command exits and +# taking any output/errors with it. WAIT_PROMPT="echo; read -p 'Update finished — press Enter to close...'" # Pull + re-stow the dotfiles first, so config changes from the other machine # land alongside the package update. SYNC="$(printf '%q' "$(dirname "$0")/sync-dotfiles.sh")" -foot -T update bash -c "$BANNER; $SYNC; $CMD; $WAIT_PROMPT" +"${TERMINAL[@]}" bash -c "$BANNER; $SYNC; $CMD; $WAIT_PROMPT" notify-send -i "$iDIR/profile-pic.jpg" -u low "$LABEL" 'has been updated.'