From ab11379dc3eef0ccd31dcf6ac63d481aeccc6d19 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 17:13:47 +0300 Subject: [PATCH 01/20] chore(retro): remove code comments --- modules/retroshell/files/scripts/thumbgen.py | 9 --------- scripts/wallpaper_core.sh | 1 - 2 files changed, 10 deletions(-) diff --git a/modules/retroshell/files/scripts/thumbgen.py b/modules/retroshell/files/scripts/thumbgen.py index 71cf693..8a7530e 100755 --- a/modules/retroshell/files/scripts/thumbgen.py +++ b/modules/retroshell/files/scripts/thumbgen.py @@ -22,9 +22,6 @@ # Default thumbnail size THUMBNAIL_SIZE = "140x140" -# Thumbnail cache directory, must match modules/retroshell Wallpaper.qml getThumbnailPath(). -# Kept separate from the full-resolution frame cache (wallpaper_frames) so widget -# thumbnails never clobber the frames used for static/lockscreen/colors display. FRAME_CACHE = Path.home() / ".config" / "retro" / "wallpaper_thumbs" @@ -155,12 +152,6 @@ def needs_thumbnail(self, file_path: Path) -> bool: return True def _prepare_thumbnail(self, thumbnail_path: Path) -> None: - """Make sure a thumbnail file can be written safely. - - This cache dir is dedicated to small thumbnails, but unlink any - pre-existing file/symlink so we never write through a link or into a - stale entry left over from a previous layout. - """ if thumbnail_path.is_symlink(): thumbnail_path.unlink() thumbnail_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/scripts/wallpaper_core.sh b/scripts/wallpaper_core.sh index 69ef4eb..7c15e7b 100755 --- a/scripts/wallpaper_core.sh +++ b/scripts/wallpaper_core.sh @@ -80,7 +80,6 @@ optimize_wallpapers() { pkill -9 mpvpaper 2>/dev/null sleep 1 - # Frames must match the new target resolution; rebuild them from scratch. rm -rf "$FRAME_CACHE" mkdir -p "$FRAME_CACHE" From 71115fed080ce417359fc0b3be9cd0473b3e311b Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 17:14:23 +0300 Subject: [PATCH 02/20] chore(quickshare): make quickshare disabled by default --- cmds/tools/settings/pages/quickshare.py | 35 +++---------------------- daemon/watchers/quickshare.lua | 2 +- modules/retro/files/variables.sh | 2 +- scripts/quickshare_core.sh | 2 +- 4 files changed, 7 insertions(+), 34 deletions(-) diff --git a/cmds/tools/settings/pages/quickshare.py b/cmds/tools/settings/pages/quickshare.py index 5eb7063..c124bb9 100644 --- a/cmds/tools/settings/pages/quickshare.py +++ b/cmds/tools/settings/pages/quickshare.py @@ -163,18 +163,11 @@ def _build_status_group(self) -> Adw.PreferencesGroup: self._enabled_sw = Adw.SwitchRow( title="Enable QuickShare", - subtitle="Start Quick Share automatically and keep it running", + subtitle="Make this device discoverable to Android Quick Share", ) self._enabled_sw.connect("notify::active", self._on_enabled) group.add(self._enabled_sw) - self._switch = Adw.SwitchRow( - title="Android Quick Share", - subtitle="Make this device discoverable to Android Quick Share", - ) - self._switch.connect("notify::active", self._on_switch) - group.add(self._switch) - self._dir_row = Adw.ActionRow(title="Download folder", subtitle="—") dir_btn = Gtk.Button(icon_name="folder-symbolic") dir_btn.set_valign(Gtk.Align.CENTER) @@ -485,7 +478,6 @@ def _render_status(self) -> None: running = self._status.get("state") == "running" self._setting_value = True - self._switch.set_active(running) self._autoaccept_sw.set_active(self._status.get("autoaccept") == "true") if hasattr(self, "_enabled_sw"): self._enabled_sw.set_active(_qs(["--enabled-status"]).strip() != "false") @@ -515,21 +507,6 @@ def _apply_status(self, out: str) -> None: # ── Actions ── - def _on_switch(self, sw: Adw.SwitchRow, _pspec) -> None: - if self._setting_value: - return - res = _qs(["--start"] if sw.get_active() else ["--stop"]) - if res.startswith("OK"): - self._window.show_toast( - "Quick Share enabled" if sw.get_active() else "Quick Share disabled" - ) - else: - self._window.show_bug_toast( - "Failed to toggle Quick Share", detail=res or "unknown error", timeout=5, - ) - sw.set_active(not sw.get_active()) - self._reload() - def _on_autoaccept(self, sw: Adw.SwitchRow, _pspec) -> None: if self._setting_value: return @@ -549,15 +526,14 @@ def _on_autoaccept(self, sw: Adw.SwitchRow, _pspec) -> None: def _on_enabled(self, sw: Adw.SwitchRow, _pspec) -> None: if self._setting_value: return - state = "on" if sw.get_active() else "off" - res = _qs(["--set-enabled", state]) + res = _qs(["--start"] if sw.get_active() else ["--stop"]) if res.startswith("OK"): self._window.show_toast( - f"Keep-alive {'enabled' if sw.get_active() else 'disabled'}" + "Quick Share enabled" if sw.get_active() else "Quick Share disabled" ) else: self._window.show_bug_toast( - "Failed to set keep-alive", detail=res or "unknown error", timeout=5, + "Failed to toggle Quick Share", detail=res or "unknown error", timeout=5, ) sw.set_active(not sw.get_active()) self._reload() @@ -896,9 +872,6 @@ def get_search_entries(self) -> list[dict]: {"key": "quickshare:autoaccept", "label": "Auto-accept Transfers", "description": "Accept incoming Quick Share files without prompting", "_group_id": "quickshare", "_group_label": "System", "_section_label": "Status"}, - {"key": "quickshare:enabled", "label": "Quick Share Enabled", - "description": "Auto-restart the Quick Share receiver if it stops", - "_group_id": "quickshare", "_group_label": "System", "_section_label": "Status"}, {"key": "quickshare:send", "label": "Send a File", "description": "Send files to a nearby Android Quick Share device", "_group_id": "quickshare", "_group_label": "System", "_section_label": "Actions"}, diff --git a/daemon/watchers/quickshare.lua b/daemon/watchers/quickshare.lua index 212c1e7..af738fd 100644 --- a/daemon/watchers/quickshare.lua +++ b/daemon/watchers/quickshare.lua @@ -23,7 +23,7 @@ return { while true do if Watcher.run_cmd("test -f '" .. core .. "' && echo ok"):find("ok", 1, true) - and Watcher.get_var("QUICKSHARE_ENABLED", "true") == "true" then + and Watcher.get_var("QUICKSHARE_ENABLED", "false") == "true" then local status = Watcher.run_cmd("bash '" .. core .. "' --status") local state = status:match("^([^|]*)") if state ~= "running" then diff --git a/modules/retro/files/variables.sh b/modules/retro/files/variables.sh index a46f316..0086373 100755 --- a/modules/retro/files/variables.sh +++ b/modules/retro/files/variables.sh @@ -130,7 +130,7 @@ export RETRO_RICING="false" export BT_RECEIVE_ACTIVE="false" export QUICKSHARE_DOWNLOAD_DIR="$HOME/Downloads" export QUICKSHARE_AUTO_ACCEPT="false" -export QUICKSHARE_ENABLED="true" +export QUICKSHARE_ENABLED="false" export BAT_IGNORE_APPS="" export BT_MAC_IGNORE="" export USB_IGNORE_DRIVES="" diff --git a/scripts/quickshare_core.sh b/scripts/quickshare_core.sh index 8130bd2..e65c206 100755 --- a/scripts/quickshare_core.sh +++ b/scripts/quickshare_core.sh @@ -273,7 +273,7 @@ qs_status() { qs_enabled_status() { local val - val=$(get_var "QUICKSHARE_ENABLED" "true") + val=$(get_var "QUICKSHARE_ENABLED" "false") [[ $val != "false" ]] && echo "true" || echo "false" } From a6f2380427797e3e067b7444d5ba6f4a80cb73c9 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 17:14:34 +0300 Subject: [PATCH 03/20] chore(lib): remove some code comments --- lib/python/variable.py | 2 -- lib/wallpaper.sh | 3 --- 2 files changed, 5 deletions(-) diff --git a/lib/python/variable.py b/lib/python/variable.py index f46dc2c..d52612c 100644 --- a/lib/python/variable.py +++ b/lib/python/variable.py @@ -26,8 +26,6 @@ def _get_mtime(path): def _strip_quotes(val): - """Remove a single matching pair of surrounding quotes, preserving any - quote chars inside the value (e.g. ``hyprctl dispatch 'hl.dsp.exit()'``).""" if len(val) >= 2 and val[0] == val[-1] and val[0] in "\"'": return val[1:-1] return val diff --git a/lib/wallpaper.sh b/lib/wallpaper.sh index 28ca35f..6e7d53f 100755 --- a/lib/wallpaper.sh +++ b/lib/wallpaper.sh @@ -22,9 +22,6 @@ rx_wallpaper_generate_cache() { [[ -z $custom_res || $custom_res == "null" || ! $custom_res =~ ^[0-9]+x[0-9]+$ ]] && custom_res="" if [[ $target =~ \.(mp4|mkv|webm)$ ]]; then - # Regenerate stale frames: source changed, or the cached frame no - # longer matches the target resolution (e.g. after WALL_RESOLUTION - # changed or a thumbnail generator clobbered the frame). if [[ -f $output ]]; then local stale=false [[ $target -nt $output ]] && stale=true From 783ab99169ef4be3be7d7c2c7f049e4b2f74bedf Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 17:14:46 +0300 Subject: [PATCH 04/20] feat(retro): add zenity package --- modules/retro/packages.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/retro/packages.sh b/modules/retro/packages.sh index 5ad75ab..78dab37 100755 --- a/modules/retro/packages.sh +++ b/modules/retro/packages.sh @@ -7,6 +7,7 @@ sed grep rsync unzip +zenity expect notify udisks2 From b7db10a183e944b2dd312e4efa742e82682d36cf Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 17:15:22 +0300 Subject: [PATCH 05/20] feat(update): force stable updates to check for release tags only --- cmds/system/update.sh | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/cmds/system/update.sh b/cmds/system/update.sh index 1a73d59..3422dce 100755 --- a/cmds/system/update.sh +++ b/cmds/system/update.sh @@ -62,11 +62,28 @@ cmd_update() { rx_log "info" "Syncing repository with $(rx_git_branch)" + local old_release_tag="" + local new_release_tag="" + if [[ $(rx_git_branch) == "main" ]]; then + old_release_tag=$(git -C "$RETRO_DIR" describe --tags --abbrev=0 --match 'v[0-9]*' "$old_head" 2>/dev/null || true) + git -C "$RETRO_DIR" fetch origin >/dev/null 2>&1 || true + new_release_tag=$(git -C "$RETRO_DIR" describe --tags --abbrev=0 --match 'v[0-9]*' origin/main 2>/dev/null || true) + if [[ -z $new_release_tag || $new_release_tag == "$old_release_tag" ]]; then + rx_log "warn" "No new release published on main yet (current: ${old_release_tag:-none})." + rx_log "warn" "Stable updates only ship with a new release tag. Skipping update." + return 0 + fi + fi + if git -C "$RETRO_DIR" pull 2>&1; then local new_head=$(git -C "$RETRO_DIR" rev-parse HEAD 2>/dev/null) if [[ $old_head != $new_head ]]; then - commits=$(git -C "$RETRO_DIR" log "$old_head..$new_head" --pretty=format:"%s" --no-merges 2>/dev/null) + if [[ $(rx_git_branch) == "main" && -n $old_release_tag && -n $new_release_tag ]]; then + commits=$(git -C "$RETRO_DIR" log "$old_release_tag..$new_release_tag" --pretty=format:"%s" --no-merges 2>/dev/null) + else + commits=$(git -C "$RETRO_DIR" log "$old_head..$new_head" --pretty=format:"%s" --no-merges 2>/dev/null) + fi if [[ -n $commits ]]; then rx_table_header "󰜘" "Changelog" @@ -203,6 +220,11 @@ cmd_update() { rx_log "info" "Restarting Retro daemon..." $RETRO_DIR/retro.sh daemon restart + + if [[ -n $commits ]] && grep -qi "retroshell" <<<"$commits"; then + rx_log "info" "RetroShell changed in this update, restarting it..." + $RETRO_DIR/retro.sh shell restart + fi else pull_failed="true" From b4674b5b2ace0120399b7112e4374209877a95ba Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 20:45:32 +0300 Subject: [PATCH 06/20] fix(quickshare): fix instant offer declined --- scripts/python/quickshare_receive.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/python/quickshare_receive.py b/scripts/python/quickshare_receive.py index 49906f5..0dbe30e 100644 --- a/scripts/python/quickshare_receive.py +++ b/scripts/python/quickshare_receive.py @@ -337,9 +337,17 @@ def notif_run(): timeout=120000, wait=True, ) - notif_result["action"] = r.stdout.strip() - except Exception: - notif_result["action"] = "" + action = r.stdout.strip() if r.returncode == 0 else "" + if action in ("accept", "deny"): + notif_result["action"] = action + elif r.returncode != 0: + rx_log_file( + "warn", + f"Accept notification not displayed (rc={r.returncode}): " + f"{r.stderr.strip()[:200] or 'no stderr'}", + ) + except Exception as exc: + rx_log_file("warn", f"Accept notification not displayed: {exc}") threading.Thread(target=notif_run, daemon=True).start() @@ -466,6 +474,11 @@ def main(): pass device_name = args.name or os.uname().nodename + if shutil.which("notify-send") is None: + rx_log_file( + "warn", + "notify-send not found — accept notifications unavailable; use Settings → Quick Share → Transfers to accept offers", + ) rx_log_file("info", f"Quick Share receiver starting (device '{device_name}', dir {args.dir}, auto-accept {'on' if args.yes else 'off'})") signal.signal(signal.SIGTERM, _signal_handler) From cef0c79554cb0fb116f57936fbbc5ccaf21a41ed Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 21:23:29 +0300 Subject: [PATCH 07/20] fix(daemon): fix battery notification spam --- daemon/watchers/battery.lua | 58 ++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/daemon/watchers/battery.lua b/daemon/watchers/battery.lua index 16c11f8..cbbd8e5 100644 --- a/daemon/watchers/battery.lua +++ b/daemon/watchers/battery.lua @@ -22,19 +22,35 @@ return { Watcher.log("battery", "Using configured battery: " .. bat_path, "info") end - local last_notified_level = 0 local saver_thresh = tonumber(Watcher.get_var("BAT_SAVER_THRESHOLD", "20")) or 20 - local low_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_THRESHOLD", "20")) or 20 - local crit_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_CRITICAL_THRESHOLD", "5")) or 5 + local notify_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_THRESHOLD", "30")) or 30 + local crit_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_CRITICAL_THRESHOLD", "15")) or 15 local tick_counter = 0 - Watcher.log("battery", string.format("Thresholds: saver=%d%%, low=%d%%, critical=%d%%", saver_thresh, low_thresh, crit_thresh), "info") + local function build_notify_levels() + local seen, out = {}, {} + for _, level in ipairs({ notify_thresh, 20, 15, 10, 5 }) do + if not seen[level] then + seen[level] = true + out[#out + 1] = level + end + end + table.sort(out, function(a, b) return a > b end) + return out + end + + local notify_levels = build_notify_levels() + local notified_levels = {} + local cycle_start_capacity = nil + + Watcher.log("battery", string.format("Thresholds: saver=%d%%, notify=%d%%, critical=%d%%", saver_thresh, notify_thresh, crit_thresh), "info") while true do if tick_counter % 10 == 0 then saver_thresh = tonumber(Watcher.get_var("BAT_SAVER_THRESHOLD", "20")) or 20 - low_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_THRESHOLD", "20")) or 20 - crit_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_CRITICAL_THRESHOLD", "5")) or 5 + notify_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_THRESHOLD", "30")) or 30 + crit_thresh = tonumber(Watcher.get_var("BAT_NOTIFY_CRITICAL_THRESHOLD", "15")) or 15 + notify_levels = build_notify_levels() end local capacity_str = Watcher.read_sys(bat_path .. "/capacity") @@ -66,15 +82,29 @@ return { end if status == "discharging" then - if capacity <= crit_thresh and capacity ~= last_notified_level then - Watcher.log("battery", "CRITICAL battery: " .. capacity .. "% (thresh: " .. crit_thresh .. "%)", "error") - engine:emit("on_battery_critical", tostring(capacity)) - last_notified_level = capacity - elseif capacity <= low_thresh and capacity ~= last_notified_level then - Watcher.log("battery", "LOW battery: " .. capacity .. "% (thresh: " .. low_thresh .. "%)", "warn") - engine:emit("on_battery_low", tostring(capacity)) - last_notified_level = capacity + if cycle_start_capacity == nil then + cycle_start_capacity = capacity + end + + for _, level in ipairs(notify_levels) do + if not notified_levels[level] + and capacity <= level + and level <= cycle_start_capacity + then + notified_levels[level] = true + if level <= crit_thresh then + Watcher.log("battery", "CRITICAL battery crossed " .. level .. "% (current " .. capacity .. "%)", "error") + engine:emit("on_battery_critical", tostring(capacity)) + else + Watcher.log("battery", "LOW battery crossed " .. level .. "% (current " .. capacity .. "%)", "warn") + engine:emit("on_battery_low", tostring(capacity)) + end + break + end end + elseif status == "charging" or status == "full" or status == "not charging" then + notified_levels = {} + cycle_start_capacity = nil end tick_counter = tick_counter + 1 From 93c047438f5fbfb1a251cdb740476354b89a6dcb Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 21:31:05 +0300 Subject: [PATCH 08/20] chore(retroshell): remove battery notification deadcode --- .../files/modules/services/Battery.qml | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/modules/retroshell/files/modules/services/Battery.qml b/modules/retroshell/files/modules/services/Battery.qml index a200337..e8ba6fe 100644 --- a/modules/retroshell/files/modules/services/Battery.qml +++ b/modules/retroshell/files/modules/services/Battery.qml @@ -17,7 +17,6 @@ Singleton { readonly property bool isCharging: available && primaryDevice.state === UPowerDevice.Charging readonly property bool isPluggedIn: available && (primaryDevice.state === UPowerDevice.Charging || primaryDevice.state === UPowerDevice.FullyCharged) readonly property int chargeState: available ? primaryDevice.state : UPowerDevice.Unknown - property int lastBatteryAlertThreshold: 0 // Add some helpful descriptive properties if needed readonly property string timeToEmpty: available && primaryDevice.timeToEmpty > 0 ? formatTime(primaryDevice.timeToEmpty) : "" @@ -42,67 +41,4 @@ Singleton { if (pct > 5) return horizontal ? Icons.batteryHLow : Icons.batteryLow; return horizontal ? Icons.batteryHEmpty : Icons.batteryEmpty; } - - function evaluateBatteryAlert() { - if (!available || isPluggedIn) { - lastBatteryAlertThreshold = 0; - return; - } - - const roundedPercentage = Math.floor(percentage); - const threshold = roundedPercentage <= 10 ? 10 : (roundedPercentage <= 20 ? 20 : 0); - if (threshold === 0) { - lastBatteryAlertThreshold = 0; - return; - } - - if (threshold === lastBatteryAlertThreshold) { - return; - } - - sendBatteryAlert(threshold, roundedPercentage); - lastBatteryAlertThreshold = threshold; - } - - function sendBatteryAlert(threshold, roundedPercentage) { - const isCritical = threshold <= 10; - Notifications.notifyInternal({ - "appName": "Battery", - "summary": isCritical ? "Critical battery" : "Low battery", - "body": "Battery is at " + roundedPercentage + "%." + (isCritical ? " Connect your charger or enable power saver." : " Enable power saver to reduce consumption."), - "urgency": NotificationUrgency.Critical, - "historyPriority": 100, - "replaceKey": "battery-low-alert", - "expireTimeout": 10000, - "actions": [{ - "identifier": "enable-power-saver", - "text": "Power saver" - }, { - "identifier": "dismiss", - "text": "Ignore" - }], - "actionHandlers": { - "enable-power-saver": function () { - PowerProfile.setProfile("power-saver"); - }, - "dismiss": function (id) { - Notifications.discardNotification(id); - } - } - }); - } - - Connections { - target: root - - function onPercentageChanged() { - root.evaluateBatteryAlert(); - } - - function onIsPluggedInChanged() { - root.evaluateBatteryAlert(); - } - } - - Component.onCompleted: Qt.callLater(root.evaluateBatteryAlert) } From 6be7ddfe72c4dd145758fac88406e10897445866 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 21:31:36 +0300 Subject: [PATCH 09/20] feat(retro): add hide bluetooth applet on install --- modules/retro/install.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/retro/install.sh b/modules/retro/install.sh index a4ba541..952654f 100755 --- a/modules/retro/install.sh +++ b/modules/retro/install.sh @@ -114,6 +114,42 @@ EOF } hide_nm_applet +hide_bluetooth_applet() { + local found="" + local dir + for dir in /etc/xdg/autostart "$HOME/.config/autostart" /usr/share/autostart; do + if [[ -f "$dir/blueman.desktop" ]]; then + found="$dir/blueman.desktop" + break + fi + done + + if [[ -z $found ]]; then + rx_log "info" "blueman.desktop not found; nothing to hide" + return 0 + fi + + if grep -q '^Hidden=true' "$found" 2>/dev/null; then + rx_log "success" "blueman.desktop already hidden ($found)" + return 0 + fi + + mkdir -p "$HOME/.config/autostart" + cat >"$HOME/.config/autostart/blueman.desktop" <<'EOF' +[Desktop Entry] +Type=Application +Name=Bluetooth Manager +Hidden=true +EOF + rx_log "success" "blueman applet hidden via user autostart override" + + if pgrep -x blueman-applet >/dev/null 2>&1; then + pkill -x blueman-applet 2>/dev/null || true + rx_log "info" "Stopped running blueman-applet" + fi +} +hide_bluetooth_applet + remove_conflicts if [[ $SECONDARY_INSTALL != "true" ]]; then From 8370df3b681403bc5598c9b0219118365e3c9c43 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 21:32:26 +0300 Subject: [PATCH 10/20] feat(settings): add xdg autostart management support --- cmds/tools/settings/core/autostart.py | 81 ++++++++++++++ cmds/tools/settings/pages/autostart.py | 149 ++++++++++++++++++++++++- lib/python/env.py | 1 + lib/xdg.sh | 14 +++ scripts/xdg_core.sh | 14 +++ 5 files changed, 256 insertions(+), 3 deletions(-) diff --git a/cmds/tools/settings/core/autostart.py b/cmds/tools/settings/core/autostart.py index 459e22b..1b35635 100644 --- a/cmds/tools/settings/core/autostart.py +++ b/cmds/tools/settings/core/autostart.py @@ -6,6 +6,8 @@ pipe-separated list. """ +import os +import re import subprocess from dataclasses import dataclass @@ -63,3 +65,82 @@ def parse_retro_startup(raw: str) -> list[RetroStartupData]: def serialize_retro_startup(items: list[RetroStartupData]) -> str: """Join retro startup entries into a pipe-separated string.""" return "|".join(item.command for item in items) + + +# --------------------------------------------------------------------------- +# XDG application autostart (wraps scripts/xdg_core.sh) +# --------------------------------------------------------------------------- + +_XDG_CORE = os.path.join( + os.environ.get("RETRO_DIR", "/opt/retrolinux"), "scripts", "xdg_core.sh" +) + + +@dataclass(slots=True) +class XdgAutostartEntry: + """A single XDG autostart ``.desktop`` entry (user or system scope).""" + + name: str + path: str + enabled: bool + binary_exists: bool + scope: str + + +def _run_xdg(args: list[str], timeout: int = 15) -> str: + try: + r = subprocess.run( + ["bash", _XDG_CORE, *args], + capture_output=True, text=True, timeout=timeout, + stdin=subprocess.DEVNULL, + ) + return r.stdout.strip() + except Exception: + return "" + + +def list_xdg_autostart() -> list[XdgAutostartEntry]: + """List all XDG autostart entries (user scope wins over system). + + The underlying script emits ``name|desktop_file|enabled|binary_exists|scope`` + lines, scanning user then system autostart dirs. A user override shadows the + system entry of the same filename, so we dedupe by ``.desktop`` basename. + """ + out = _run_xdg(["--autostart-list"]) + entries: list[XdgAutostartEntry] = [] + seen: set[str] = set() + for line in out.splitlines(): + parts = line.split("|") + if len(parts) < 5: + continue + name, path, enabled, binary, scope = parts[:5] + basename = os.path.basename(path) + if basename in seen: + continue + seen.add(basename) + entries.append(XdgAutostartEntry( + name=name, + path=path, + enabled=(enabled == "true"), + binary_exists=(binary == "yes"), + scope=scope, + )) + entries.sort(key=lambda e: (0 if e.scope == "user" else 1, e.name.lower())) + return entries + + +def toggle_xdg_autostart(desktop: str, action: str = "toggle") -> bool: + """Enable/disable an XDG autostart entry by ``.desktop`` filename.""" + return _run_xdg(["--autostart-toggle", desktop, action]).startswith("OK") + + +def delete_xdg_autostart(desktop: str) -> bool: + """Remove a user XDG autostart entry (or its override) by filename.""" + return _run_xdg(["--autostart-delete", desktop]).startswith("OK") + + +def clean_xdg_autostart() -> int: + """Remove stale user autostart entries whose binary is missing.""" + out = _run_xdg(["--autostart-clean"]) + m = re.search(r"cleaned=(\d+)", out) + return int(m.group(1)) if m else 0 diff --git a/cmds/tools/settings/pages/autostart.py b/cmds/tools/settings/pages/autostart.py index d5df569..be665e2 100644 --- a/cmds/tools/settings/pages/autostart.py +++ b/cmds/tools/settings/pages/autostart.py @@ -5,15 +5,22 @@ editable entries. No Hyprland ``exec``/``exec-once`` involvement. """ +import os +import threading from html import escape as html_escape -from gi.repository import Adw, Gtk +from gi.repository import Adw, GLib, Gtk from settings.core.autostart import ( RetroStartupData, + XdgAutostartEntry, + clean_xdg_autostart, + delete_xdg_autostart, get_system_tasks, + list_xdg_autostart, parse_retro_startup, serialize_retro_startup, + toggle_xdg_autostart, ) from settings.pages.section import SavedListSectionPage from settings.ui import clear_children, make_inline_hint, make_page_layout @@ -66,6 +73,7 @@ def __init__( self._content_box: Gtk.Box self._scrolled: Gtk.ScrolledWindow self._retro_rows: list[Gtk.Widget] = [] + self._xdg_group: Adw.PreferencesGroup | None = None self._reorder = RowReorderController( move=self._move_retro_item, iter_rows=lambda: self._retro_rows, @@ -81,7 +89,25 @@ def _load(self, saved_sections: dict[str, list[str]] | None = None) -> None: items = parse_retro_startup(raw) self._retro_owned: list[RetroStartupData] = items self._retro_saved = list(items) - self._system_tasks = get_system_tasks() + self._system_tasks: list[tuple[str, str]] = [] + self._xdg_entries: list[XdgAutostartEntry] = [] + self._xdg_async(lambda: (get_system_tasks(),), self._on_system_tasks_loaded) + self._xdg_async(lambda: (list_xdg_autostart(),), self._on_xdg_list_loaded) + + def _on_system_tasks_loaded(self, tasks: list[tuple[str, str]]) -> None: + self._system_tasks = tasks + if self._content_box is None: + return + self._rebuild_list() + + def _on_xdg_list_loaded(self, entries: list[XdgAutostartEntry]) -> None: + self._xdg_entries = entries + if self._content_box is None: + return + if self._xdg_group is None: + self._rebuild_list() + else: + self._refresh_xdg_group(entries) # ── Build ── @@ -145,6 +171,120 @@ def _build_custom_group(self) -> list[Gtk.Widget]: group.add(self._make_retro_row(idx, item)) return [group] + def _build_xdg_group(self) -> list[Gtk.Widget]: + if not self._xdg_entries: + return [] + + group = Adw.PreferencesGroup(title="Application Autostart") + n = len(self._xdg_entries) + group.set_description( + f"{n} app{'s' if n != 1 else ''} launching at login" + ) + + clean_btn = Gtk.Button(icon_name="user-trash-symbolic") + clean_btn.set_valign(Gtk.Align.CENTER) + clean_btn.add_css_class("flat") + clean_btn.set_tooltip_text("Remove stale autostart entries (missing binary)") + clean_btn.connect("clicked", lambda _b: self._on_clean_xdg()) + group.set_header_suffix(clean_btn) + + self._xdg_group = group + for entry in self._xdg_entries: + group.add(self._make_xdg_row(entry)) + return [group] + + def _make_xdg_row(self, entry: XdgAutostartEntry) -> Adw.SwitchRow: + scope = "System" if entry.scope == "system" else "User" + subtitle = f"{scope} · {os.path.basename(entry.path)}" + if not entry.binary_exists: + subtitle += " · binary missing" + + row = Adw.SwitchRow(title=html_escape(entry.name), subtitle=subtitle) + row.set_active(entry.enabled) + row.connect("notify::active", lambda r, e=entry: self._on_xdg_toggle(r, e)) + if not entry.binary_exists: + row.add_css_class("option-default") + row.set_opacity(0.7) + + delete_btn = Gtk.Button(icon_name="user-trash-symbolic") + delete_btn.set_valign(Gtk.Align.CENTER) + delete_btn.add_css_class("flat") + delete_btn.set_tooltip_text("Remove this autostart entry") + delete_btn.connect("clicked", lambda _b, e=entry: self._on_delete_xdg(e)) + row.add_suffix(delete_btn) + return row + + def _on_xdg_toggle(self, row: Adw.SwitchRow, entry: XdgAutostartEntry) -> None: + action = "enable" if row.get_active() else "disable" + desktop = os.path.basename(entry.path) + + def worker() -> tuple[bool, list[XdgAutostartEntry]]: + return toggle_xdg_autostart(desktop, action), list_xdg_autostart() + + self._xdg_async(worker, self._on_xdg_toggle_done, row, action) + + def _on_xdg_toggle_done(self, row: Adw.SwitchRow, action: str, ok: bool, entries: list[XdgAutostartEntry]) -> None: + if not ok: + self._window.show_toast("Failed to update autostart entry", timeout=4) + self._refresh_xdg_group(entries) + + def _on_delete_xdg(self, entry: XdgAutostartEntry) -> None: + desktop = os.path.basename(entry.path) + + def worker() -> tuple[bool, list[XdgAutostartEntry]]: + return delete_xdg_autostart(desktop), list_xdg_autostart() + + self._xdg_async(worker, self._on_xdg_delete_done, entry) + + def _on_xdg_delete_done(self, entry: XdgAutostartEntry, ok: bool, entries: list[XdgAutostartEntry]) -> None: + if ok: + self._window.show_toast(f"Removed {entry.name} from autostart", timeout=4) + else: + self._window.show_toast("Nothing to remove — entry is system-owned", timeout=4) + self._refresh_xdg_group(entries) + + def _on_clean_xdg(self) -> None: + def worker() -> tuple[int, list[XdgAutostartEntry]]: + return clean_xdg_autostart(), list_xdg_autostart() + + self._xdg_async(worker, self._on_xdg_clean_done) + + def _on_xdg_clean_done(self, cleaned: int, entries: list[XdgAutostartEntry]) -> None: + if cleaned: + self._window.show_toast(f"Removed {cleaned} stale autostart entr{'y' if cleaned == 1 else 'ies'}", timeout=4) + else: + self._window.show_toast("No stale autostart entries found", timeout=3) + self._refresh_xdg_group(entries) + + def _xdg_async(self, work, on_done, *on_done_args) -> None: + def runner() -> None: + try: + result = work() + except Exception: + result = None + GLib.idle_add(self._dispatch_xdg_result, on_done, on_done_args, result) + + threading.Thread(target=runner, daemon=True).start() + + @staticmethod + def _dispatch_xdg_result(on_done, on_done_args, result) -> bool: + if result is not None: + on_done(*on_done_args, *result) + return False + + def _refresh_xdg_group(self, entries: list[XdgAutostartEntry] | None = None) -> None: + if self._xdg_group is None: + return + if entries is None: + entries = list_xdg_autostart() + n = len(entries) + self._xdg_group.set_description( + f"{n} app{'s' if n != 1 else ''} launching at login" + ) + clear_children(self._xdg_group) + for entry in entries: + self._xdg_group.add(self._make_xdg_row(entry)) + def _rebuild_list(self) -> None: clear_children(self._content_box) @@ -156,10 +296,13 @@ def _rebuild_list(self) -> None: self._retro_rows = [None] * len(self._retro_owned) for w in self._build_custom_group(): self._content_box.append(w) + self._xdg_group = None + for w in self._build_xdg_group(): + self._content_box.append(w) for w in self._build_system_group(): self._content_box.append(w) - if not self._retro_owned and not self._system_tasks: + if not self._retro_owned and not self._system_tasks and not self._xdg_entries: self._content_box.append(self._build_empty_state()) def _build_empty_state(self) -> EmptyState: diff --git a/lib/python/env.py b/lib/python/env.py index 4981c1d..1512369 100644 --- a/lib/python/env.py +++ b/lib/python/env.py @@ -11,6 +11,7 @@ def get_shell_env(overrides=None): "DISPLAY": os.environ.get("DISPLAY", ""), "DBUS_SESSION_BUS_ADDRESS": os.environ.get("DBUS_SESSION_BUS_ADDRESS", ""), "XDG_RUNTIME_DIR": os.environ.get("XDG_RUNTIME_DIR", ""), + "PATH": os.environ.get("PATH", "/usr/local/bin:/usr/bin:/bin"), "RETRO_DIR": os.environ.get("RETRO_DIR", ""), "RETRO_CONFIG": os.environ.get("RETRO_CONFIG", ""), } diff --git a/lib/xdg.sh b/lib/xdg.sh index e2cbd4a..b23db22 100755 --- a/lib/xdg.sh +++ b/lib/xdg.sh @@ -744,6 +744,20 @@ rx_xdg_autostart_toggle() { esac } +rx_xdg_autostart_delete() { + local name="$1" + [[ -z $name ]] && echo "result=error|reason=no_name" && return 1 + + local file="$HOME/.config/autostart/$name" + if [[ -f $file || -L $file ]]; then + rm -f "$file" + echo "OK|deleted" + else + echo "result=error|reason=not_found" + return 1 + fi +} + rx_xdg_autostart_clean() { local user_autostart="$HOME/.config/autostart" [[ ! -d $user_autostart ]] && echo "cleaned=0" && return 0 diff --git a/scripts/xdg_core.sh b/scripts/xdg_core.sh index dc1fc68..09702bb 100755 --- a/scripts/xdg_core.sh +++ b/scripts/xdg_core.sh @@ -175,6 +175,19 @@ autostart_clean() { echo "$result" } +autostart_delete() { + local name="$1" + [[ -z $name ]] && echo "result=error|reason=no_name" && return 1 + local result=$(rx_xdg_autostart_delete "$name") + if [[ $result == OK* ]]; then + rx_log_file "INFO" "Autostart entry deleted: $name" + echo "$result" + else + echo "$result" + return 1 + fi +} + health_check() { rx_xdg_health } @@ -208,6 +221,7 @@ case "$1" in "--query") query_file "$2" ;; "--autostart-list") autostart_list ;; "--autostart-toggle") autostart_toggle "$2" "$3" ;; + "--autostart-delete") autostart_delete "$2" ;; "--autostart-clean") autostart_clean ;; "--health") health_check ;; "--status") full_status ;; From 27fabab39c2616e5b390327bbb32dbf7579830f3 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Fri, 14 Aug 2026 23:14:59 +0300 Subject: [PATCH 11/20] fix(tools): move quickshare & users to tools category --- cmds/tools/quickshare.sh | 2 +- cmds/tools/users.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/tools/quickshare.sh b/cmds/tools/quickshare.sh index ad5a4ee..53d7a76 100755 --- a/cmds/tools/quickshare.sh +++ b/cmds/tools/quickshare.sh @@ -174,4 +174,4 @@ cmd_quickshare() { esac } -register_command "SYSTEM" "quickshare" "Native Android Quick Share receiver (no rquickshare)" "cmd_quickshare" +register_command "TOOLS" "quickshare" "Native Android Quick Share receiver (no rquickshare)" "cmd_quickshare" diff --git a/cmds/tools/users.sh b/cmds/tools/users.sh index cf83a59..af65e1b 100755 --- a/cmds/tools/users.sh +++ b/cmds/tools/users.sh @@ -286,4 +286,4 @@ cmd_users() { esac } -register_command "SYSTEM" "users" "Manage system users with Retro config" "cmd_users" +register_command "TOOLS" "users" "Manage system users with Retro config" "cmd_users" From ec8a2f8e29ab457826fe815367d860756024b641 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:00:56 +0300 Subject: [PATCH 12/20] feat(retro): save last stable version --- modules/retro/files/variables.sh | 1 + modules/retro/install.sh | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/modules/retro/files/variables.sh b/modules/retro/files/variables.sh index 0086373..34ca43b 100755 --- a/modules/retro/files/variables.sh +++ b/modules/retro/files/variables.sh @@ -125,6 +125,7 @@ export RETRO_BLUR_PASSES="1" export RETRO_BLUR_VIBRANCY="0.1696" export RETRO_INSTALL="complete" export RETRO_BRANCH="develop" +export RETRO_LAST_STABLE="" export WALL_CURRENT="$HOME/.config/retro/wallpapers/retro/car-in-neon-gas-station.mp4" export RETRO_RICING="false" export BT_RECEIVE_ACTIVE="false" diff --git a/modules/retro/install.sh b/modules/retro/install.sh index 952654f..c88c883 100755 --- a/modules/retro/install.sh +++ b/modules/retro/install.sh @@ -271,6 +271,26 @@ sync_missing_variables() { sync_missing_variables +remember_last_stable() { + local current + current=$(bash "$RETRO_DIR/scripts/variable_core.sh" --get RETRO_LAST_STABLE 2>/dev/null) + [[ -z $current || $current == "null" ]] || return 0 + + local tag + tag=$(git -C "$RETRO_DIR" describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null) + if [[ -z $tag ]]; then + git -C "$RETRO_DIR" fetch origin main >/dev/null 2>&1 || true + tag=$(git -C "$RETRO_DIR" describe --tags --abbrev=0 --match 'v[0-9]*' origin/main 2>/dev/null) + fi + [[ -z $tag ]] && tag=$(git -C "$RETRO_DIR" tag --list 'v[0-9]*' --sort=-v:refname 2>/dev/null | head -1) + + if [[ -n $tag ]]; then + bash "$RETRO_DIR/scripts/variable_core.sh" --set RETRO_LAST_STABLE "$tag" >/dev/null 2>&1 + rx_log "success" "Remembered last stable version: ${PINK}${tag}${RESET}" + fi +} +remember_last_stable + HYPRIDLE_SRC="$RETRO_DIR/modules/hyprland/files/hypridle.conf" HYPRIDLE_DST="$RETRO_CONFIG/hypridle.conf" if [[ ! -f $HYPRIDLE_DST ]] || cmp -s "$HYPRIDLE_SRC" "$HYPRIDLE_DST"; then From 58f85017b97ea08e6a518dad7169526f387c136a Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:01:25 +0300 Subject: [PATCH 13/20] feat(settings): add timeshift backup on branch switch --- cmds/tools/settings/pages/about.py | 122 ++++++++++++++++++----------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/cmds/tools/settings/pages/about.py b/cmds/tools/settings/pages/about.py index 49c46ce..8f82db7 100644 --- a/cmds/tools/settings/pages/about.py +++ b/cmds/tools/settings/pages/about.py @@ -355,40 +355,69 @@ def do_switch(): ) def _switch_branch(self, target: str) -> None: + backup_ok, _backup_msg = self._create_backup(target) + if backup_ok: + self._finish_branch_switch(target) + else: + GLib.idle_add(self._ask_continue_without_backup, target) + + def _create_backup(self, target: str) -> tuple[bool, str]: + try: + r = subprocess.run( + ["retro", "timeshift", "create", f"Pre-branch-switch: {target}"], + capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL, + ) + except Exception as e: + return False, str(e) + return "CREATED" in (r.stdout + r.stderr), (r.stdout or r.stderr).strip() + + def _ask_continue_without_backup(self, target: str) -> None: + dialog = Adw.AlertDialog( + heading="Backup failed", + body="Timeshift failed, no system backup was able to be made. " + "Do you want to continue switching branches anyway?", + ) + dialog.add_response("cancel", "Abort") + dialog.add_response("confirm", "Continue") + dialog.set_response_appearance("confirm", Adw.ResponseAppearance.DESTRUCTIVE) + dialog.set_default_response("cancel") + dialog.set_close_response("cancel") + + def on_response(_dialog, response): + if response == "confirm": + threading.Thread( + target=self._finish_branch_switch, args=(target,), daemon=True + ).start() + else: + self._branch_switching = False + if self._branch_dd is not None: + self._branch_dd.set_sensitive(True) + self._branch_dd.set_selected(self._branch_index(self._current_branch())) + + dialog.connect("response", on_response) + dialog.present(self._window) + + def _finish_branch_switch(self, target: str) -> None: ok, msg = self._git_switch(target) GLib.idle_add(self._on_branch_switched, ok, msg) def _git_switch(self, target: str) -> tuple[bool, str]: - repo = os.environ.get("RETRO_DIR", "/opt/retrolinux") - - def git(*args): - return subprocess.run( - ["git", "-C", repo, *args], + """Delegate the switch to ``retro -b switch`` (single source of truth).""" + try: + r = subprocess.run( + ["retro", "-b", "switch", target], capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL, ) - - try: - if git("status", "--porcelain").stdout.strip(): - git("reset", "--hard") - r = git("fetch", "origin") - if r.returncode != 0: - return False, r.stderr.strip() or f"Failed to fetch origin" - r = git("rev-parse", "--verify", f"origin/{target}") - if r.returncode != 0: - return False, f"Branch '{target}' does not exist on the remote" - r = git("checkout", "-B", target, f"origin/{target}") - if r.returncode != 0: - return False, r.stderr.strip() or f"Failed to switch to {target}" except Exception as e: return False, str(e) - - try: - from lib.python.variable import set_var - set_var("RETRO_BRANCH", target) - except Exception: - pass - return True, target + out = (r.stdout or "").strip() + if out.startswith("OK|"): + return True, out[3:] + reason = out + if "reason=" in out: + reason = out.split("reason=", 1)[-1] + return False, reason or "Switch failed" def _on_branch_switched(self, ok: bool, msg: str) -> None: self._branch_switching = False @@ -397,6 +426,8 @@ def _on_branch_switched(self, ok: bool, msg: str) -> None: if ok: self._window.show_toast(f"Switched to {msg}") self._refresh_version_display() + if msg == "develop": + self._run_terminal(self._reinstall_command()) else: if self._branch_dd is not None: self._branch_dd.set_selected(self._branch_index(self._current_branch())) @@ -451,35 +482,20 @@ def worker(): @staticmethod def _count_updates() -> int: - """Count Retro Linux updates = commits behind the remote branch.""" - repo = os.environ.get("RETRO_DIR", "/opt/retrolinux") - try: - branch = subprocess.run( - ["git", "-C", repo, "rev-parse", "--abbrev-ref", "HEAD"], - capture_output=True, text=True, timeout=5, - stdin=subprocess.DEVNULL, - ).stdout.strip() - except Exception: - branch = "" - if not branch: - return -1 + """Count updates via ``retro -b updates`` (single source of truth).""" try: r = subprocess.run( - ["git", "-C", repo, "fetch", "origin"], + ["retro", "-b", "updates"], capture_output=True, text=True, timeout=60, stdin=subprocess.DEVNULL, ) except Exception: return -1 - try: - out = subprocess.run( - ["git", "-C", repo, "rev-list", "--count", f"HEAD..origin/{branch}"], - capture_output=True, text=True, timeout=15, - stdin=subprocess.DEVNULL, - ).stdout.strip() - return int(out) if out.isdigit() else 0 - except Exception: - return -1 + for line in (r.stdout or "").splitlines(): + if line.startswith("count="): + val = line[len("count="):] + return int(val) if val.lstrip("-").isdigit() else -1 + return -1 def _on_updates_checked(self, count: int, interactive: bool) -> None: self._updates_count = count @@ -523,6 +539,18 @@ def _run_terminal(command: str) -> None: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) + @staticmethod + def _reinstall_command() -> str: + from lib.python.variable import get_var + install_type = get_var("RETRO_INSTALL", "complete") + ricing = get_var("RETRO_RICING", "false") + type_filter = "-t core" if install_type == "minimal" else "-t all" + mode = "-m" if ricing == "true" else "-i" + return ( + f"retro {mode} existing -a root {type_filter} -y && " + f"retro {mode} existing -a user {type_filter} -y" + ) + # ── Lifecycle (read-only) ── def is_dirty(self) -> bool: From 70cb8e044f00615ff13d3db07d4f7cac4038076f Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:02:03 +0300 Subject: [PATCH 14/20] feat(update): make update save last stable branch --- cmds/system/update.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmds/system/update.sh b/cmds/system/update.sh index 3422dce..d26a8af 100755 --- a/cmds/system/update.sh +++ b/cmds/system/update.sh @@ -218,6 +218,11 @@ cmd_update() { rx_log "success" "Update finished" + if [[ $(rx_git_branch) == "main" && -n $new_release_tag ]]; then + $RETRO_DIR/retro.sh variable set RETRO_LAST_STABLE "$new_release_tag" >/dev/null 2>&1 || true + rx_log "success" "Last stable version updated to ${PINK}${new_release_tag}${RESET}" + fi + rx_log "info" "Restarting Retro daemon..." $RETRO_DIR/retro.sh daemon restart From b59c5a92044393e7e7de7afc1906e4029644eda4 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:02:26 +0300 Subject: [PATCH 15/20] feat(branch): add a branch management tool --- cmds/system/branch.sh | 128 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 cmds/system/branch.sh diff --git a/cmds/system/branch.sh b/cmds/system/branch.sh new file mode 100644 index 0000000..332f331 --- /dev/null +++ b/cmds/system/branch.sh @@ -0,0 +1,128 @@ +#!/bin/bash + +source "$RETRO_DIR/lib/help.sh" + +_var_get() { + bash "$RETRO_DIR/scripts/variable_core.sh" --get "$1" 2>/dev/null +} + +_var_set() { + bash "$RETRO_DIR/scripts/variable_core.sh" --set "$1" "$2" >/dev/null 2>&1 +} + +_stable_tag() { + local tag + tag=$(rx_git_run describe --tags --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null) + [[ -n $tag ]] && { echo "$tag"; return 0; } + tag=$(_var_get "RETRO_LAST_STABLE") + [[ -z $tag || $tag == "null" ]] && tag="" + [[ -n $tag ]] && { echo "$tag"; return 0; } + tag=$(rx_git_run describe --tags --abbrev=0 --match 'v[0-9]*' origin/main 2>/dev/null) + [[ -n $tag ]] && { echo "$tag"; return 0; } + tag=$(git -C "$RETRO_DIR" tag --list 'v[0-9]*' --sort=-v:refname 2>/dev/null | head -1) + echo "$tag" +} + +cmd_branch() { + local action="${1,,}" + shift 2>/dev/null || true + + case "$action" in + status) + local branch=$(rx_git_run rev-parse --abbrev-ref HEAD) + local tag=$(rx_git_run describe --tags --abbrev=0 --match 'v[0-9]*' HEAD) + echo "branch=${branch:-unknown}" + echo "version=${tag:-none}" + ;; + + list) + git -C "$RETRO_DIR" branch -a 2>/dev/null \ + | sed 's/^[* ] //' \ + | grep -v '^remotes/' \ + | sort -u + ;; + + switch) + local target="$1" + [[ -z $target ]] && { echo "result=error|reason=no_target"; return 1; } + + if [[ -n $(git -C "$RETRO_DIR" status --porcelain 2>/dev/null) ]]; then + git -C "$RETRO_DIR" reset --hard >/dev/null 2>&1 + fi + + git -C "$RETRO_DIR" fetch origin --tags >/dev/null 2>&1 \ + || { echo "result=error|reason=fetch_failed"; return 1; } + + git -C "$RETRO_DIR" rev-parse --verify "origin/$target" >/dev/null 2>&1 \ + || { echo "result=error|reason=branch_not_found|branch=$target"; return 1; } + + local stable + stable=$(_stable_tag) + + rx_log "info" "Switching to ${PINK}${target}${RESET} at ${PINK}${stable:-latest}${RESET}" + + if [[ -n $stable ]]; then + _var_set "RETRO_LAST_STABLE" "$stable" + if git -C "$RETRO_DIR" checkout -B "$target" "$stable" >/dev/null 2>&1; then + _var_set "RETRO_BRANCH" "$target" + rx_log "success" "On ${PINK}${target}${RESET} (${PINK}${stable}${RESET})" + echo "OK|$target" + return 0 + fi + echo "result=error|reason=checkout_failed|branch=$target" + return 1 + fi + + if git -C "$RETRO_DIR" checkout -B "$target" "origin/$target" >/dev/null 2>&1; then + _var_set "RETRO_BRANCH" "$target" + rx_log "success" "On ${PINK}${target}${RESET}" + echo "OK|$target" + return 0 + fi + echo "result=error|reason=checkout_failed|branch=$target" + return 1 + ;; + + updates) + local branch=$(rx_git_run rev-parse --abbrev-ref HEAD) + [[ -z $branch || $branch == "N/A" ]] && { echo "count=-1"; return 0; } + + git -C "$RETRO_DIR" fetch origin --tags >/dev/null 2>&1 || true + + if [[ $branch == "main" ]]; then + local cur=$(_stable_tag) + local count=0 + local tag + while IFS= read -r tag; do + [[ -z $tag ]] && continue + if [[ -n $cur && $tag == "$cur" ]]; then + continue + fi + if [[ -z $cur || "$(printf '%s\n%s' "$cur" "$tag" | sort -V | tail -1)" == "$tag" ]]; then + ((count++)) + fi + done < <(git -C "$RETRO_DIR" tag --list 'v*' --sort=v:refname --merged origin/main 2>/dev/null) + echo "count=$count" + else + local out + out=$(git -C "$RETRO_DIR" rev-list --count "HEAD..origin/$branch" 2>/dev/null) + echo "count=${out:-0}" + fi + ;; + + *) + rx_help_usage "retro -b " + rx_help_commands "Available commands" + rx_help_cmd "status" "Show current branch and release version" + rx_help_cmd "list" "List local branches" + rx_help_cmd "switch " "Switch branch to the last stable release" + rx_help_cmd "updates" "Count available updates" + rx_help_examples + rx_help_example "retro -b switch main" "Restore the last stable release on main" + rx_help_example "retro -b updates" "Show how many updates are available" + rx_help_spacer + ;; + esac +} + +register_command "SYSTEM" "-b|--branch" "Switch branches and check updates from the terminal" "cmd_branch" From ed4bf0b93bef999c152efd95804d1d7cafe95aac Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:11:04 +0300 Subject: [PATCH 16/20] feat(settings): add lazy loading for xdg autostart apps --- cmds/tools/settings/pages/autostart.py | 91 +++++++++++++++++++------- cmds/tools/settings/window.py | 5 +- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/cmds/tools/settings/pages/autostart.py b/cmds/tools/settings/pages/autostart.py index be665e2..8dc7a03 100644 --- a/cmds/tools/settings/pages/autostart.py +++ b/cmds/tools/settings/pages/autostart.py @@ -74,6 +74,8 @@ def __init__( self._scrolled: Gtk.ScrolledWindow self._retro_rows: list[Gtk.Widget] = [] self._xdg_group: Adw.PreferencesGroup | None = None + self._spinner_box: Gtk.Box | None = None + self._pending_loads = 0 self._reorder = RowReorderController( move=self._move_retro_item, iter_rows=lambda: self._retro_rows, @@ -91,23 +93,50 @@ def _load(self, saved_sections: dict[str, list[str]] | None = None) -> None: self._retro_saved = list(items) self._system_tasks: list[tuple[str, str]] = [] self._xdg_entries: list[XdgAutostartEntry] = [] + self._pending_loads = 2 + self._xdg_load_seq = getattr(self, "_xdg_load_seq", 0) + 1 self._xdg_async(lambda: (get_system_tasks(),), self._on_system_tasks_loaded) - self._xdg_async(lambda: (list_xdg_autostart(),), self._on_xdg_list_loaded) + self._xdg_async( + lambda: (self._xdg_load_seq, list_xdg_autostart()), self._on_xdg_list_loaded + ) def _on_system_tasks_loaded(self, tasks: list[tuple[str, str]]) -> None: self._system_tasks = tasks + self._load_step_done() + + def _on_xdg_list_loaded(self, seq: int, entries: list[XdgAutostartEntry]) -> None: + if seq == self._xdg_load_seq: + self._xdg_entries = entries + self._load_step_done() + + def _load_step_done(self) -> None: + self._pending_loads -= 1 + if self._pending_loads > 0: + return if self._content_box is None: return + if self._spinner_box is not None: + try: + self._content_box.remove(self._spinner_box) + except Exception: + pass + self._spinner_box = None self._rebuild_list() - def _on_xdg_list_loaded(self, entries: list[XdgAutostartEntry]) -> None: - self._xdg_entries = entries - if self._content_box is None: - return - if self._xdg_group is None: - self._rebuild_list() - else: - self._refresh_xdg_group(entries) + def _build_spinner(self) -> Gtk.Box: + spinner_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) + spinner_box.set_valign(Gtk.Align.CENTER) + spinner_box.set_halign(Gtk.Align.CENTER) + spinner_box.set_margin_top(48) + spinner = Gtk.Spinner() + spinner.set_size_request(32, 32) + spinner.start() + spinner_box.append(spinner) + lbl = Gtk.Label(label="Loading autostart entries\u2026") + lbl.add_css_class("dim-label") + spinner_box.append(lbl) + self._spinner_box = spinner_box + return spinner_box # ── Build ── @@ -126,7 +155,10 @@ def build(self, header: Adw.HeaderBar | None = None) -> Adw.ToolbarView: toolbar_view, _, self._content_box, self._scrolled = make_page_layout(header=page_header) - self._rebuild_list() + if self._pending_loads > 0: + self._content_box.append(self._build_spinner()) + else: + self._rebuild_list() return toolbar_view # ── List rendering ── @@ -174,9 +206,12 @@ def _build_custom_group(self) -> list[Gtk.Widget]: def _build_xdg_group(self) -> list[Gtk.Widget]: if not self._xdg_entries: return [] + self._xdg_group = self._make_xdg_group_widget(self._xdg_entries) + return [self._xdg_group] + def _make_xdg_group_widget(self, entries: list[XdgAutostartEntry]) -> Adw.PreferencesGroup: group = Adw.PreferencesGroup(title="Application Autostart") - n = len(self._xdg_entries) + n = len(entries) group.set_description( f"{n} app{'s' if n != 1 else ''} launching at login" ) @@ -188,10 +223,9 @@ def _build_xdg_group(self) -> list[Gtk.Widget]: clean_btn.connect("clicked", lambda _b: self._on_clean_xdg()) group.set_header_suffix(clean_btn) - self._xdg_group = group - for entry in self._xdg_entries: + for entry in entries: group.add(self._make_xdg_row(entry)) - return [group] + return group def _make_xdg_row(self, entry: XdgAutostartEntry) -> Adw.SwitchRow: scope = "System" if entry.scope == "system" else "User" @@ -201,7 +235,7 @@ def _make_xdg_row(self, entry: XdgAutostartEntry) -> Adw.SwitchRow: row = Adw.SwitchRow(title=html_escape(entry.name), subtitle=subtitle) row.set_active(entry.enabled) - row.connect("notify::active", lambda r, e=entry: self._on_xdg_toggle(r, e)) + row.connect("notify::active", lambda r, _pspec, e=entry: self._on_xdg_toggle(r, e)) if not entry.binary_exists: row.add_css_class("option-default") row.set_opacity(0.7) @@ -217,6 +251,7 @@ def _make_xdg_row(self, entry: XdgAutostartEntry) -> Adw.SwitchRow: def _on_xdg_toggle(self, row: Adw.SwitchRow, entry: XdgAutostartEntry) -> None: action = "enable" if row.get_active() else "disable" desktop = os.path.basename(entry.path) + self._xdg_load_seq += 1 def worker() -> tuple[bool, list[XdgAutostartEntry]]: return toggle_xdg_autostart(desktop, action), list_xdg_autostart() @@ -230,6 +265,7 @@ def _on_xdg_toggle_done(self, row: Adw.SwitchRow, action: str, ok: bool, entries def _on_delete_xdg(self, entry: XdgAutostartEntry) -> None: desktop = os.path.basename(entry.path) + self._xdg_load_seq += 1 def worker() -> tuple[bool, list[XdgAutostartEntry]]: return delete_xdg_autostart(desktop), list_xdg_autostart() @@ -244,6 +280,8 @@ def _on_xdg_delete_done(self, entry: XdgAutostartEntry, ok: bool, entries: list[ self._refresh_xdg_group(entries) def _on_clean_xdg(self) -> None: + self._xdg_load_seq += 1 + def worker() -> tuple[int, list[XdgAutostartEntry]]: return clean_xdg_autostart(), list_xdg_autostart() @@ -273,17 +311,22 @@ def _dispatch_xdg_result(on_done, on_done_args, result) -> bool: return False def _refresh_xdg_group(self, entries: list[XdgAutostartEntry] | None = None) -> None: - if self._xdg_group is None: - return if entries is None: entries = list_xdg_autostart() - n = len(entries) - self._xdg_group.set_description( - f"{n} app{'s' if n != 1 else ''} launching at login" - ) - clear_children(self._xdg_group) - for entry in entries: - self._xdg_group.add(self._make_xdg_row(entry)) + self._xdg_entries = entries + + if self._xdg_group is None or self._content_box is None: + self._rebuild_list() + return + + new_group = self._make_xdg_group_widget(entries) + prev = self._xdg_group.get_prev_sibling() + self._content_box.remove(self._xdg_group) + if prev is not None: + self._content_box.insert_child_after(new_group, prev) + else: + self._content_box.prepend(new_group) + self._xdg_group = new_group def _rebuild_list(self) -> None: clear_children(self._content_box) diff --git a/cmds/tools/settings/window.py b/cmds/tools/settings/window.py index 9ada5da..febf8f3 100644 --- a/cmds/tools/settings/window.py +++ b/cmds/tools/settings/window.py @@ -536,18 +536,15 @@ def _build_pages(self) -> tuple[list[dict], dict[str, dict]]: (BindsPage, "_binds_page", "binds", "Keybinds"), (MonitorsPage, "_monitors_page", "monitors", "Monitors"), (WorkspacesPage, "_workspaces_page", "workspaces", "Workspaces"), - (AutostartPage, "_autostart_page", "autostart", "Autostart"), (EnvVarsPage, "_env_vars_page", "env_vars", "Env Variables"), ] for cls, attr, slug, title in section_page_specs: self._lazy_section_specs[slug] = (cls, attr, title) - # Window rules and layer rules are eagerly built at startup so their - # state (parsed config, external rules) is always loaded — lazy - # construction would discard already-tracked rules on page navigation. for cls, attr, slug, title in [ (WindowRulesPage, "_window_rules_page", "window_rules", "Window Rules"), (LayerRulesPage, "_layer_rules_page", "layer_rules", "Layer Rules"), + (AutostartPage, "_autostart_page", "autostart", "Autostart"), ]: page = cls(self, on_dirty_changed=self._on_section_dirty, push_undo=self._undo.push, saved_sections=self.saved_sections) setattr(self, attr, page) From e4872020a7106572de89b63d02370e186326f261 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 00:13:28 +0300 Subject: [PATCH 17/20] fix(settings): small fix for daemon and logs pages --- cmds/tools/settings/pages/daemon.py | 2 +- cmds/tools/settings/pages/logs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmds/tools/settings/pages/daemon.py b/cmds/tools/settings/pages/daemon.py index fa8a85c..9ef64c4 100644 --- a/cmds/tools/settings/pages/daemon.py +++ b/cmds/tools/settings/pages/daemon.py @@ -466,7 +466,7 @@ def rebuild(*_args): power_sw = Gtk.Switch() power_sw.set_valign(Gtk.Align.CENTER) power_sw.set_active(not w["disabled"]) - power_sw.connect("notify::active", lambda _sw, n=w["name"], d=w["disabled"]: self._toggle_watcher(n, _sw.get_active())) + power_sw.connect("notify::active", lambda _sw, _pspec, n=w["name"], d=w["disabled"]: self._toggle_watcher(n, _sw.get_active())) if w["log"]: expander = Adw.ExpanderRow() diff --git a/cmds/tools/settings/pages/logs.py b/cmds/tools/settings/pages/logs.py index 4050aaf..86fef5b 100644 --- a/cmds/tools/settings/pages/logs.py +++ b/cmds/tools/settings/pages/logs.py @@ -272,7 +272,7 @@ def on_expand(e, _pspec, name=log["name"]): power_sw = Gtk.Switch() power_sw.set_valign(Gtk.Align.CENTER) power_sw.set_active(not log["disabled"]) - power_sw.connect("notify::active", lambda sw, n=log["name"]: self._toggle_log(n, sw.get_active())) + power_sw.connect("notify::active", lambda sw, _pspec, n=log["name"]: self._toggle_log(n, sw.get_active())) expander.add_suffix(power_sw) clear_btn = Gtk.Button(icon_name="user-trash-symbolic") From 4fa4b948fcf41d1684bb5288600d353ea0de4f5e Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 01:51:01 +0300 Subject: [PATCH 18/20] feat(bin): add retro select verion on post --- bin/post/clone.sh | 88 +++++++++++++++++++++++++++++++++++++------- bin/setup/network.sh | 71 +++++++++++++++++++---------------- 2 files changed, 115 insertions(+), 44 deletions(-) diff --git a/bin/post/clone.sh b/bin/post/clone.sh index 18d80f2..3df9986 100755 --- a/bin/post/clone.sh +++ b/bin/post/clone.sh @@ -43,26 +43,88 @@ rx_post_clone_repo() { return 1 fi - if git ls-remote --exit-code --heads "$RETRO_REPO_URL" "refs/heads/$RETRO_BRANCH" >/dev/null 2>&1; then - if git clone --branch "$RETRO_BRANCH" "$RETRO_REPO_URL" "$target_dir"; then - find "$target_dir" -type f -name "*.sh" -exec chmod 755 {} \; - gum style --foreground 2 "Successfully cloned ${RETRO_BRANCH} from GitHub" - return 0 - fi + local branch_args=() + if timeout 30 git ls-remote --exit-code --heads "$RETRO_REPO_URL" "refs/heads/$RETRO_BRANCH" >/dev/null 2>&1; then + branch_args=(--branch "$RETRO_BRANCH") else gum style --foreground 3 "Branch ${RETRO_BRANCH} not found, cloning default branch..." - if git clone "$RETRO_REPO_URL" "$target_dir"; then - find "$target_dir" -type f -name "*.sh" -exec chmod 755 {} \; - gum style --foreground 2 "Successfully cloned default branch from GitHub" - return 0 + fi + + local clone_ok=false + if [[ "$RETRO_BRANCH" == "develop" ]]; then + if timeout 600 git clone --single-branch --depth 1 "${branch_args[@]}" "$RETRO_REPO_URL" "$target_dir"; then + clone_ok=true + fi + else + if timeout 600 git clone --single-branch "${branch_args[@]}" "$RETRO_REPO_URL" "$target_dir"; then + clone_ok=true fi fi + if [[ $clone_ok == true ]]; then + find "$target_dir" -type f -name "*.sh" -exec chmod 755 {} \; + local actual_branch + actual_branch=$(git -C "$target_dir" branch --show-current 2>/dev/null) + rx_select_retro_version "$target_dir" "${actual_branch:-$RETRO_BRANCH}" + gum style --foreground 2 "Successfully cloned from GitHub (${actual_branch:-${RETRO_BRANCH:-default}})" + return 0 + fi + gum style --foreground 1 "ERROR: Failed to clone repository." return 1 fi } +rx_select_retro_version() { + local target_dir="$1" + local branch="${2:-develop}" + + if [[ "$branch" == "develop" ]]; then + return 0 + fi + + local versions + versions=$(git -C "$target_dir" tag -l 2>/dev/null \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sed 's/^v//' \ + | awk -F. ' + /^[0-9]+\.[0-9]+\.[0-9]+$/ { + key = $1 "." $2 + if (!(key in max) || $3 > max[key]) { max[key] = $3; ver[key] = "v" $0 } + } + END { for (k in ver) print ver[k] } + ' \ + | sort -Vr) + + if [[ -z $versions ]]; then + gum style --foreground 3 "No release versions found, staying on ${branch}" + return 0 + fi + + local -a options=() + local v + while IFS= read -r v; do + [[ -n $v ]] && options+=("$v") + done <<< "$versions" + + local choice="" + if [[ -t 0 ]]; then + rx_clear_logo + choice=$(gum choose --header "Select RetroLinux version (${branch})" "${options[@]}") + fi + + if [[ -z $choice ]]; then + gum style --foreground 3 "No version selected, staying on ${branch}" + return 0 + fi + + if git -C "$target_dir" checkout "$choice" 2>/dev/null; then + gum style --foreground 2 "Checked out ${choice}" + else + gum style --foreground 1 "Failed to checkout ${choice}, staying on ${branch}" + fi +} + rx_install_retro_bootstrap() { rx_clear_logo rx_step "Bootstrapping first-boot setup..." @@ -72,13 +134,13 @@ rx_install_retro_bootstrap() { return 0 fi - local username=$(arch-chroot /mnt getent passwd 1000 2>/dev/null | cut -d: -f1) + local username=$(timeout 30 arch-chroot /mnt getent passwd 1000 2>/dev/null | cut -d: -f1) local home_dir="" - [[ -n $username ]] && home_dir=$(arch-chroot /mnt getent passwd "$username" 2>/dev/null | cut -d: -f6) + [[ -n $username ]] && home_dir=$(timeout 30 arch-chroot /mnt getent passwd "$username" 2>/dev/null | cut -d: -f6) if [[ -n $username && -n $home_dir ]]; then gum style --foreground 7 "Creating first-boot autostart script..." - arch-chroot /mnt bash -c " + timeout 60 arch-chroot /mnt bash -c " mkdir -p '$home_dir/.config/hypr' autostart_file='$home_dir/.config/hypr/autostart.sh' cat > \$autostart_file <<'EOF' diff --git a/bin/setup/network.sh b/bin/setup/network.sh index 580a4e8..be69770 100755 --- a/bin/setup/network.sh +++ b/bin/setup/network.sh @@ -5,28 +5,21 @@ source "$RETRO_INSTALL/lib/setup_lib.sh" setup_network() { rx_load_state rx_clear_logo - echo - gum style "Network connectivity check" - echo - - rx_check_internet && { - rx_clear_logo - echo - gum style --foreground 2 "Internet connected" - echo - return 0 - } + rx_step "Configuring network..." - if ! rx_check_network_hardware; then - return 1 - fi + if gum confirm --affirmative "WiFi" --negative "Ethernet" "Select your network type" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + local wifi_iface + wifi_iface=$(rx_get_wifi_iface) - local wifi_iface - wifi_iface=$(rx_get_wifi_iface) - local eth_iface - eth_iface=$(rx_get_ethernet_iface) + if [[ -z $wifi_iface ]]; then + rx_clear_logo + echo + gum style --foreground 1 "No WiFi adapter detected" + echo + rx_retry_or_exit "WiFi unavailable" || rx_abort + return 1 + fi - if [[ -n $wifi_iface ]]; then local retry=0 local max_retries=5 while ((retry < max_retries)); do @@ -37,6 +30,10 @@ setup_network() { NETWORK_TYPE="WiFi" WIFI_SSID="$WIFI_SELECTED_SSID" rx_save_state + rx_clear_logo + echo + gum style --foreground 2 "WiFi connected: ${WIFI_SSID}" + echo return 0 elif ((result == 2)); then ((retry++)) @@ -58,28 +55,40 @@ setup_network() { fi fi done + + rx_clear_logo + echo + gum style --foreground 1 "Network unavailable" + echo + return 1 fi + local eth_iface + eth_iface=$(rx_get_ethernet_iface) if [[ -n $eth_iface ]]; then - if rx_wait_for_ethernet "$eth_iface" && rx_check_internet; then - NETWORK_TYPE="Ethernet" - WIFI_SSID="" - rx_save_state - rx_clear_logo - echo - gum style --foreground 2 "Ethernet connected" - echo - return 0 - fi + ip link set "$eth_iface" up 2>/dev/null + fi + + if rx_check_internet || (rx_wait_for_ethernet "$eth_iface" && rx_check_internet); then + NETWORK_TYPE="Ethernet" + WIFI_SSID="" + WIFI_PASSWORD="" + rx_save_state + rx_clear_logo + echo + gum style --foreground 2 "Ethernet connected" + echo + return 0 fi rx_clear_logo echo - gum style --foreground 1 "Network unavailable" + gum style --foreground 1 "Ethernet connection failed" echo + rx_retry_or_exit "Ethernet unavailable" || rx_abort return 1 } if ! setup_network; then rx_setup_fail "Network" -fi \ No newline at end of file +fi From 016c077b272b76d702e3f8470f52a70d8d453489 Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 01:51:16 +0300 Subject: [PATCH 19/20] chore(bin): make network step be after browser --- bin/retroinstall | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/retroinstall b/bin/retroinstall index 56cbb2e..3c53439 100644 --- a/bin/retroinstall +++ b/bin/retroinstall @@ -42,7 +42,6 @@ SETUP_SCRIPTS=( "kernel.sh" "boot.sh" "mirrors.sh" - "network.sh" "bluetooth.sh" "fingerprint.sh" "print.sh" @@ -51,6 +50,7 @@ SETUP_SCRIPTS=( "editor.sh" "filemanager.sh" "browser.sh" + "network.sh" "config.sh" ) From 4d762269de713dd22bcdcc1427922cccb8d84e3d Mon Sep 17 00:00:00 2001 From: itsvlxd Date: Sat, 15 Aug 2026 14:06:22 +0300 Subject: [PATCH 20/20] fix(bin): fix retroinstall not load previous states --- bin/lib/errors.sh | 5 +- bin/lib/handlers.sh | 102 ++++++++++++++++++++--------------------- bin/setup/bluetooth.sh | 2 +- bin/setup/boot.sh | 6 +-- bin/setup/branch.sh | 2 +- bin/setup/browser.sh | 9 +++- bin/setup/display.sh | 6 +-- bin/setup/hostname.sh | 2 +- bin/setup/install.sh | 2 +- bin/setup/keyboard.sh | 2 +- bin/setup/locale.sh | 13 +++++- bin/setup/luks.sh | 10 ++-- bin/setup/mirrors.sh | 2 +- bin/setup/network.sh | 56 ++++++++++++++-------- bin/setup/print.sh | 2 +- bin/setup/ricing.sh | 2 +- bin/setup/root.sh | 6 +-- bin/setup/ssh.sh | 10 ++-- bin/setup/timezone.sh | 2 +- bin/setup/user.sh | 8 ++-- 20 files changed, 144 insertions(+), 105 deletions(-) diff --git a/bin/lib/errors.sh b/bin/lib/errors.sh index c265a24..630bea7 100755 --- a/bin/lib/errors.sh +++ b/bin/lib/errors.sh @@ -7,9 +7,10 @@ rx_retry_or_exit() { gum style --foreground 1 --padding "1 0 1 $PADDING_LEFT" "$message" echo if gum confirm --negative "Exit" --affirmative "Retry" "Retry?" --padding "$GUM_CONFIRM_PADDING"; then - return 0 + exec "${RETRO_INSTALL:-/opt/retrolinux/bin}/retroinstall" fi - return 1 + gum style "Run 'retroinstall' to try again" + exit 1 } rx_step_error() { diff --git a/bin/lib/handlers.sh b/bin/lib/handlers.sh index 54d09d0..1d25784 100755 --- a/bin/lib/handlers.sh +++ b/bin/lib/handlers.sh @@ -148,57 +148,57 @@ rx_setup_traps() { # Global state - always load on module source rx_save_state() { - cat < "$RETRO_STATE" -KEYBOARD="$KEYBOARD" -SYS_LANG="$SYS_LANG" -SYS_ENC="$SYS_ENC" -USER_PASSWORD="$USER_PASSWORD" -USER_NAME="$USER_NAME" -USER_HOSTNAME="$USER_HOSTNAME" -USER_TIMEZONE="$USER_TIMEZONE" -DISK_SELECTED="$DISK_SELECTED" -NETWORK_TYPE="$NETWORK_TYPE" -WIFI_SSID="$WIFI_SSID" -WIFI_PASSWORD="$WIFI_PASSWORD" -ROOT_PASSWORD="$ROOT_PASSWORD" -USER_SUDO="$USER_SUDO" -LUKS_ENABLED="$LUKS_ENABLED" -LUKS_PASSWORD="$LUKS_PASSWORD" -LUKS_ITER_TIME="$LUKS_ITER_TIME" -KERNEL_SELECTION="$KERNEL_SELECTION" -BLUETOOTH_ENABLED="$BLUETOOTH_ENABLED" -PRINT_SERVICE_ENABLED="$PRINT_SERVICE_ENABLED" -CUSTOM_MIRRORS="$CUSTOM_MIRRORS" -MIRROR_REGIONS="$MIRROR_REGIONS" -RX_CURRENT_STEP="$RX_CURRENT_STEP" -RX_START_STEP="${RX_START_STEP:-1}" -RX_SKIP_STEP="$RX_SKIP_STEP" -RX_GO_BACK_TO="$RX_GO_BACK_TO" -SSH_ENABLED="$SSH_ENABLED" -SSH_PORT="$SSH_PORT" -SSH_PASSWORD_LOGIN="$SSH_PASSWORD_LOGIN" -SSH_KEY_LOGIN="$SSH_KEY_LOGIN" -SSH_ROOT_LOGIN="$SSH_ROOT_LOGIN" -GRUB_THEME_CHOICE="$GRUB_THEME_CHOICE" -BOOT_VIDEO_GRUB="$BOOT_VIDEO_GRUB" -GRUB_OS_PROBER="$GRUB_OS_PROBER" -DISPLAY_ASPECT_RATIO="$DISPLAY_ASPECT_RATIO" -DISPLAY_RES_X="$DISPLAY_RES_X" -DISPLAY_RES_Y="$DISPLAY_RES_Y" -AUR_HELPER="$AUR_HELPER" -EDITOR_CHOICE="$EDITOR_CHOICE" -INSTALL_TYPE="$INSTALL_TYPE" -FILEMANAGER_CHOICE="$FILEMANAGER_CHOICE" -BROWSER_CHOICE="$BROWSER_CHOICE" -WALLPAPER_RES="$WALLPAPER_RES" -FINGERPRINT_ENABLED="$FINGERPRINT_ENABLED" -FIREWALL_ENGINE="$FIREWALL_ENGINE" -RICE_MODE="$RICE_MODE" -RETRO_BRANCH="$RETRO_BRANCH" -GRUB_SNAPSHOTS_ENABLED="$GRUB_SNAPSHOTS_ENABLED" -GRUB_TIMEOUT="$GRUB_TIMEOUT" -GRUB_KERNEL="$GRUB_KERNEL" -EOF + { + printf 'KEYBOARD=%q\n' "$KEYBOARD" + printf 'SYS_LANG=%q\n' "$SYS_LANG" + printf 'SYS_ENC=%q\n' "$SYS_ENC" + printf 'USER_PASSWORD=%q\n' "$USER_PASSWORD" + printf 'USER_NAME=%q\n' "$USER_NAME" + printf 'USER_HOSTNAME=%q\n' "$USER_HOSTNAME" + printf 'USER_TIMEZONE=%q\n' "$USER_TIMEZONE" + printf 'DISK_SELECTED=%q\n' "$DISK_SELECTED" + printf 'NETWORK_TYPE=%q\n' "$NETWORK_TYPE" + printf 'WIFI_SSID=%q\n' "$WIFI_SSID" + printf 'WIFI_PASSWORD=%q\n' "$WIFI_PASSWORD" + printf 'ROOT_PASSWORD=%q\n' "$ROOT_PASSWORD" + printf 'USER_SUDO=%q\n' "$USER_SUDO" + printf 'LUKS_ENABLED=%q\n' "$LUKS_ENABLED" + printf 'LUKS_PASSWORD=%q\n' "$LUKS_PASSWORD" + printf 'LUKS_ITER_TIME=%q\n' "$LUKS_ITER_TIME" + printf 'KERNEL_SELECTION=%q\n' "$KERNEL_SELECTION" + printf 'BLUETOOTH_ENABLED=%q\n' "$BLUETOOTH_ENABLED" + printf 'PRINT_SERVICE_ENABLED=%q\n' "$PRINT_SERVICE_ENABLED" + printf 'CUSTOM_MIRRORS=%q\n' "$CUSTOM_MIRRORS" + printf 'MIRROR_REGIONS=%q\n' "$MIRROR_REGIONS" + printf 'RX_CURRENT_STEP=%q\n' "$RX_CURRENT_STEP" + printf 'RX_START_STEP=%q\n' "${RX_START_STEP:-1}" + printf 'RX_SKIP_STEP=%q\n' "$RX_SKIP_STEP" + printf 'RX_GO_BACK_TO=%q\n' "$RX_GO_BACK_TO" + printf 'SSH_ENABLED=%q\n' "$SSH_ENABLED" + printf 'SSH_PORT=%q\n' "$SSH_PORT" + printf 'SSH_PASSWORD_LOGIN=%q\n' "$SSH_PASSWORD_LOGIN" + printf 'SSH_KEY_LOGIN=%q\n' "$SSH_KEY_LOGIN" + printf 'SSH_ROOT_LOGIN=%q\n' "$SSH_ROOT_LOGIN" + printf 'GRUB_THEME_CHOICE=%q\n' "$GRUB_THEME_CHOICE" + printf 'BOOT_VIDEO_GRUB=%q\n' "$BOOT_VIDEO_GRUB" + printf 'GRUB_OS_PROBER=%q\n' "$GRUB_OS_PROBER" + printf 'DISPLAY_ASPECT_RATIO=%q\n' "$DISPLAY_ASPECT_RATIO" + printf 'DISPLAY_RES_X=%q\n' "$DISPLAY_RES_X" + printf 'DISPLAY_RES_Y=%q\n' "$DISPLAY_RES_Y" + printf 'AUR_HELPER=%q\n' "$AUR_HELPER" + printf 'EDITOR_CHOICE=%q\n' "$EDITOR_CHOICE" + printf 'INSTALL_TYPE=%q\n' "$INSTALL_TYPE" + printf 'FILEMANAGER_CHOICE=%q\n' "$FILEMANAGER_CHOICE" + printf 'BROWSER_CHOICE=%q\n' "$BROWSER_CHOICE" + printf 'WALLPAPER_RES=%q\n' "$WALLPAPER_RES" + printf 'FINGERPRINT_ENABLED=%q\n' "$FINGERPRINT_ENABLED" + printf 'FIREWALL_ENGINE=%q\n' "$FIREWALL_ENGINE" + printf 'RICE_MODE=%q\n' "$RICE_MODE" + printf 'RETRO_BRANCH=%q\n' "$RETRO_BRANCH" + printf 'GRUB_SNAPSHOTS_ENABLED=%q\n' "$GRUB_SNAPSHOTS_ENABLED" + printf 'GRUB_TIMEOUT=%q\n' "$GRUB_TIMEOUT" + printf 'GRUB_KERNEL=%q\n' "$GRUB_KERNEL" + } > "$RETRO_STATE" } rx_load_state() { diff --git a/bin/setup/bluetooth.sh b/bin/setup/bluetooth.sh index e146573..021f2e8 100755 --- a/bin/setup/bluetooth.sh +++ b/bin/setup/bluetooth.sh @@ -6,7 +6,7 @@ setup_bluetooth() { rx_load_state rx_step "Let's setup Bluetooth..." - if gum confirm --affirmative "Yes, enable Bluetooth" --negative "No, skip Bluetooth" "Bluetooth Service" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable Bluetooth" --negative "No, skip Bluetooth" "Bluetooth Service" --default="${BLUETOOTH_ENABLED:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then # shellcheck disable=SC2034 BLUETOOTH_ENABLED="true" else diff --git a/bin/setup/boot.sh b/bin/setup/boot.sh index f250b3b..f3f55db 100755 --- a/bin/setup/boot.sh +++ b/bin/setup/boot.sh @@ -95,7 +95,7 @@ Custom (Enter manually)" rx_step "Let's configure your bootloader..." local custom_res - custom_res=$(gum input --placeholder "1920x1080" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Resolution (WxH)> " --padding "$GUM_INPUT_PADDING") || { + custom_res=$(gum input --placeholder "1920x1080" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Resolution (WxH)> " --value "$BOOT_VIDEO_GRUB" --padding "$GUM_INPUT_PADDING") || { selected_resolution="$BOOT_VIDEO_GRUB" } [[ -n $custom_res ]] && selected_resolution="$custom_res" @@ -109,7 +109,7 @@ Custom (Enter manually)" gum style --foreground 7 "Detect and add other operating systems (Windows, other Linux distros)" echo - if gum confirm --affirmative "Yes, enable OS probing" --negative "No, skip OS probing" "OS Probing" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable OS probing" --negative "No, skip OS probing" "OS Probing" --default="${GRUB_OS_PROBER:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then GRUB_OS_PROBER="true" else GRUB_OS_PROBER="false" @@ -121,7 +121,7 @@ Custom (Enter manually)" gum style --foreground 7 "Include BTRFS snapshots in GRUB boot menu for easy rollback" echo - if gum confirm --affirmative "Yes, enable snapshots" --negative "No, disable snapshots" "Snapshot Boot" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable snapshots" --negative "No, disable snapshots" "Snapshot Boot" --default="${GRUB_SNAPSHOTS_ENABLED:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then GRUB_SNAPSHOTS_ENABLED="true" else GRUB_SNAPSHOTS_ENABLED="false" diff --git a/bin/setup/branch.sh b/bin/setup/branch.sh index 3850367..6947a57 100755 --- a/bin/setup/branch.sh +++ b/bin/setup/branch.sh @@ -15,7 +15,7 @@ setup_branch() { gum style --foreground 7 --padding "0 0 0 $PADDING_LEFT" "Latest development changes, updated continuously. Best for testing." gum style --foreground 7 --padding "0 0 1 $PADDING_LEFT" "Recommended while Retro Linux is still in development." - if gum confirm --affirmative "Stable (main)" --negative "Rolling (develop)" "Select release branch" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Stable (main)" --negative "Rolling (develop)" "Select release branch" --default="$([[ $RETRO_BRANCH == "main" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then RETRO_BRANCH="main" else RETRO_BRANCH="develop" diff --git a/bin/setup/browser.sh b/bin/setup/browser.sh index 3ba5b81..403eea7 100755 --- a/bin/setup/browser.sh +++ b/bin/setup/browser.sh @@ -7,7 +7,14 @@ rx_setup_browser() { local display_options=("Firefox" "Zen" "Chromium" "None (skip browser)") local pkg_options=("firefox" "zen-browser-bin" "chromium" "none") - local selection=$(gum choose --header "Select browser to install:" --padding "$GUM_CHOOSE_PADDING" "${display_options[@]}") + local current_browser="Firefox" + case "$BROWSER_CHOICE" in + firefox) current_browser="Firefox" ;; + zen-browser-bin) current_browser="Zen" ;; + chromium) current_browser="Chromium" ;; + none) current_browser="None (skip browser)" ;; + esac + local selection=$(gum choose --selected "$current_browser" --header "Select browser to install:" --padding "$GUM_CHOOSE_PADDING" "${display_options[@]}") if [[ -z "$selection" ]]; then gum style --foreground 3 "No browser selected, skipping" diff --git a/bin/setup/display.sh b/bin/setup/display.sh index 0d1c89b..568be0b 100755 --- a/bin/setup/display.sh +++ b/bin/setup/display.sh @@ -79,7 +79,7 @@ Custom (Enter manually)" rx_step "Let's configure your display settings..." local custom_ratio - custom_ratio=$(gum input --placeholder "16:9, 16:10, 21:9, 32:9, etc." --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Aspect Ratio> " --padding "$GUM_INPUT_PADDING") || { + custom_ratio=$(gum input --placeholder "16:9, 16:10, 21:9, 32:9, etc." --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Aspect Ratio> " --value "$DISPLAY_ASPECT_RATIO" --padding "$GUM_INPUT_PADDING") || { DISPLAY_ASPECT_RATIO="16:9" } [[ -n $custom_ratio ]] && DISPLAY_ASPECT_RATIO="$custom_ratio" @@ -151,7 +151,7 @@ $resolutions" [[ -n $BOOT_VIDEO_GRUB ]] && current_res="$BOOT_VIDEO_GRUB" local display_res - display_res=$(echo "$resolutions" | gum choose --height 10 --header "Select display resolution" --padding "$GUM_CHOOSE_PADDING") || { + display_res=$(echo "$resolutions" | gum choose --height 10 --selected "$current_res" --header "Select display resolution" --padding "$GUM_CHOOSE_PADDING") || { rx_step_error "2" "Resolution selection failed" rx_retry_or_exit "Display configuration required" || rx_abort } @@ -170,7 +170,7 @@ $resolutions" rx_step "Let's configure your display settings..." local custom_res - custom_res=$(gum input --placeholder "1920x1080" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Resolution (WxH)> " --padding "$GUM_INPUT_PADDING") || { + custom_res=$(gum input --placeholder "1920x1080" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Resolution (WxH)> " --value "$BOOT_VIDEO_GRUB" --padding "$GUM_INPUT_PADDING") || { display_res="1920x1080" } [[ -n $custom_res ]] && display_res="$custom_res" diff --git a/bin/setup/hostname.sh b/bin/setup/hostname.sh index 5766740..e3a5096 100755 --- a/bin/setup/hostname.sh +++ b/bin/setup/hostname.sh @@ -8,7 +8,7 @@ setup_hostname() { while true; do local hostname - hostname=$(gum input --placeholder "Please set the hostname for your computer" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Hostname> " --value "retrolinux" --padding "$GUM_INPUT_PADDING") || { + hostname=$(gum input --placeholder "Please set the hostname for your computer" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Hostname> " --value "${USER_HOSTNAME:-retrolinux}" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Hostname input failed" rx_retry_or_exit "Hostname is required" || rx_abort return 1 diff --git a/bin/setup/install.sh b/bin/setup/install.sh index 9ebb5ba..ef18edb 100755 --- a/bin/setup/install.sh +++ b/bin/setup/install.sh @@ -15,7 +15,7 @@ setup_install() { gum style --foreground 7 --padding "0 0 0 $PADDING_LEFT" "Installs only core system modules: essential services, package" gum style --foreground 7 --padding "0 0 1 $PADDING_LEFT" "management, and basic desktop functionality. Lightweight setup." - if gum confirm --affirmative "Complete install" --negative "Minimal install" "Select Installation Type" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Complete install" --negative "Minimal install" "Select Installation Type" --default="$([[ $INSTALL_TYPE == "complete" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then INSTALL_TYPE="complete" else INSTALL_TYPE="minimal" diff --git a/bin/setup/keyboard.sh b/bin/setup/keyboard.sh index 856b307..c5dd4c6 100755 --- a/bin/setup/keyboard.sh +++ b/bin/setup/keyboard.sh @@ -57,7 +57,7 @@ Turkish Ukrainian' local choice - choice=$(printf '%s\n' "$keyboards" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --prompt "Keyboard> " --placeholder "Please select your keyboard layout" --padding "$GUM_FILTER_PADDING") || { + choice=$(printf '%s\n' "$keyboards" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --value "$(rx_keyboard_display_name "$KEYBOARD")" --prompt "Keyboard> " --placeholder "Please select your keyboard layout" --padding "$GUM_FILTER_PADDING") || { rx_step_error "1" "Keyboard selection cancelled" rx_retry_or_exit "Keyboard selection is required" || rx_abort return 1 diff --git a/bin/setup/locale.sh b/bin/setup/locale.sh index e79725c..53f4844 100755 --- a/bin/setup/locale.sh +++ b/bin/setup/locale.sh @@ -12,8 +12,19 @@ setup_locale() { done lang_list=$(echo "$lang_list" | sort | uniq) + local current_lang="" + if [[ -n $SYS_LANG ]]; then + local saved_code="${SYS_LANG%%.*}" + for code in "${!LOCALE_LANG_NAMES[@]}"; do + if [[ "$code" == "$saved_code" ]]; then + current_lang="${LOCALE_LANG_NAMES[$code]}" + break + fi + done + fi + local choice - choice=$(echo "$lang_list" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --prompt "Language> " --placeholder "Please select your system language" --padding "$GUM_FILTER_PADDING") || { + choice=$(echo "$lang_list" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --value "$current_lang" --prompt "Language> " --placeholder "Please select your system language" --padding "$GUM_FILTER_PADDING") || { rx_step_error "1" "Language selection cancelled" rx_retry_or_exit "Language selection is required" || rx_abort return 1 diff --git a/bin/setup/luks.sh b/bin/setup/luks.sh index 5fc9e80..31387be 100755 --- a/bin/setup/luks.sh +++ b/bin/setup/luks.sh @@ -6,13 +6,13 @@ setup_luks() { rx_load_state rx_step "Let's setup disk encryption..." - if gum confirm --affirmative "Yes, enable encryption" --negative "No, skip encryption" "LUKS Encryption" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable encryption" --negative "No, skip encryption" "LUKS Encryption" --default="${LUKS_ENABLED:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then LUKS_ENABLED="true" local use_same_password="false" if [[ -n $USER_PASSWORD ]]; then - if gum confirm --affirmative "Yes, use same password" --negative "No, enter different password" "Would you like to configure LUKS with the same password used for ${USER_NAME}?" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, use same password" --negative "No, enter different password" "Would you like to configure LUKS with the same password used for ${USER_NAME}?" --default="$([[ -n $LUKS_PASSWORD && $LUKS_PASSWORD == "$USER_PASSWORD" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then LUKS_PASSWORD="$USER_PASSWORD" use_same_password="true" fi @@ -21,13 +21,13 @@ setup_luks() { if [[ $use_same_password != "true" ]]; then while true; do local password - password=$(gum input --placeholder "Create a LUKS encryption password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "LUKS Password> " --padding "$GUM_INPUT_PADDING") || { + password=$(gum input --placeholder "Create a LUKS encryption password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "LUKS Password> " --value "$LUKS_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "LUKS password input failed" rx_retry_or_exit "LUKS password is required" || rx_abort return 1 } local password_confirmation - password_confirmation=$(gum input --placeholder "Confirm LUKS password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --padding "$GUM_INPUT_PADDING") || { + password_confirmation=$(gum input --placeholder "Confirm LUKS password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --value "$LUKS_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "LUKS password confirmation failed" rx_retry_or_exit "Password confirmation is required" || rx_abort return 1 @@ -74,7 +74,7 @@ setup_luks() { esac local iter_choice - iter_choice=$(echo "$iter_time_options" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --prompt "Iteration> " --placeholder "Select iteration time" --padding "$GUM_FILTER_PADDING") || { + iter_choice=$(echo "$iter_time_options" | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --value "$current_iter" --prompt "Iteration> " --placeholder "Select iteration time" --padding "$GUM_FILTER_PADDING") || { rx_step_error "2" "Iteration time selection cancelled" rx_retry_or_exit "Iteration time is required" || rx_abort return 1 diff --git a/bin/setup/mirrors.sh b/bin/setup/mirrors.sh index 19154b1..f577be0 100755 --- a/bin/setup/mirrors.sh +++ b/bin/setup/mirrors.sh @@ -83,7 +83,7 @@ United States' echo local custom_mirror - custom_mirror=$(gum input --placeholder 'https://mirror.example.com/$repo/os/$arch' --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Custom URL> " --padding "$GUM_INPUT_PADDING") || { + custom_mirror=$(gum input --placeholder 'https://mirror.example.com/$repo/os/$arch' --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Custom URL> " --value "$CUSTOM_MIRRORS" --padding "$GUM_INPUT_PADDING") || { rx_step_error "7" "Custom mirror input cancelled" rx_retry_or_exit "Custom mirror input" || rx_abort return 1 diff --git a/bin/setup/network.sh b/bin/setup/network.sh index be69770..ba7abb7 100755 --- a/bin/setup/network.sh +++ b/bin/setup/network.sh @@ -7,16 +7,43 @@ setup_network() { rx_clear_logo rx_step "Configuring network..." - if gum confirm --affirmative "WiFi" --negative "Ethernet" "Select your network type" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + _setup_ethernet() { + local eth_iface + eth_iface=$(rx_get_ethernet_iface) + if [[ -n $eth_iface ]]; then + ip link set "$eth_iface" up 2>/dev/null + fi + + if rx_check_internet || (rx_wait_for_ethernet "$eth_iface" && rx_check_internet); then + NETWORK_TYPE="Ethernet" + WIFI_SSID="" + WIFI_PASSWORD="" + rx_save_state + rx_clear_logo + echo + gum style --foreground 2 "Ethernet connected" + echo + return 0 + fi + + return 1 + } + + if gum confirm --affirmative "WiFi" --negative "Ethernet" "Select your network type" --default="$([[ $NETWORK_TYPE == "WiFi" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then local wifi_iface wifi_iface=$(rx_get_wifi_iface) if [[ -z $wifi_iface ]]; then + gum style --foreground 3 "No WiFi adapter detected, trying Ethernet..." + echo + if _setup_ethernet; then + return 0 + fi rx_clear_logo echo - gum style --foreground 1 "No WiFi adapter detected" + gum style --foreground 1 "Network unavailable" echo - rx_retry_or_exit "WiFi unavailable" || rx_abort + rx_retry_or_exit "No network connection available" || rx_abort return 1 fi @@ -56,28 +83,21 @@ setup_network() { fi done + gum style --foreground 3 "WiFi connection failed, trying Ethernet..." + echo + if _setup_ethernet; then + return 0 + fi + rx_clear_logo echo gum style --foreground 1 "Network unavailable" echo + rx_retry_or_exit "No network connection available" || rx_abort return 1 fi - local eth_iface - eth_iface=$(rx_get_ethernet_iface) - if [[ -n $eth_iface ]]; then - ip link set "$eth_iface" up 2>/dev/null - fi - - if rx_check_internet || (rx_wait_for_ethernet "$eth_iface" && rx_check_internet); then - NETWORK_TYPE="Ethernet" - WIFI_SSID="" - WIFI_PASSWORD="" - rx_save_state - rx_clear_logo - echo - gum style --foreground 2 "Ethernet connected" - echo + if _setup_ethernet; then return 0 fi diff --git a/bin/setup/print.sh b/bin/setup/print.sh index 5c68f58..ed37feb 100755 --- a/bin/setup/print.sh +++ b/bin/setup/print.sh @@ -6,7 +6,7 @@ setup_print() { rx_load_state rx_step "Let's setup printing service..." - if gum confirm --affirmative "Yes, enable printing" --negative "No, skip printing" "CUPS Print Service" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable printing" --negative "No, skip printing" "CUPS Print Service" --default="${PRINT_SERVICE_ENABLED:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then # shellcheck disable=SC2034 PRINT_SERVICE_ENABLED="true" else diff --git a/bin/setup/ricing.sh b/bin/setup/ricing.sh index 12bf55c..eac84a0 100755 --- a/bin/setup/ricing.sh +++ b/bin/setup/ricing.sh @@ -17,7 +17,7 @@ setup_ricing() { gum style --foreground 7 --padding "0 0 0 $PADDING_LEFT" "Edit configs freely — updates will never overwrite your changes." gum style --foreground 7 --padding "0 0 1 $PADDING_LEFT" "Note: You manage your own customizations after installation." - if gum confirm --affirmative "Managed (recommended)" --negative "Manual (copy configs)" "Ricing Mode" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Managed (recommended)" --negative "Manual (copy configs)" "Ricing Mode" --default="$([[ $RICE_MODE == "stable" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then RICE_MODE="stable" else RICE_MODE="advanced" diff --git a/bin/setup/root.sh b/bin/setup/root.sh index 79a56f8..b763d5a 100755 --- a/bin/setup/root.sh +++ b/bin/setup/root.sh @@ -9,7 +9,7 @@ setup_root() { local use_same_password="false" if [[ -n $USER_PASSWORD ]]; then - if gum confirm --affirmative "Yes, use same password" --negative "No, enter different password" "Would you like to configure root with the same password used for ${USER_NAME}?" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, use same password" --negative "No, enter different password" "Would you like to configure root with the same password used for ${USER_NAME}?" --default="$([[ -n $ROOT_PASSWORD && $ROOT_PASSWORD == "$USER_PASSWORD" ]] && echo true || echo false)" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then ROOT_PASSWORD="$USER_PASSWORD" use_same_password="true" rx_save_state @@ -20,13 +20,13 @@ setup_root() { if [[ $use_same_password != "true" ]]; then while true; do local password - password=$(gum input --placeholder "Create a root password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Password> " --padding "$GUM_INPUT_PADDING") || { + password=$(gum input --placeholder "Create a root password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Password> " --value "$ROOT_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Root password input failed" rx_retry_or_exit "Root password is required" || rx_abort return 1 } local password_confirmation - password_confirmation=$(gum input --placeholder "Confirm root password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --padding "$GUM_INPUT_PADDING") || { + password_confirmation=$(gum input --placeholder "Confirm root password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --value "$ROOT_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Root password confirmation failed" rx_retry_or_exit "Password confirmation is required" || rx_abort return 1 diff --git a/bin/setup/ssh.sh b/bin/setup/ssh.sh index b183681..e89a8ff 100755 --- a/bin/setup/ssh.sh +++ b/bin/setup/ssh.sh @@ -7,7 +7,7 @@ setup_ssh() { rx_clear_logo rx_step "Let's setup SSH access..." - if ! gum confirm --affirmative "Yes, enable SSH" --negative "No, skip SSH" "SSH Service" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if ! gum confirm --affirmative "Yes, enable SSH" --negative "No, skip SSH" "SSH Service" --default="${SSH_ENABLED:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then SSH_ENABLED="false" rx_save_state return 0 @@ -19,7 +19,7 @@ setup_ssh() { rx_step "Let's setup SSH access..." local ssh_port - ssh_port=$(gum input --placeholder "SSH port (default: 22)" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Port> " --value "22" --padding "$GUM_INPUT_PADDING") || { + ssh_port=$(gum input --placeholder "SSH port (default: 22)" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Port> " --value "${SSH_PORT:-22}" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "SSH port input failed" rx_retry_or_exit "SSH port is required" || rx_abort return 1 @@ -39,7 +39,7 @@ setup_ssh() { rx_clear_logo rx_step "Let's setup SSH access..." - if gum confirm --affirmative "Yes, enable password login" --negative "No, disable password" "Password Authentication" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable password login" --negative "No, disable password" "Password Authentication" --default="${SSH_PASSWORD_LOGIN:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then SSH_PASSWORD_LOGIN="true" else SSH_PASSWORD_LOGIN="false" @@ -48,7 +48,7 @@ setup_ssh() { rx_clear_logo rx_step "Let's setup SSH access..." - if gum confirm --affirmative "Yes, enable SSH keys" --negative "No, disable keys" "Public Key Authentication" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable SSH keys" --negative "No, disable keys" "Public Key Authentication" --default="${SSH_KEY_LOGIN:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then SSH_KEY_LOGIN="true" else SSH_KEY_LOGIN="false" @@ -57,7 +57,7 @@ setup_ssh() { rx_clear_logo rx_step "Let's setup SSH access..." - if gum confirm --affirmative "Yes, allow root login" --negative "No, disable root" "Root Login" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, allow root login" --negative "No, disable root" "Root Login" --default="${SSH_ROOT_LOGIN:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then SSH_ROOT_LOGIN="true" else SSH_ROOT_LOGIN="false" diff --git a/bin/setup/timezone.sh b/bin/setup/timezone.sh index a44bd94..bb6fe89 100755 --- a/bin/setup/timezone.sh +++ b/bin/setup/timezone.sh @@ -10,7 +10,7 @@ setup_timezone() { current_tz=$(rx_get_current_timezone) # shellcheck disable=SC2034 - USER_TIMEZONE=$(timedatectl list-timezones | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --prompt "Timezone> " --placeholder "Please select your timezone" --padding "$GUM_FILTER_PADDING") || { + USER_TIMEZONE=$(timedatectl list-timezones | gum filter --height "$GUM_FILTER_HEIGHT" "${GUM_FILTER_STYLE[@]}" --value "$USER_TIMEZONE" --prompt "Timezone> " --placeholder "Please select your timezone" --padding "$GUM_FILTER_PADDING") || { rx_step_error "1" "Timezone selection failed" rx_retry_or_exit "Timezone is required" || rx_abort return 1 diff --git a/bin/setup/user.sh b/bin/setup/user.sh index ba3b7fa..9b82f27 100755 --- a/bin/setup/user.sh +++ b/bin/setup/user.sh @@ -8,7 +8,7 @@ setup_user() { while true; do local username - username=$(gum input --placeholder "Pick a username" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Username> " --padding "$GUM_INPUT_PADDING") || { + username=$(gum input --placeholder "Pick a username" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --prompt "Username> " --value "$USER_NAME" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Username input failed" rx_retry_or_exit "Username is required" || rx_abort return 1 @@ -25,13 +25,13 @@ setup_user() { while true; do local password - password=$(gum input --placeholder "Create a password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Password> " --padding "$GUM_INPUT_PADDING") || { + password=$(gum input --placeholder "Create a password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Password> " --value "$USER_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Password input failed" rx_retry_or_exit "Password is required" || rx_abort return 1 } local password_confirmation - password_confirmation=$(gum input --placeholder "Confirm your password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --padding "$GUM_INPUT_PADDING") || { + password_confirmation=$(gum input --placeholder "Confirm your password" --placeholder.foreground 8 --prompt.foreground "#ff79c6" --password --prompt "Confirm> " --value "$USER_PASSWORD" --padding "$GUM_INPUT_PADDING") || { rx_step_error "2" "Password confirmation failed" rx_retry_or_exit "Password confirmation is required" || rx_abort return 1 @@ -48,7 +48,7 @@ setup_user() { fi done - if gum confirm --affirmative "Yes, enable sudo" --negative "No, skip sudo" "User sudo access" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then + if gum confirm --affirmative "Yes, enable sudo" --negative "No, skip sudo" "User sudo access" --default="${USER_SUDO:-true}" $GUM_CONFIRM_STYLE --padding "$GUM_CONFIRM_PADDING"; then # shellcheck disable=SC2034 USER_SUDO="true" else