diff --git a/dot-migration/omarchy-quickshell/README.md b/dot-migration/omarchy-quickshell/README.md new file mode 100644 index 00000000..968a4eee --- /dev/null +++ b/dot-migration/omarchy-quickshell/README.md @@ -0,0 +1,212 @@ +# Omarchy Quickshell Migration Plan + +This file is temporary planning material for the Omarchy 4 Quickshell migration. It lives in `dot-migration/` because that directory is ignored by stow, so these notes and draft files can exist on the dotfiles branch without being installed into the live home directory. + +The likely long-term home is a separate `omarchy-quickshell` repository. Do not create that repository yet. Treat this directory as the staging area for the VM migration plan, draft shell config, and checklist. + +## Current Action + +For this PR branch, the active work is: + +1. Record the upstream Omarchy Quickshell extension point. +2. Keep a single tracked plan and checklist in this file. +3. Keep the PR body short and link here instead of duplicating the migration plan. +4. Avoid live activation on the host machine. + +No file in this directory should be stowed. The host remains on Omarchy 3 until the Omarchy 4 work passes VM validation. + +## Upstream Baseline + +- Upstream PR: +- Upstream branch: `omarchy-shell` +- Extension commit: `1bb439476af2a7b2bdee03c682ae002655c3523c` +- Default shell config at that commit: `config/omarchy/shell.json` +- Local comparison worktree used during planning: `/tmp/opencode/omarchy-shell` + +The Omarchy PR default config is the baseline. Do not derive the Quickshell user config directly from the current Waybar config. Use the upstream `shell.json` shape first, then layer custom modules and host-specific choices into a user config for VM testing. + +If `basecamp/omarchy#5856` advances, compare the new head against `1bb439476af2a7b2bdee03c682ae002655c3523c` before applying this checklist. + +## Scope + +This plan covers dotfiles-side preparation for Omarchy 4 and VM validation. + +In scope: + +1. Draft an Omarchy Quickshell migration plan in this ignored dotfiles path. +2. Use the Omarchy PR default `shell.json` as the extension point. +3. Keep command and script changes compatible with the current Omarchy 3 host where practical. +4. Prepare VM checklist and validation criteria for Omarchy 4. +5. Identify which current Waybar modules become built-ins, command modules, QML modules, or deferred work. +6. Keep Hypr Lua migration requirements visible alongside the shell migration. + +Out of scope for this PR branch: + +1. Creating the future `omarchy-quickshell` repository. +2. Writing live `~/.config/omarchy/shell.json` on the host. +3. Switching the host machine to Omarchy 4. +4. Deleting the current Waybar, hypridle, or hyprlock configs before VM validation. +5. Editing `~/.local/share/omarchy`. + +## Quickshell Extension Model + +The PR makes Omarchy's desktop shell intentionally extensible. + +Important rules: + +1. `~/.config/omarchy/shell.json` becomes canonical once it exists. It does not deep-merge with the default config. +2. Built-in widget IDs use the reserved `omarchy.*` namespace. +3. Custom IDs should use a separate namespace such as `timmo.*`. +4. One-off command modules can be declared inline in `bar.layout.left`, `bar.layout.center`, or `bar.layout.right`. +5. Command module output can be plain text or Waybar-style JSON containing `text`, `tooltip`, and `class`. +6. Command modules run on an interval and parse output after the command exits. They are suitable for polling, not long-running streams. +7. Custom QML modules can live under `~/.config/omarchy/bar/modules/.qml`, or an entry can provide an explicit `source` path. +8. Full plugins live under `~/.config/omarchy/plugins//manifest.json` and can declare `bar-widget`, `panel`, `overlay`, `menu`, or `service` kinds. +9. Plugins are unsandboxed code running inside `omarchy-shell`, so custom QML should stay small and auditable. +10. Waybar `signal = RTMIN+N` refresh behavior does not map directly. Future modules should use polling or shell IPC instead. +11. Waybar CSS class semantics do not map directly. Command modules currently use `class` or `alt` mainly to determine active styling. + +## Temporary File Layout + +Use this ignored staging layout while the future repository does not exist: + +```text +dot-migration/omarchy-quickshell/ + README.md # this plan and checklist + shell.json # optional future draft copied from Omarchy PR default + modules.md # optional deeper module inventory if this file gets too large + bar/modules/ # optional future one-off QML module prototypes + plugins/ # optional future full plugin prototypes +``` + +Only create the optional files when they are needed. This PR starts with the plan file. + +## Waybar To Shell Mapping + +Start with the Omarchy PR default bar layout, then map current custom behavior onto it. + +| Current Waybar module | Omarchy 4 target | Notes | +| --- | --- | --- | +| `custom/omarchy` | `omarchy.menu` | Built-in menu widget. | +| `hyprland/workspaces` | `omarchy.workspaces` | Built-in workspaces widget. | +| `clock` | `omarchy.clock` | Built-in clock widget; configure format inline. | +| `custom/update` | `omarchy.system-update` | Built-in update widget. | +| `group/tray-expander`, `tray`, `custom/expand-icon` | `omarchy.tray` | Built-in tray handles drawer behavior. | +| `bluetooth` | `omarchy.bluetooth` | Built-in panel/widget. | +| `network` | `omarchy.network` | Built-in panel/widget; verify NetworkManager behavior in VM. | +| `pulseaudio` | `omarchy.audio` | Built-in audio panel/widget. | +| `cpu`, `memory`, `battery` | `omarchy.monitor` / `omarchy.power` | Use built-ins first; only add custom output if needed. | +| `custom/screenrecording-indicator` | `omarchy.indicators` item | Built-in indicator support. | +| `custom/idle-indicator` | `omarchy.indicators` item | Maps to shell idle/stay-awake behavior. | +| `custom/notification-silencing-indicator` | `omarchy.indicators` item | Maps to DND indicator. | +| `custom/voxtype` | `omarchy.indicators` item | Built-in dictation indicator; verify click behavior differences. | +| `custom/git-diff` | `timmo.git-diff` command module | Polling command module; remove Waybar signal dependency. | +| `custom/git-notifications` | `timmo.git-notifications` command module | Polling command module; open/refresh clicks need shell-compatible behavior. | +| `custom/git-workflows` | `timmo.git-workflows` command module | Polling command module; no RTMIN refresh equivalent. | +| `custom/twitch-notifications-active` | `timmo.twitch-notifications` command module | Good command-module candidate using `--status-bar-json`. | +| `custom/temperature` | `timmo.temperature` command module | Polling Home Assistant status. | +| `custom/co2-alert` | `timmo.co2-alert` command module | Polling Home Assistant status; class styling may need adjustment. | +| `custom/voc-alert` | `timmo.voc-alert` command module or omit | Desktop currently hides this permanently; verify need. | +| `custom/nas-activity` | `timmo.nas-activity` command module | Polling Home Assistant status. | +| `custom/current-next-event` | `timmo.current-next-event` command module | Polling Home Assistant status. | +| `custom/in-a-call` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/time-check` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/heating` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/rain` | QML/service or rewritten one-shot command | Current behavior is a streaming watcher. | +| `custom/doorbell` | QML/service plugin | Stateful stream, trigger command, cooldown, and host-specific monitor geometry. | + +## Script Changes Needed + +Script work should preserve current host behavior while making the Omarchy 4 VM path clear. + +1. Replace direct `omarchy-launch-walker` assumptions with an abstraction that uses `omarchy-menu-select` when available and falls back to the current Walker launcher while the host remains on Omarchy 3. +2. Apply that to `twitch-menu`, `workspace-menu`, and `workspace-relayout`. +3. Split generic status-bar JSON output from Waybar-specific refresh behavior in the git status scripts. +4. Keep Waybar-specific wrappers working until the host leaves Omarchy 3. +5. Avoid adding new `--waybar` or `--status-waybar` aliases. Use `--bar-json` and `--status-bar-json` only. + +## Hypr Lua Checklist + +Current Hypr Lua prep exists in `timmo001/omarchy-hypr`, but Omarchy 4 migration still needs VM validation. + +Before enabling Omarchy 4 in the VM: + +1. Preserve prepared root Lua files from upstream migration clobbering, especially `hyprland.lua`, `bindings.lua`, `autostart.lua`, `input.lua`, `looknfeel.lua`, and `monitors.lua`. +2. Confirm `hyprland.lua` still requires the local `hypr.envs` module if needed. +3. Re-check local binding conflicts against Omarchy 4 defaults and add explicit `hl.unbind(...)` calls where local bindings intentionally replace defaults. +4. Keep host-specific monitor behavior in `hosts/desktop` and `hosts/laptop`. +5. Treat `hypridle.conf` and `hyprlock.conf` as Omarchy 3-only once shell idle/lock are active. +6. Move idle timing to `shell.json` after Omarchy 4 shell idle service is active. +7. Verify `hyprsunset` laptop-specific behavior does not conflict with Omarchy 4 night-light behavior. + +## Theme Checklist + +Omarchy 4 moves more visual behavior into shell theme tokens. + +For each custom theme that should survive the migration: + +1. Keep `colors.toml` as the palette source where available. +2. Add or update `shell.toml` for bar, menu, launcher, notification, lock, and shared control tokens. +3. Convert Hypr theme overrides from `hyprland.conf` to `hyprland.lua` where needed. +4. Revisit Mako settings because shell notifications replace Mako. +5. Revisit Waybar CSS because it will not apply to Quickshell widgets. +6. Verify current background and unlock assets still match Omarchy 4 background and lock behavior. + +## VM Test Checklist + +Use the VM as the first activation target. + +Base setup: + +1. Install or switch the VM to the Omarchy 4 branch represented by the chosen upstream commit. +2. Confirm the VM's Omarchy checkout matches or intentionally supersedes `1bb439476af2a7b2bdee03c682ae002655c3523c`. +3. Apply dotfiles branches needed for this PR. +4. Apply Hypr Lua branch/config needed for host override testing. +5. Copy or adapt the draft `shell.json` from this migration directory only after reviewing the current upstream default. + +Shell validation: + +1. `omarchy-shell` starts without QML errors. +2. `omarchy shell shell ping` or equivalent IPC health check succeeds. +3. Default built-in widgets render before adding custom modules. +4. Command modules render last-line JSON correctly. +5. Click handlers work with `onClick`, `onRightClick`, and `onMiddleClick` naming. +6. Missing secrets or services degrade gracefully for Home Assistant, GitHub, and Twitch modules. +7. No custom module relies on Waybar RTMIN signals. +8. No custom module relies on Waybar CSS classes for essential behavior. + +Hypr validation: + +1. Hyprland starts from Lua config. +2. `hyprctl reload` succeeds. +3. `hyprctl configerrors` is clean or contains only understood upstream issues. +4. Desktop and laptop host-selection behavior is still clear. +5. Resume recovery still works without `hypridle` `after_sleep_cmd`. +6. Lock, idle, screensaver, and wake behavior are verified through Omarchy shell services. + +Desktop behavior validation: + +1. Menus open through the shell menu path instead of Walker-only paths. +2. Workspace menu and relayout flows work. +3. Twitch menu works. +4. Git diff, GitHub notification, and workflow modules show expected status. +5. Doorbell behavior is either explicitly deferred or implemented through a QML/service plugin. +6. Network and Wi-Fi behavior works with NetworkManager. +7. Notification and OSD behavior match the intended theme. + +## Promotion Criteria + +Promote this work out of `dot-migration/` only when all of these are true: + +1. The VM passes the shell and Hypr validation checklist. +2. The selected upstream Omarchy commit is either merged, tagged, or intentionally accepted as the target. +3. The future `omarchy-quickshell` repository layout is decided. +4. Draft files are moved from this temporary directory into the final repo or replaced with links to that repo. +5. Host activation is reviewed separately from the PR that only prepares dotfiles. + +## Open Decisions + +1. Whether git status modules should remain shell command modules or become a small shared QML plugin with explicit refresh IPC. +2. Whether Home Assistant streaming modules should be rewritten as one-shot polling commands or implemented as a QML service plugin. +3. Whether desktop and laptop should share a single `shell.json` with host-specific scripts, or separate generated user configs in the future `omarchy-quickshell` repo. +4. Which custom themes are worth fully porting to `shell.toml` before host activation. diff --git a/dot/src/doctor/checks/systemd.ts b/dot/src/doctor/checks/systemd.ts index 19a495bc..edf3c621 100644 --- a/dot/src/doctor/checks/systemd.ts +++ b/dot/src/doctor/checks/systemd.ts @@ -11,6 +11,7 @@ import type { CheckResult } from "../types.js"; const HOME = process.env.HOME ?? `/home/${process.env.USER}`; const XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME ?? join(HOME, ".config"); +const USER_SYSTEMD_DIR = join(XDG_CONFIG_HOME, "systemd", "user"); function displayPath(p: string): string { return p.replace(HOME, "~"); @@ -21,6 +22,18 @@ const LEGACY_WORKFLOW_WATCH_SERVICE_UNIT = "git-workflow-watch.service"; const LEGACY_WORKFLOW_WATCH_TIMER_UNIT = "git-workflow-watch.timer"; const DOCTOR_STARTUP_TIMER_UNIT = "dot-doctor-startup.timer"; const DAILY_VOLUME_ZERO_TIMER_UNIT = "daily-volume-zero.timer"; +const RESUME_MONITOR_SERVICE_UNIT = "dot-on-resume-monitor.service"; +const DOCTOR_STARTUP_NOTIFY_SCRIPT = join( + HOME, + ".local", + "bin", + "dot-doctor-notify", +); +const RESUME_MONITOR_SCRIPT = join(HOME, ".local", "bin", "on-resume-monitor"); + +function userSystemdUnitPath(unit: string): string { + return join(USER_SYSTEMD_DIR, unit); +} function pathExistsOrSymlink(path: string): boolean { try { @@ -67,6 +80,131 @@ function addObsoletePathCheck( } } +function addExecutablePresenceCheck( + results: CheckResult[], + path: string, + okMessage: string, + warnMessage: string, + detail?: string, +): void { + results.push( + executableExists(path) + ? { severity: "ok", message: okMessage } + : { + severity: "warn", + message: warnMessage, + ...(detail && { detail }), + }, + ); +} + +function addFilePresenceCheck( + results: CheckResult[], + path: string, + okMessage: string, + warnMessage: string, + detail: string, +): void { + results.push( + existsSync(path) + ? { severity: "ok", message: okMessage } + : { severity: "warn", message: warnMessage, detail }, + ); +} + +const checkRequiredUserUnit = ( + results: CheckResult[], + executor: CommandExecutorService, + unit: string, + label: string, + enableDetail: string, +) => + Effect.gen(function* () { + const hasSystemctl = + (yield* executor.exitCode("which", ["systemctl"])) === 0; + if (!hasSystemctl) { + results.push({ + severity: "warn", + message: `Skipping ${label.toLowerCase()} checks (systemctl not found)`, + }); + return; + } + + const enabled = yield* executor.exitCode("systemctl", [ + "--user", + "is-enabled", + unit, + ]); + if (enabled === 0) { + results.push({ severity: "ok", message: `${label} enabled: ${unit}` }); + } else { + results.push({ + severity: "warn", + message: `${label} is disabled: ${unit}`, + detail: enableDetail, + }); + } + + const active = yield* executor.exitCode("systemctl", [ + "--user", + "is-active", + unit, + ]); + if (active === 0) { + results.push({ severity: "ok", message: `${label} active: ${unit}` }); + } else { + results.push({ + severity: "warn", + message: `${label} is not active: ${unit}`, + detail: enableDetail, + }); + } + }); + +interface RequiredUserUnitSetup { + readonly scriptPath: string; + readonly scriptOkMessage: string; + readonly scriptWarnMessage: string; + readonly scriptDetail?: string; + readonly unitPath: string; + readonly unitOkMessage: string; + readonly unitWarnMessage: string; + readonly unitDetail: string; + readonly unit: string; + readonly unitLabel: string; +} + +const checkRequiredUserUnitSetup = (setup: RequiredUserUnitSetup) => + Effect.gen(function* () { + const executor = yield* CommandExecutor; + const results: CheckResult[] = []; + const enableDetail = `Enable with: systemctl --user enable --now ${setup.unit}`; + + addExecutablePresenceCheck( + results, + setup.scriptPath, + setup.scriptOkMessage, + setup.scriptWarnMessage, + setup.scriptDetail, + ); + addFilePresenceCheck( + results, + setup.unitPath, + setup.unitOkMessage, + setup.unitWarnMessage, + setup.unitDetail, + ); + yield* checkRequiredUserUnit( + results, + executor, + setup.unit, + setup.unitLabel, + enableDetail, + ); + + return results; + }); + const checkObsoleteUserUnit = ( results: CheckResult[], executor: CommandExecutorService, @@ -553,87 +691,31 @@ export const checkGitNotifications = Effect.gen(function* () { }); /** Check doctor startup notification timer */ -export const checkDoctorStartup = Effect.gen(function* () { - const executor = yield* CommandExecutor; - const results: CheckResult[] = []; - - const notifyScript = join(HOME, ".local", "bin", "dot-doctor-notify"); - const unitPath = join( - XDG_CONFIG_HOME, - "systemd", - "user", - DOCTOR_STARTUP_TIMER_UNIT, - ); - - if (existsSync(notifyScript)) { - results.push({ - severity: "ok", - message: `Doctor startup notify script found: ${displayPath(notifyScript)}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup notify script missing or not executable: ${displayPath(notifyScript)}`, - }); - } - - if (existsSync(unitPath)) { - results.push({ - severity: "ok", - message: `Doctor startup timer unit file found: ${displayPath(unitPath)}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer unit file missing: ${displayPath(unitPath)}`, - detail: "Run dot stow (or dot install) to link systemd user units", - }); - } - - const hasSystemctl = (yield* executor.exitCode("which", ["systemctl"])) === 0; - if (hasSystemctl) { - const enabled = yield* executor.exitCode("systemctl", [ - "--user", - "is-enabled", - DOCTOR_STARTUP_TIMER_UNIT, - ]); - if (enabled === 0) { - results.push({ - severity: "ok", - message: `Doctor startup timer enabled: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer is disabled: ${DOCTOR_STARTUP_TIMER_UNIT}`, - detail: `Enable with: systemctl --user enable --now ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } - - const active = yield* executor.exitCode("systemctl", [ - "--user", - "is-active", - DOCTOR_STARTUP_TIMER_UNIT, - ]); - if (active === 0) { - results.push({ - severity: "ok", - message: `Doctor startup timer active: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } else { - results.push({ - severity: "warn", - message: `Doctor startup timer is not active: ${DOCTOR_STARTUP_TIMER_UNIT}`, - }); - } - } else { - results.push({ - severity: "warn", - message: "Skipping doctor startup timer checks (systemctl not found)", - }); - } +export const checkDoctorStartup = checkRequiredUserUnitSetup({ + scriptPath: DOCTOR_STARTUP_NOTIFY_SCRIPT, + scriptOkMessage: `Doctor startup notify script found: ${displayPath(DOCTOR_STARTUP_NOTIFY_SCRIPT)}`, + scriptWarnMessage: `Doctor startup notify script missing or not executable: ${displayPath(DOCTOR_STARTUP_NOTIFY_SCRIPT)}`, + unitPath: userSystemdUnitPath(DOCTOR_STARTUP_TIMER_UNIT), + unitOkMessage: `Doctor startup timer unit file found: ${displayPath(userSystemdUnitPath(DOCTOR_STARTUP_TIMER_UNIT))}`, + unitWarnMessage: `Doctor startup timer unit file missing: ${displayPath(userSystemdUnitPath(DOCTOR_STARTUP_TIMER_UNIT))}`, + unitDetail: "Run dot stow (or dot install) to link systemd user units", + unit: DOCTOR_STARTUP_TIMER_UNIT, + unitLabel: "Doctor startup timer", +}); - return results; +/** Check resume recovery monitor service used after hypridle is removed. */ +export const checkResumeMonitor = checkRequiredUserUnitSetup({ + scriptPath: RESUME_MONITOR_SCRIPT, + scriptOkMessage: `Resume monitor script is executable: ${displayPath(RESUME_MONITOR_SCRIPT)}`, + scriptWarnMessage: `Resume monitor script is missing or not executable: ${displayPath(RESUME_MONITOR_SCRIPT)}`, + scriptDetail: + "Run dot stow (or dot install) to link the resume monitor script", + unitPath: userSystemdUnitPath(RESUME_MONITOR_SERVICE_UNIT), + unitOkMessage: `Resume monitor service unit file found: ${displayPath(userSystemdUnitPath(RESUME_MONITOR_SERVICE_UNIT))}`, + unitWarnMessage: `Resume monitor service unit file missing: ${displayPath(userSystemdUnitPath(RESUME_MONITOR_SERVICE_UNIT))}`, + unitDetail: "Run dot stow (or dot install) to link systemd user units", + unit: RESUME_MONITOR_SERVICE_UNIT, + unitLabel: "Resume monitor service", }); /** Check daily volume reset timer (laptop-only, informational) */ @@ -643,9 +725,8 @@ export const checkDailyVolumeReset = Effect.gen(function* () { const host = process.env.OMARCHY_HOST ?? "unset"; const script = join(HOME, ".local", "bin", "daily-volume-zero"); - const systemdDir = join(XDG_CONFIG_HOME, "systemd", "user"); - const serviceUnit = join(systemdDir, "daily-volume-zero.service"); - const timerUnit = join(systemdDir, DAILY_VOLUME_ZERO_TIMER_UNIT); + const serviceUnit = userSystemdUnitPath("daily-volume-zero.service"); + const timerUnit = userSystemdUnitPath(DAILY_VOLUME_ZERO_TIMER_UNIT); if (existsSync(script)) { results.push({ diff --git a/dot/src/doctor/runner.ts b/dot/src/doctor/runner.ts index fb2c61fc..4562c2a0 100644 --- a/dot/src/doctor/runner.ts +++ b/dot/src/doctor/runner.ts @@ -12,6 +12,7 @@ import { checkGitNotifications, checkWorkflowRuns, checkDoctorStartup, + checkResumeMonitor, checkDailyVolumeReset, } from "./checks/systemd.js"; import { checkOmarchy } from "./checks/omarchy.js"; @@ -49,6 +50,7 @@ const sections: readonly SectionDef[] = [ { name: "Workflow runs checks", check: checkWorkflowRuns }, { name: "Git notification checks", check: checkGitNotifications }, { name: "Doctor startup notification", check: checkDoctorStartup }, + { name: "Resume recovery monitor", check: checkResumeMonitor }, { name: "Daily volume reset", check: checkDailyVolumeReset }, { name: "Omarchy repository checks", check: checkOmarchy }, { name: "Private access", check: checkPrivateAccess }, diff --git a/scripts/.local/bin/on-resume b/scripts/.local/bin/on-resume index 59764c1e..9ba939d2 100755 --- a/scripts/.local/bin/on-resume +++ b/scripts/.local/bin/on-resume @@ -1,21 +1,25 @@ #!/bin/bash -# Called by hypridle after_sleep_cmd on system resume from sleep. +# Called after system resume from sleep. # Restarts services that don't recover well after suspend. LOG_FILE="/tmp/on-resume.log" CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" -WAYBAR_CACHE_DIR="$CACHE_HOME/waybar" -DOT_FETCH_CACHE_DIR="$CACHE_HOME/dot/fetch-upstream" +# The git status modules keep the legacy Waybar cache namespace until their +# shell module port lands. +GIT_MODULE_CACHE_DIR="$CACHE_HOME/waybar" log() { echo "[$(date '+%H:%M:%S')] $1" >> "$LOG_FILE" } -clear_git_caches() { - rm -f "$WAYBAR_CACHE_DIR"/git-*-waybar.json "$WAYBAR_CACHE_DIR"/git-*-waybar.json.tmp - rmdir "$WAYBAR_CACHE_DIR"/git-*-waybar.lock 2>/dev/null || true - rm -rf "$DOT_FETCH_CACHE_DIR" +clear_git_refresh_locks() { + rm -f "$GIT_MODULE_CACHE_DIR"/git-*-waybar.json.tmp + rmdir "$GIT_MODULE_CACHE_DIR"/git-*-waybar.lock 2>/dev/null || true +} + +refresh_shell_indicators() { + omarchy-shell -q omarchy.indicators refresh || true } if [[ "${ON_RESUME_DETACHED:-0}" != "1" ]]; then @@ -31,15 +35,13 @@ log "Resume started" pkill -f '/usr/bin/[t]witch-notifications( |$)' >/dev/null 2>&1 || pkill -f '(^| )[t]witch-notifications( |$)' >/dev/null 2>&1 || true log "Killed twitch-notifications" -# Clear git caches and restart waybar so modules refresh after network resume -clear_git_caches -pkill -x waybar >/dev/null 2>&1 || true -sleep 0.2 -pkill -9 -x waybar >/dev/null 2>&1 || true -log "Killed waybar, cleared git caches" +# Clear stale git watcher locks while keeping cached JSON for restart efficiency. +clear_git_refresh_locks +log "Cleared stale git watcher locks and temporary cache files" -uwsm-app -- waybar >/dev/null 2>&1 & -log "Waybar restarted" +# Ask the shell to repaint state that may have changed while suspended. +refresh_shell_indicators +log "Requested Omarchy shell indicator refresh" # Restart twitch-notifications after brief delay sleep 1 diff --git a/scripts/.local/bin/on-resume-monitor b/scripts/.local/bin/on-resume-monitor new file mode 100755 index 00000000..af322cb0 --- /dev/null +++ b/scripts/.local/bin/on-resume-monitor @@ -0,0 +1,24 @@ +#!/bin/bash + +set -euo pipefail + +sleep_signal="type='signal',sender='org.freedesktop.login1',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" +saw_sleep=0 + +while IFS= read -r line; do + case "$line" in + *"boolean true"*) + saw_sleep=1 + ;; + *"boolean false"*) + if (( saw_sleep )); then + sleep 1 + "$HOME/.local/bin/on-resume" + fi + saw_sleep=0 + ;; + esac +done < <(dbus-monitor --system "$sleep_signal") + +# Restart the user service if dbus-monitor exits unexpectedly. +exit 1 diff --git a/scripts/.local/bin/twitch-menu b/scripts/.local/bin/twitch-menu index fc1ee09d..2be11f7f 100755 --- a/scripts/.local/bin/twitch-menu +++ b/scripts/.local/bin/twitch-menu @@ -1,6 +1,24 @@ #!/usr/bin/env bash set -euo pipefail +menu_select() { + local prompt="$1" + local width="$2" + local maxheight="$3" + shift 3 + + if command -v omarchy-menu-select >/dev/null 2>&1; then + omarchy-menu-select "$prompt" "$@" -- --width "$width" --minheight 1 --maxheight "$maxheight" 2>/dev/null || true + return 0 + fi + + if command -v omarchy-launch-walker >/dev/null 2>&1; then + printf '%s\n' "$@" | omarchy-launch-walker --dmenu --width "$width" --minheight 1 --maxheight "$maxheight" -p "$prompt…" 2>/dev/null || true + fi + + return 0 +} + open_url() { local url="${1:?}" local host="${OMARCHY_HOST:-}" @@ -56,7 +74,7 @@ channels_menu() { fi done - choice="$(printf '%s\n' "${options[@]}" | omarchy-launch-walker --dmenu --width 360 --minheight 1 --maxheight 630 -p 'Twitch channels…' 2>/dev/null || true)" + choice="$(menu_select 'Twitch channels' 360 630 "${options[@]}")" choice_label="${choice#* }" [[ $choice_label == 'Open all live autolaunch' ]] && exec twitch-notifications --recheck --open selected="${choice_label% \[offline\]}" @@ -70,7 +88,7 @@ channels_menu() { } while true; do - choice="$(printf '%s\n' ' Recheck notifications' '󰜉 Restart notifications' ' Open following' ' Open following live' ' Open channel(s)..' | omarchy-launch-walker --dmenu --width 295 --minheight 1 --maxheight 630 -p 'Twitch…' 2>/dev/null || true)" + choice="$(menu_select 'Twitch' 295 630 ' Recheck notifications' '󰜉 Restart notifications' ' Open following' ' Open following live' ' Open channel(s)..')" choice="${choice#* }" case "$choice" in diff --git a/scripts/.local/bin/workspace-menu b/scripts/.local/bin/workspace-menu index 6eb90a8c..db25a4ee 100755 --- a/scripts/.local/bin/workspace-menu +++ b/scripts/.local/bin/workspace-menu @@ -2,6 +2,24 @@ set -euo pipefail +menu_select() { + local prompt="$1" + local width="$2" + local maxheight="$3" + shift 3 + + if command -v omarchy-menu-select >/dev/null 2>&1; then + omarchy-menu-select "$prompt" "$@" -- --width "$width" --minheight 1 --maxheight "$maxheight" 2>/dev/null || true + return 0 + fi + + if command -v omarchy-launch-walker >/dev/null 2>&1; then + printf '%s\n' "$@" | omarchy-launch-walker --dmenu --width "$width" --minheight 1 --maxheight "$maxheight" -p "$prompt…" 2>/dev/null || true + fi + + return 0 +} + while true; do options=( '󰙀 Relayout current workspace' @@ -15,7 +33,7 @@ while true; do options=('󰣇 Setup workspace' "${options[@]}") fi - choice="$(printf '%s\n' "${options[@]}" | omarchy-launch-walker --dmenu --width 360 --minheight 1 --maxheight 420 -p 'Workspace…' 2>/dev/null || true)" + choice="$(menu_select 'Workspace' 360 420 "${options[@]}")" choice="${choice#* }" case "$choice" in diff --git a/scripts/.local/bin/workspace-relayout b/scripts/.local/bin/workspace-relayout index 56412f34..c2f47695 100755 --- a/scripts/.local/bin/workspace-relayout +++ b/scripts/.local/bin/workspace-relayout @@ -59,6 +59,28 @@ notify() { notify-send "$NOTIFY_TITLE" "$1" } +menu_select() { + local prompt="$1" + local width="$2" + local maxheight="$3" + shift 3 + + if command -v omarchy-menu-select >/dev/null 2>&1; then + omarchy-menu-select "$prompt" "$@" -- --width "$width" --minheight 1 --maxheight "$maxheight" 2>/dev/null || true + return 0 + fi + + if command -v omarchy-launch-walker >/dev/null 2>&1; then + printf '%s\n' "$@" | omarchy-launch-walker --dmenu --width "$width" --minheight 1 --maxheight "$maxheight" -p "$prompt…" 2>/dev/null || true + fi + + return 0 +} + +menu_selector_available() { + command -v omarchy-menu-select >/dev/null 2>&1 || command -v omarchy-launch-walker >/dev/null 2>&1 +} + focus_addr() { local addr="$1" [ -n "$addr" ] || return 1 @@ -201,13 +223,13 @@ choose_layout() { local three_half_label local three_lmr_label - if ! command -v omarchy-launch-walker >/dev/null 2>&1; then - notify "omarchy-launch-walker is not available" + if ! menu_selector_available; then + notify "No Omarchy menu selector is available" return 1 fi option_count="$(jq -r 'length' <<< "$clients_json")" - prompt="$option_count window layout..." + prompt="$option_count window layout" two_label='1 top / 1 bottom [74/26]' two_alt_label='1 top / 1 bottom [63/37]' two_half_label='1 top / 1 bottom [50/50]' @@ -222,13 +244,13 @@ choose_layout() { case "$option_count" in 2) - choice="$(printf '%s\n' "$two_label" "$two_alt_label" "$two_half_label" "$two_lr_label" "$two_lr_one_third_label" | omarchy-launch-walker --dmenu --width 400 --minheight 1 --maxheight 320 -p "$prompt" 2>/dev/null || true)" + choice="$(menu_select "$prompt" 400 320 "$two_label" "$two_alt_label" "$two_half_label" "$two_lr_label" "$two_lr_one_third_label")" ;; 4) - choice="$(printf '%s\n' "$grid_label" "$top_bottom_label" | omarchy-launch-walker --dmenu --width 360 --minheight 1 --maxheight 240 -p "$prompt" 2>/dev/null || true)" + choice="$(menu_select "$prompt" 360 240 "$grid_label" "$top_bottom_label")" ;; 3) - choice="$(printf '%s\n' "$three_label" "$three_current_label" "$three_half_label" "$three_lmr_label" | omarchy-launch-walker --dmenu --width 420 --minheight 1 --maxheight 300 -p "$prompt" 2>/dev/null || true)" + choice="$(menu_select "$prompt" 420 300 "$three_label" "$three_current_label" "$three_half_label" "$three_lmr_label")" ;; *) notify "Expected 2, 3, or 4 visible tiled windows on this workspace, found $option_count" diff --git a/systemd/.config/systemd/user/dot-on-resume-monitor.service b/systemd/.config/systemd/user/dot-on-resume-monitor.service new file mode 100644 index 00000000..8fa886bc --- /dev/null +++ b/systemd/.config/systemd/user/dot-on-resume-monitor.service @@ -0,0 +1,13 @@ +[Unit] +Description=Run dot resume recovery after system resume +After=dbus.socket +Requires=dbus.socket + +[Service] +Type=simple +ExecStart=%h/.local/bin/on-resume-monitor +Restart=always +RestartSec=2 + +[Install] +WantedBy=graphical-session.target