diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8de9215b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +# Python bytecode (orchestrator) +__pycache__/ +*.pyc diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 00000000..ff0e3599 --- /dev/null +++ b/.luarc.json @@ -0,0 +1,11 @@ +{ + "workspace": { + "library": [ + "/usr/share/hypr/stubs" + ], + "checkThirdParty": false + }, + "diagnostics": { + "globals": ["hl"] + } +} diff --git a/AGENTS.md b/AGENTS.md index 7d61ac11..e2aee0d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ Common prefixes include: Other current prefixes include: -- `ac-`, `audio-`, `battery-`, `branch-`, `brightness-`, `channel-`, `config-`, `debug-`, `dev-`, `drive-`, `first-`, `font-`, `haptic-`, `hibernation-`, `hook-`, `hyprland-`, `menu-`, `migrate-`, `notification-`, `npx-`, `plymouth-`, `powerprofiles-`, `reinstall-`, `remove-`, `screensaver-`, `show-`, `snapshot-`, `state-`, `sudo-`, `swayosd-`, `system-`, `transcode-`, `tui-`, `tz-`, `upload-`, `version-`, `voxtype-`, `webapp-`, `wifi-`, `windows-` +- `ac-`, `audio-`, `battery-`, `branch-`, `brightness-`, `channel-`, `config-`, `debug-`, `dev-`, `drive-`, `first-`, `font-`, `haptic-`, `hibernation-`, `hook-`, `hyprland-`, `menu-`, `migrate-`, `notification-`, `npm-`, `plymouth-`, `powerprofiles-`, `reinstall-`, `remove-`, `screensaver-`, `show-`, `snapshot-`, `state-`, `sudo-`, `system-`, `transcode-`, `tui-`, `tz-`, `upload-`, `version-`, `voxtype-`, `webapp-`, `wifi-`, `windows-` # Command Metadata @@ -40,61 +40,128 @@ Commands in `bin/` can declare CLI metadata in comments near the top of the file Supported metadata keys: - `# omarchy:summary=...` - short help text -- `# omarchy:group=...` - command group when it differs from the filename-derived prefix -- `# omarchy:name=...` - command name within the group - `# omarchy:args=...` - usage arguments - `# omarchy:examples=...` - examples separated with ` | ` - `# omarchy:alias=...` / `# omarchy:aliases=...` - alternate routes - `# omarchy:hidden=true` - hide from default command listings - `# omarchy:requires-sudo=true` - mark commands that require sudo +Only use `omarchy:examples` where there are args that need explaining. + Prefer explicit metadata for user-facing commands. Keep routes consistent with the filename unless there is a deliberate alias or compatibility route. Example: ```bash # omarchy:summary=Take a screenshot -# omarchy:group=capture # omarchy:args=[smart|region|windows|fullscreen] [slurp|copy] # omarchy:examples=omarchy screenshot | omarchy capture screenshot region -# omarchy:aliases=omarchy screenshot ``` +# Runtime Environment + +- `$OMARCHY_PATH` is set at the top level by the uwsm session environment and is always available to Omarchy runtime code. +- Commands in `bin/` and Quickshell QML should rely on `$OMARCHY_PATH` / `Quickshell.env("OMARCHY_PATH")`; do not derive fallback paths from `HOME`, `Quickshell.shellDir`, or re-export/default `OMARCHY_PATH` manually. + +# Privileged Commands + +- Whenever you need to trigger a sudo command, use `pkexec` so it results in a user prompt they can approve. + +# Git + +- Commits should be atomic: include only one coherent change or fix, and do not mix unrelated work. +- Commit messages should be succinct and describe the change being made. + # Install Scripts -Install entry points (`install.sh`, `boot.sh`) use `#!/bin/bash`. Many scripts under `install/` are sourced via `run_logged` and intentionally do not have shebangs. +The ISO owns installation orchestration. This repo ships target-side setup commands and reusable setup leaves: -Install stage files follow this pattern: +- `bin/omarchy-setup-system` runs root-owned system setup during ISO finalization. +- `bin/omarchy-setup-hardware` runs idempotent hardware-specific setup and is called by `omarchy-setup-system`. +- `bin/omarchy-finalize-user` runs the per-user runtime finalization (skill symlinks, xdg-user-dirs, mime defaults, `install/user/all.sh`). Shipped user defaults are seeded by `/etc/skel` from `omarchy-settings`, not by this command. `bin/omarchy-reinstall-configs` is the explicit destructive resync of those defaults into an existing user's `$HOME`. +- leaf scripts under `install/` are sourced by `run_logged $OMARCHY_INSTALL/path/to/script.sh` and intentionally do not have shebangs. +- avoid `exit` in sourced setup scripts unless intentionally aborting setup. +- use `$OMARCHY_INSTALL` and `$OMARCHY_PATH` instead of hard-coded Omarchy paths. +- keep root-scoped hardware setup under `install/hardware/` and orchestrate it through `install/hardware/all.sh`. +- keep every per-user setup leaf under `install/user/` (including `install/user/hardware/` and `install/user/first-run/`) so it is clear what must run for each user. +- prefer helper commands for package and command checks where available. -- `install/*/all.sh` lists scripts in execution order -- leaf scripts are sourced by `run_logged $OMARCHY_INSTALL/path/to/script.sh` -- avoid `exit` in sourced install scripts unless intentionally aborting the install -- use `$OMARCHY_INSTALL` and `$OMARCHY_PATH` instead of hard-coded Omarchy paths -- keep hardware-specific logic under `install/config/hardware/` -- prefer helper commands for package and command checks where available - -Raw `command -v`, `pacman`, and `pacman-key` are acceptable in bootstrap/preflight/package-helper contexts where the helper commands may not be available yet or where direct package-manager behavior is the point of the script. +Raw `command -v`, `pacman`, and `pacman-key` are acceptable in package-helper contexts where direct package-manager behavior is the point of the script. # Helper Commands Use these instead of raw shell commands: - `omarchy-cmd-missing` / `omarchy-cmd-present` - check for commands -- `omarchy-pkg-missing` / `omarchy-pkg-present` - check for packages +- `omarchy-pkg-missing` / `omarchy-pkg-present` - check for packages (don't use these if you can just use `omarchy-pkg-add`/`omarchy-pkg-drop`) - `omarchy-pkg-add` - install packages (handles both pacman and AUR) +- `omarchy-pkg-drop` - remove packages; use this instead of raw `pacman -R*` +- `omarchy-notification-send` - send desktop notifications; do not call `notify-send` directly - `omarchy-hw-asus-rog` - detect ASUS ROG hardware (and similar `hw-*` commands) -Exceptions are allowed for bootstrap, preflight, migration, and package-helper scripts where the helper may not be available yet, where the helper itself is being implemented, or where direct package-manager behavior is required. +Exceptions are allowed for migration and package-helper scripts where the helper may not be available yet, where the helper itself is being implemented, or where direct package-manager behavior is required. # Config Structure - `config/` - default configs copied to `~/.config/` - `default/themed/*.tpl` - templates with `{{ variable }}` placeholders for theme colors -- `themes/*/colors.toml` - theme color definitions (accent, background, foreground, color0-15) +- `themes/*/colors.toml` - theme color definitions (accent, background, foreground, red/green/yellow/blue/magenta/cyan and bright_* variants) -# Visual Changes +# Tests -When making visual changes, such as Waybar styles or desktop appearance, always take and analyze a screenshot after applying the change to verify the result. Use `omarchy capture screenshot fullscreen save` for fullscreen screenshots. +Run focused automated tests for the area you changed. Current test entry points: + +- `./test/all` - aggregate runner for CLI and shell tests +- `./test/cli` - CLI routing, command metadata, theme helpers, and safe dispatch coverage +- `./test/shell` - all Omarchy shell tests under `test/shell.d/` + +New Omarchy shell tests should live in `test/shell.d/*-test.sh` so `./test/shell` picks them up automatically. Source `test/shell.d/base-test.sh` for shared root-path discovery, assertions, and Node test helpers. + +For visual changes, such as omarchy-shell styling, desktop appearance, screenshots, or screen recording flows, verify with the running UI in addition to automated tests. Take and analyze screenshots with `omarchy capture screenshot fullscreen save`. For animation, transitions, capture, or screen recording behavior, make a short recording with `omarchy screenrecord --fullscreen`, stop it with `omarchy screenrecord --stop-recording`, and review the output before finishing. + +For interactive UI work, use `wtype` to simulate keyboard input when available. Example: start the UI in the background, wait briefly for focus, then run `wtype -k Right -k Return` to exercise keyboard selection and confirm the resulting command output or state change. Prefer this over manual-only verification when a UI returns a selected value or changes a symlink/config. + +When testing layer-shell UI, capture the reference and candidate states as separate screenshots, then compare them visually before further edits. If a launched UI would otherwise remain open, keep track of its PID and stop it after the screenshot; avoid broad process kills unless checking with `ps` first. + +# Omarchy shell + +The Quickshell desktop runs as a single long-running process out of +`shell/`. Hyprland autostart launches it directly with `quickshell -p`; do +not start additional standalone `quickshell -p` instances for individual +components. + +Run `omarchy-restart-shell` after making changes to QML files. + +Plugin contract: + +- Each plugin lives in its own directory under + `shell/plugins//` (first-party) or + `~/.config/omarchy/plugins//` (third-party). +- Every plugin ships a `manifest.json` declaring `id`, `kinds`, + `activation`, and `entryPoints`. The full schema is in + [`docs/omarchy-shell.md`](docs/omarchy-shell.md). +- Entry-point QML files are `Item`s (not `ShellRoot`), and accept the + shell-injected properties `omarchyPath`, `shell`, `manifest`, and + `pluginRegistry` / `barWidgetRegistry` as appropriate. +- Panel / overlay / menu plugins must expose `open(payloadJson)` and + `close()` lifecycle methods for `shell summon` and `shell hide`. + +IPC: + +- `bin/omarchy-shell` is the canonical IPC entry point. It forwards to + the running shell and does not start it. Prefer it over re-implementing + direct Quickshell socket calls in every CLI. +- The `shell` IPC target exposes `ping`, `summon`, `hide`, `toggle`, + `rescanPlugins`, `setPluginEnabled`, and `listPlugins`. Individual + plugins can register additional IPC targets (the bar registers `bar`, + the background switcher registers `image-selector`). + +Widget files in `shell/plugins/bar/widgets/` contain Nerd Font glyphs as raw +unicode characters. The `Write` and `Edit` tools strip multi-byte +codepoints in some positions — do **not** rewrite widget files wholesale +through those tools. For glyph fixes, use the targeted `Edit` tool with +the surrounding context, or a Python script that inserts codepoints via +`chr(0xXXXXX)`. # Refresh Pattern @@ -104,28 +171,26 @@ To copy a default config to user config with automatic backup: omarchy-refresh-config hypr/hyprlock.conf ``` -This copies `~/.local/share/omarchy/config/hypr/hyprlock.conf` to `~/.config/hypr/hyprlock.conf`. +This copies `/etc/skel/.config/hypr/hyprlock.conf` to `~/.config/hypr/hyprlock.conf`. # Migrations -To create a new migration, run `omarchy-dev-add-migration --no-edit`. This creates a migration file named after the unix timestamp of the last commit. +Read `docs/migrations.md` before creating or changing migrations. + +Migrations are split by execution scope: + +- `migrations/system/.sh` — root, noninteractive, safe to run from pacman via `omarchy-migrate-system` (`omarchy` `post_upgrade` calls it). Use for `/etc`, `/usr`, `/boot`, services, hardware quirks, and other system state. Do not prompt. +- `migrations/user/.sh` — current user/session, may touch `~/.config`, `~/.local`, user systemd, browser prefs, DBus/session state, and may prompt if necessary. Runs through `omarchy-migrate` / `omarchy-migrate-user`; pending state is per-user based on missing files under `~/.local/state/omarchy/migrations/user/`. + +To create a new migration, run `omarchy-dev-add-migration system --no-edit` or `omarchy-dev-add-migration user --no-edit` based on scope. New migration format: -- File permissions must be `0644` (`-rw-r--r--`); migrations are sourced, not executed directly +- File permissions must be `0644` (`-rw-r--r--`); migration runners execute them with `bash -euo pipefail`, not through executable bits - No shebang line - Start with an `echo` describing what the migration does - Use `$OMARCHY_PATH` to reference the omarchy directory - Prefer helper commands such as `omarchy-cmd-present`, `omarchy-cmd-missing`, `omarchy-pkg-present`, and `omarchy-pkg-missing` -Some older migrations predate these rules. Do not copy older migrations that start with shebangs, omit the leading `echo`, or hard-code `~/.local/share/omarchy`. +Omarchy 4.0 is upgraded through `bin/omarchy-upgrade-to-4`, not through the normal migration runner. Do not add compatibility migrations for old installer layouts; put pre-4 package-layout transition work in the upgrade command instead. -Migrations may use raw `pacman`, `command -v`, or direct config edits when needed for historical compatibility or one-off repair work. - -Example: -```bash -echo "Disable fingerprint in hyprlock if fingerprint auth is not configured" - -if omarchy-cmd-missing fprintd-list || ! fprintd-list "$USER" 2>/dev/null | grep -q "finger"; then - sed -i 's/fingerprint:enabled = .*/fingerprint:enabled = false/' ~/.config/hypr/hyprlock.conf -fi -``` +Migrations may use raw `pacman`, `command -v`, or direct config edits when needed for one-off repair work. diff --git a/README.md b/README.md index a630d13c..4bd515f1 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Omarchy is a beautiful, modern & opinionated Linux distribution by DHH. Read more at [omarchy.org](https://omarchy.org). +## Docs + +- [omarchy-shell](docs/omarchy-shell.md) — shell host, plugin manifest, IPC, `shell.json`, custom bar modules + ## License Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT). diff --git a/applications/Basecamp.desktop b/applications/Basecamp.desktop new file mode 100644 index 00000000..7c518ba4 --- /dev/null +++ b/applications/Basecamp.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Basecamp +Exec=omarchy-launch-webapp https://launchpad.37signals.com +Terminal=false +Type=Application +Icon=basecamp +StartupNotify=true diff --git a/applications/ChatGPT.desktop b/applications/ChatGPT.desktop new file mode 100644 index 00000000..2ec04673 --- /dev/null +++ b/applications/ChatGPT.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=ChatGPT +Exec=omarchy-launch-webapp https://chatgpt.com/ +Terminal=false +Type=Application +Icon=chatgpt +StartupNotify=true diff --git a/applications/Discord.desktop b/applications/Discord.desktop new file mode 100644 index 00000000..6a2039ca --- /dev/null +++ b/applications/Discord.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Discord +Exec=omarchy-launch-webapp https://discord.com/channels/@me +Terminal=false +Type=Application +Icon=discord +StartupNotify=true diff --git a/applications/Disk Usage.desktop b/applications/Disk Usage.desktop new file mode 100644 index 00000000..8a18c798 --- /dev/null +++ b/applications/Disk Usage.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Disk Usage +Exec=xdg-terminal-exec --app-id=TUI.float -e bash -c "dust -r; read -n 1 -s" +Terminal=false +Type=Application +Icon=disk-usage +StartupNotify=true diff --git a/applications/Docker.desktop b/applications/Docker.desktop new file mode 100644 index 00000000..4f9831c0 --- /dev/null +++ b/applications/Docker.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Docker +Exec=xdg-terminal-exec --app-id=TUI.tile -e lazydocker +Terminal=false +Type=Application +Icon=docker +StartupNotify=true diff --git a/applications/Figma.desktop b/applications/Figma.desktop new file mode 100644 index 00000000..92be688a --- /dev/null +++ b/applications/Figma.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Figma +Exec=omarchy-launch-webapp https://figma.com/ +Terminal=false +Type=Application +Icon=figma +StartupNotify=true diff --git a/applications/Fizzy.desktop b/applications/Fizzy.desktop new file mode 100644 index 00000000..062d1870 --- /dev/null +++ b/applications/Fizzy.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Fizzy +Exec=omarchy-launch-webapp https://app.fizzy.do/ +Terminal=false +Type=Application +Icon=fizzy +StartupNotify=true diff --git a/applications/GitHub.desktop b/applications/GitHub.desktop new file mode 100644 index 00000000..76d09b86 --- /dev/null +++ b/applications/GitHub.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=GitHub +Exec=omarchy-launch-webapp https://github.com/ +Terminal=false +Type=Application +Icon=github +StartupNotify=true diff --git a/applications/Google Contacts.desktop b/applications/Google Contacts.desktop new file mode 100644 index 00000000..8f92c882 --- /dev/null +++ b/applications/Google Contacts.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Google Contacts +Exec=omarchy-launch-webapp https://contacts.google.com/ +Terminal=false +Type=Application +Icon=google-contacts +StartupNotify=true diff --git a/applications/Google Maps.desktop b/applications/Google Maps.desktop new file mode 100644 index 00000000..3cf546b6 --- /dev/null +++ b/applications/Google Maps.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Google Maps +Exec=omarchy-launch-webapp https://maps.google.com +Terminal=false +Type=Application +Icon=google-maps +StartupNotify=true diff --git a/applications/Google Messages.desktop b/applications/Google Messages.desktop new file mode 100644 index 00000000..18474555 --- /dev/null +++ b/applications/Google Messages.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Google Messages +Exec=omarchy-launch-webapp https://messages.google.com/web/conversations +Terminal=false +Type=Application +Icon=google-messages +StartupNotify=true diff --git a/applications/Google Photos.desktop b/applications/Google Photos.desktop new file mode 100644 index 00000000..44ccd254 --- /dev/null +++ b/applications/Google Photos.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=Google Photos +Exec=omarchy-launch-webapp https://photos.google.com/ +Terminal=false +Type=Application +Icon=google-photos +StartupNotify=true diff --git a/applications/HEY.desktop b/applications/HEY.desktop new file mode 100644 index 00000000..e59bd0ea --- /dev/null +++ b/applications/HEY.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=HEY +Exec=omarchy-webapp-handler-hey %u +Terminal=false +Type=Application +Icon=hey +StartupNotify=true +MimeType=x-scheme-handler/mailto diff --git a/applications/WhatsApp.desktop b/applications/WhatsApp.desktop new file mode 100644 index 00000000..f193616d --- /dev/null +++ b/applications/WhatsApp.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=WhatsApp +Exec=omarchy-launch-webapp https://web.whatsapp.com/ +Terminal=false +Type=Application +Icon=whatsapp +StartupNotify=true diff --git a/applications/X.desktop b/applications/X.desktop new file mode 100644 index 00000000..02fe0424 --- /dev/null +++ b/applications/X.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=X +Exec=omarchy-launch-webapp https://x.com/ +Terminal=false +Type=Application +Icon=x +StartupNotify=true diff --git a/applications/YouTube.desktop b/applications/YouTube.desktop new file mode 100644 index 00000000..d6659d75 --- /dev/null +++ b/applications/YouTube.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Name=YouTube +Exec=omarchy-launch-webapp https://youtube.com/ +Terminal=false +Type=Application +Icon=youtube +StartupNotify=true diff --git a/applications/Zoom.desktop b/applications/Zoom.desktop new file mode 100644 index 00000000..227abaa8 --- /dev/null +++ b/applications/Zoom.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Version=1.0 +Name=Zoom +Exec=omarchy-webapp-handler-zoom %u +Terminal=false +Type=Application +Icon=zoom +StartupNotify=true +MimeType=x-scheme-handler/zoommtg;x-scheme-handler/zoomus diff --git a/applications/battlenet.desktop b/applications/battlenet.desktop new file mode 100644 index 00000000..f1499c72 --- /dev/null +++ b/applications/battlenet.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Battle.net +GenericName=Game Launcher +Comment=Blizzard game launcher (umu-launcher + GE-Proton) +Exec=omarchy-launch-battlenet +Icon=battle-net +Terminal=false +Categories=Game; +StartupNotify=true +StartupWMClass=battle.net.exe diff --git a/default/foot/foot.desktop b/applications/foot.desktop similarity index 100% rename from default/foot/foot.desktop rename to applications/foot.desktop diff --git a/applications/hidden/avahi-discover.desktop b/applications/hidden/avahi-discover.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/avahi-discover.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/bssh.desktop b/applications/hidden/bssh.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/bssh.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/btop.desktop b/applications/hidden/btop.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/btop.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/bvnc.desktop b/applications/hidden/bvnc.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/bvnc.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/cmake-gui.desktop b/applications/hidden/cmake-gui.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/cmake-gui.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/cups.desktop b/applications/hidden/cups.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/cups.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/dropbox.desktop b/applications/hidden/dropbox.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/dropbox.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/electron34.desktop b/applications/hidden/electron34.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/electron34.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/electron36.desktop b/applications/hidden/electron36.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/electron36.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/electron37.desktop b/applications/hidden/electron37.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/electron37.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/fcitx5-configtool.desktop b/applications/hidden/fcitx5-configtool.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/fcitx5-configtool.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/fcitx5-wayland-launcher.desktop b/applications/hidden/fcitx5-wayland-launcher.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/fcitx5-wayland-launcher.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/foot-server.desktop b/applications/hidden/foot-server.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/foot-server.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/footclient.desktop b/applications/hidden/footclient.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/footclient.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/java-java-openjdk.desktop b/applications/hidden/java-java-openjdk.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/java-java-openjdk.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/jconsole-java-openjdk.desktop b/applications/hidden/jconsole-java-openjdk.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/jconsole-java-openjdk.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/jshell-java-openjdk.desktop b/applications/hidden/jshell-java-openjdk.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/jshell-java-openjdk.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/kbd-layout-viewer5.desktop b/applications/hidden/kbd-layout-viewer5.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/kbd-layout-viewer5.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/kcm_fcitx5.desktop b/applications/hidden/kcm_fcitx5.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/kcm_fcitx5.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/kcm_kaccounts.desktop b/applications/hidden/kcm_kaccounts.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/kcm_kaccounts.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/kvantummanager.desktop b/applications/hidden/kvantummanager.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/kvantummanager.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/limine-snapper-restore.desktop b/applications/hidden/limine-snapper-restore.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/limine-snapper-restore.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/lstopo.desktop b/applications/hidden/lstopo.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/lstopo.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/org.fcitx.Fcitx5.desktop b/applications/hidden/org.fcitx.Fcitx5.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/org.fcitx.Fcitx5.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/org.fcitx.fcitx5-config-qt.desktop b/applications/hidden/org.fcitx.fcitx5-config-qt.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/org.fcitx.fcitx5-config-qt.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/org.fcitx.fcitx5-migrator.desktop b/applications/hidden/org.fcitx.fcitx5-migrator.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/org.fcitx.fcitx5-migrator.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/org.fcitx.fcitx5-qt5-gui-wrapper.desktop b/applications/hidden/org.fcitx.fcitx5-qt5-gui-wrapper.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/org.fcitx.fcitx5-qt5-gui-wrapper.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/org.fcitx.fcitx5-qt6-gui-wrapper.desktop b/applications/hidden/org.fcitx.fcitx5-qt6-gui-wrapper.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/org.fcitx.fcitx5-qt6-gui-wrapper.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/qv4l2.desktop b/applications/hidden/qv4l2.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/qv4l2.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/qvidcap.desktop b/applications/hidden/qvidcap.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/qvidcap.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/uuctl.desktop b/applications/hidden/uuctl.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/uuctl.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/wiremix.desktop b/applications/hidden/wiremix.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/wiremix.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/xgps.desktop b/applications/hidden/xgps.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/xgps.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/hidden/xgpsspeed.desktop b/applications/hidden/xgpsspeed.desktop deleted file mode 100644 index e1e3e173..00000000 --- a/applications/hidden/xgpsspeed.desktop +++ /dev/null @@ -1,2 +0,0 @@ -[Desktop Entry] -Hidden=true diff --git a/applications/icons/Battle.net.png b/applications/icons/Battle.net.png new file mode 100644 index 00000000..a18e41c7 Binary files /dev/null and b/applications/icons/Battle.net.png differ diff --git a/applications/icons/Retro Gaming.png b/applications/icons/Retro Gaming.png new file mode 100644 index 00000000..313172bc Binary files /dev/null and b/applications/icons/Retro Gaming.png differ diff --git a/bin/omarchy b/bin/omarchy index c23a6457..2c8433f9 100755 --- a/bin/omarchy +++ b/bin/omarchy @@ -27,19 +27,25 @@ declare -A BINARY_TO_KEY declare -A GROUP_DESCRIPTIONS GROUP_DESCRIPTIONS[ac]="AC power detection" +GROUP_DESCRIPTIONS[audio]="Audio input and output controls" GROUP_DESCRIPTIONS[battery]="Battery status helpers" +GROUP_DESCRIPTIONS[bluetooth]="Bluetooth device controls" GROUP_DESCRIPTIONS[branch]="Omarchy git branch management" GROUP_DESCRIPTIONS[branding]="About and screensaver branding" GROUP_DESCRIPTIONS[brightness]="Display and keyboard brightness" GROUP_DESCRIPTIONS[capture]="Screenshots and screen recording" GROUP_DESCRIPTIONS[channel]="Omarchy release channel management" +GROUP_DESCRIPTIONS[clipboard]="Clipboard helpers" GROUP_DESCRIPTIONS[cmd]="Command and shortcut helpers" GROUP_DESCRIPTIONS[config]="System configuration helpers" GROUP_DESCRIPTIONS[debug]="Diagnostics and support logs" +GROUP_DESCRIPTIONS[finalize]="Finalize user setup" GROUP_DESCRIPTIONS[default]="Default application selection" GROUP_DESCRIPTIONS[dev]="Omarchy development tools" +GROUP_DESCRIPTIONS[dns]="DNS resolver configuration" GROUP_DESCRIPTIONS[drive]="Drive selection and encryption" GROUP_DESCRIPTIONS[font]="Font management" +GROUP_DESCRIPTIONS[games]="Game launchers and helpers" GROUP_DESCRIPTIONS[hibernation]="Hibernation setup and removal" GROUP_DESCRIPTIONS[hook]="User hook runner" GROUP_DESCRIPTIONS[hw]="Hardware detection and controls" @@ -48,9 +54,13 @@ GROUP_DESCRIPTIONS[install]="Optional software installers" GROUP_DESCRIPTIONS[launch]="Application launchers" GROUP_DESCRIPTIONS[menu]="Omarchy menu commands" GROUP_DESCRIPTIONS[migrate]="Migration runner" +GROUP_DESCRIPTIONS[monitor]="Monitor status helpers" +GROUP_DESCRIPTIONS[network]="Network status helpers" GROUP_DESCRIPTIONS[notification]="Notification helpers" -GROUP_DESCRIPTIONS[npx]="NPX package wrappers" +GROUP_DESCRIPTIONS[mise]="Mise tool wrappers" +GROUP_DESCRIPTIONS[osd]="On-screen display status helpers" GROUP_DESCRIPTIONS[pkg]="Package management helpers" +GROUP_DESCRIPTIONS[plugin]="Omarchy shell plugin and bar widget management" GROUP_DESCRIPTIONS[plymouth]="Plymouth boot theme management" GROUP_DESCRIPTIONS[powerprofiles]="Power profile management" GROUP_DESCRIPTIONS[refresh]="Reset config to defaults" @@ -59,11 +69,12 @@ GROUP_DESCRIPTIONS[reminder]="Desktop notification reminders" GROUP_DESCRIPTIONS[remove]="Removal workflows" GROUP_DESCRIPTIONS[restart]="Restart Omarchy components" GROUP_DESCRIPTIONS[setup]="Interactive setup wizards" +GROUP_DESCRIPTIONS[shell]="Omarchy shell IPC helpers" GROUP_DESCRIPTIONS[screensaver]="Screensaver branding and animation" GROUP_DESCRIPTIONS[snapshot]="System snapshots" +GROUP_DESCRIPTIONS[style]="Global UI style controls" GROUP_DESCRIPTIONS[sudo]="Sudo configuration helpers" -GROUP_DESCRIPTIONS[swayosd]="SwayOSD status display helpers" -GROUP_DESCRIPTIONS[system]="Reboot, shutdown, logout, and lock" +GROUP_DESCRIPTIONS[system]="System status, reboot, shutdown, logout, and lock" GROUP_DESCRIPTIONS[theme]="Theme management" GROUP_DESCRIPTIONS[toggle]="Toggle Omarchy features" GROUP_DESCRIPTIONS[transcode]="Image and video transcoding" diff --git a/bin/omarchy-audio-input-mute b/bin/omarchy-audio-input-mute index 19e12f23..554eae44 100755 --- a/bin/omarchy-audio-input-mute +++ b/bin/omarchy-audio-input-mute @@ -4,18 +4,10 @@ wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle >/dev/null -if pactl get-source-mute @DEFAULT_SOURCE@ | rg -q 'yes'; then - led=on - osd_message='Microphone muted' - osd_icon='microphone-sensitivity-muted-symbolic' +if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then + omarchy-brightness-keyboard-mute on + omarchy-osd -i microphone-muted -m "Microphone muted" else - led=off - osd_message='Microphone on' - osd_icon='audio-input-microphone-symbolic' + omarchy-brightness-keyboard-mute off + omarchy-osd -i microphone -m "Microphone on" fi - -omarchy-brightness-keyboard-mute "$led" - -omarchy-swayosd-client \ - --custom-message "$osd_message" \ - --custom-icon "$osd_icon" diff --git a/bin/omarchy-audio-input-set-default b/bin/omarchy-audio-input-set-default new file mode 100755 index 00000000..280042d2 --- /dev/null +++ b/bin/omarchy-audio-input-set-default @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Set the default audio input and move active streams +# omarchy:args= +# omarchy:examples=omarchy audio input set default 43 alsa_input.pci-0000_00_1f.3.analog-stereo + +node_id=${1:-} +source_name=${2:-} + +if [[ -z $node_id || -z $source_name ]]; then + echo "Usage: omarchy-audio-input-set-default " >&2 + exit 1 +fi + +wpctl set-default "$node_id" 2>/dev/null || true +pactl set-default-source "$source_name" 2>/dev/null || true + +pactl list short source-outputs 2>/dev/null | awk '{ print $1 }' | while read -r output; do + [[ -n $output ]] && pactl move-source-output "$output" "$source_name" 2>/dev/null || true +done diff --git a/bin/omarchy-audio-output-set-default b/bin/omarchy-audio-output-set-default new file mode 100755 index 00000000..5f7d8f04 --- /dev/null +++ b/bin/omarchy-audio-output-set-default @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Set the default audio output and move active streams +# omarchy:args= +# omarchy:examples=omarchy audio output set default 42 alsa_output.pci-0000_00_1f.3.analog-stereo + +node_id=${1:-} +sink_name=${2:-} + +if [[ -z $node_id || -z $sink_name ]]; then + echo "Usage: omarchy-audio-output-set-default " >&2 + exit 1 +fi + +timeout 2 wpctl set-default "$node_id" 2>/dev/null || true +timeout 2 pactl set-default-sink "$sink_name" 2>/dev/null || true + +timeout 2 pactl list short sink-inputs 2>/dev/null | awk '{ print $1 }' | while read -r input; do + [[ -n $input ]] && timeout 2 pactl move-sink-input "$input" "$sink_name" 2>/dev/null || true +done diff --git a/bin/omarchy-audio-output-switch b/bin/omarchy-audio-output-switch index f5f94d44..73af2246 100755 --- a/bin/omarchy-audio-output-switch +++ b/bin/omarchy-audio-output-switch @@ -1,17 +1,17 @@ #!/bin/bash -# omarchy:summary=Switch between audio outputs while preserving the mute status. By default mapped to Super + Mute. +# omarchy:summary=Switch between audio outputs while preserving the mute status -sinks=$(pactl -f json list sinks | jq '[.[] | select((.ports | length == 0) or ([.ports[]? | .availability != "not available"] | any))]') -sinks_count=$(echo "$sinks" | jq '. | length') +sinks=$(timeout 2 pactl -f json list sinks | jq '[.[] | select((.ports | length == 0) or ([.ports[]? | .availability != "not available"] | any))]') +sinks_count=$(jq 'length' <<<"$sinks") if (( sinks_count == 0 )); then - omarchy-swayosd-client --custom-message "No audio devices found" + omarchy-osd -m "No audio devices found" exit 1 fi -current_sink_name=$(pactl get-default-sink) -current_sink_index=$(echo "$sinks" | jq -r --arg name "$current_sink_name" 'map(.name) | index($name)') +current_sink_name=$(timeout 2 pactl get-default-sink) +current_sink_index=$(jq -r --arg name "$current_sink_name" 'map(.name) | index($name)' <<<"$sinks") if [[ $current_sink_index != "null" ]]; then next_sink_index=$(((current_sink_index + 1) % sinks_count)) @@ -19,28 +19,13 @@ else next_sink_index=0 fi -next_sink=$(echo "$sinks" | jq -r ".[$next_sink_index]") -next_sink_name=$(echo "$next_sink" | jq -r '.name') +next_sink=$(jq -c ".[$next_sink_index]" <<<"$sinks") +next_sink_name=$(jq -r '.name' <<<"$next_sink") +next_sink_description=$(jq -r '.description // .properties."device.description" // .name' <<<"$next_sink") +next_sink_volume=$(jq -r '.volume | to_entries[0].value.value_percent | sub("%"; "") | tonumber' <<<"$next_sink") +next_sink_is_muted=$(jq -r '.mute' <<<"$next_sink") -next_sink_description=$(echo "$next_sink" | jq -r '.description') -if [[ $next_sink_description == "(null)" ]] || [[ $next_sink_description == "null" ]] || [[ -z $next_sink_description ]]; then - # For Bluetooth devices, the friendly name is on the Device entry (device.id), not the Sink entry (object.id) - device_id=$(echo "$next_sink" | jq -r '.properties."device.id"') - if [[ $device_id != "null" ]] && [[ -n $device_id ]]; then - next_sink_description=$(wpctl status | grep -E "^\s*│?\s+${device_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//') - fi - # Fall back to object.id lookup if device.id didn't yield a result - if [[ -z $next_sink_description ]]; then - sink_id=$(echo "$next_sink" | jq -r '.properties."object.id"') - next_sink_description=$(wpctl status | grep -E "\s+\*?\s+${sink_id}\." | sed -E 's/^.*[0-9]+\.\s+//' | sed -E 's/\s+\[.*$//') - fi -fi - -next_sink_volume=$(echo "$next_sink" | jq -r \ - '.volume | to_entries[0].value.value_percent | sub("%"; "")') -next_sink_is_muted=$(echo "$next_sink" | jq -r '.mute') - -if [[ $next_sink_is_muted = "true" ]] || (( next_sink_volume == 0 )); then +if [[ $next_sink_is_muted == "true" ]] || (( next_sink_volume == 0 )); then icon_state="muted" elif (( next_sink_volume <= 33 )); then icon_state="low" @@ -50,13 +35,8 @@ else icon_state="high" fi -next_sink_volume_icon="sink-volume-${icon_state}-symbolic" - if [[ $next_sink_name != $current_sink_name ]]; then - next_sink_wpid=$(echo "$next_sink" | jq -r '.properties."object.id"') - wpctl set-default "$next_sink_wpid" + omarchy-audio-output-set-default "$(jq -r '.index' <<<"$next_sink")" "$next_sink_name" fi -omarchy-swayosd-client \ - --custom-message "$next_sink_description" \ - --custom-icon "$next_sink_volume_icon" +omarchy-osd -i "volume-${icon_state}" -m "$next_sink_description" diff --git a/bin/omarchy-audio-output-volume b/bin/omarchy-audio-output-volume new file mode 100755 index 00000000..cc9a02c2 --- /dev/null +++ b/bin/omarchy-audio-output-volume @@ -0,0 +1,67 @@ +#!/bin/bash + +# omarchy:summary=Adjust output volume and show the Omarchy OSD +# omarchy:args= +# omarchy:examples=omarchy audio output volume raise | omarchy audio output volume lower | omarchy audio output volume mute-toggle | omarchy audio output volume +1 + +action="${1:-}" + +if [[ -z $action ]]; then + echo "Usage: omarchy-audio-output-volume " + exit 1 +fi + +volume_state() { + wpctl get-volume @DEFAULT_AUDIO_SINK@ +} + +volume_percent() { + volume_state | awk '{ for (i=1; i<=NF; i++) if ($i ~ /^[0-9.]+$/) print int($i * 100) }' +} + +volume_muted() { + volume_state | grep -q MUTED +} + +unmute_output() { + wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 >/dev/null +} + +case "$action" in + raise) action="+5" ;; + lower) action="-5" ;; +esac + +if [[ $action == "mute-toggle" ]]; then + runtime_dir="${XDG_RUNTIME_DIR:-/tmp}" + debounce_file="$runtime_dir/omarchy-audio-output-volume-mute-toggle.last" + now=$(date +%s%3N) + last=0 + [[ -r $debounce_file ]] && read -r last < "$debounce_file" || true + if (( now - last < 250 )); then + exit 0 + fi + printf '%s\n' "$now" > "$debounce_file" + + wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle >/dev/null +elif [[ $action == +* ]]; then + step="${action#+}" + unmute_output + wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ "${step}%+" +elif [[ $action == -* ]]; then + step="${action#-}" + unmute_output + wpctl set-volume @DEFAULT_AUDIO_SINK@ "${step}%-" +else + echo "Unknown volume action: $action" + exit 1 +fi + +percent=$(volume_percent) +if volume_muted || (( percent == 0 )); then + icon="volume-muted" +else + icon="volume-high" +fi + +omarchy-osd -i "$icon" -p "$percent" diff --git a/bin/omarchy-audio-sink-availability b/bin/omarchy-audio-sink-availability new file mode 100755 index 00000000..0f333e26 --- /dev/null +++ b/bin/omarchy-audio-sink-availability @@ -0,0 +1,45 @@ +#!/bin/bash + +# omarchy:summary=Print PulseAudio sink availability for the shell +# omarchy:group=audio + +pactl list sinks 2>/dev/null | awk ' + function emit_sink() { + if (name == "") return + print name "\t" ((port_count == 0 || available) ? 1 : 0) + } + + /^Sink #/ { + emit_sink() + name = "" + in_ports = 0 + port_count = 0 + available = 0 + next + } + + /^[[:space:]]*Name:/ { + name = $2 + next + } + + /^[[:space:]]*Ports:$/ { + in_ports = 1 + next + } + + in_ports && /^\tActive Port:/ { + in_ports = 0 + next + } + + in_ports && /^\t\t/ { + port_count++ + if ($0 !~ /not available/) available = 1 + next + } + + END { + emit_sink() + } +' diff --git a/bin/omarchy-audio-source-switch b/bin/omarchy-audio-source-switch new file mode 100755 index 00000000..1b452133 --- /dev/null +++ b/bin/omarchy-audio-source-switch @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Cycle to the next media source and transfer playback when the current source is playing +# omarchy:args=[next|previous] +# omarchy:examples=omarchy audio source switch | omarchy-audio-source-switch previous + +direction="${1:-next}" + +case "$direction" in + next) + omarchy-shell media sourceSwitch + ;; + previous) + omarchy-shell media sourceSwitchPrevious + ;; + *) + echo "Usage: omarchy-audio-source-switch [next|previous]" >&2 + exit 1 + ;; +esac diff --git a/bin/omarchy-battery-low b/bin/omarchy-battery-low new file mode 100755 index 00000000..d107d4f8 --- /dev/null +++ b/bin/omarchy-battery-low @@ -0,0 +1,17 @@ +#!/bin/bash + +# omarchy:summary=Send the low battery warning notification and run battery-low hooks. +# omarchy:args= +# omarchy:hidden=true + +set -euo pipefail + +if (($# != 1)); then + echo "Usage: omarchy-battery-low " >&2 + exit 1 +fi + +level=$1 + +omarchy-notification-send -g 󱐋 -u critical "Time to recharge!" "Battery is down to ${level}%" -i battery-caution -t 30000 +omarchy-hook battery-low "$level" diff --git a/bin/omarchy-battery-monitor b/bin/omarchy-battery-monitor deleted file mode 100755 index 1238791e..00000000 --- a/bin/omarchy-battery-monitor +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Designed to be run by systemd timer every 30 seconds and alerts if battery is low -# omarchy:hidden=true - -BATTERY_THRESHOLD=10 -NOTIFICATION_FLAG="/run/user/$UID/omarchy_battery_notified" -BATTERY_LEVEL=$(omarchy-battery-remaining) -BATTERY_STATE=$(upower -i $(upower -e | grep 'BAT') | grep -E "state" | awk '{print $2}') - -send_notification() { - notify-send -u critical "󱐋 Time to recharge!" "Battery is down to ${1}%" -i battery-caution -t 30000 - omarchy-hook battery-low "$1" -} - -if [[ -n $BATTERY_LEVEL && $BATTERY_LEVEL =~ ^[0-9]+$ ]]; then - if [[ $BATTERY_STATE == "discharging" ]] && (( BATTERY_LEVEL <= BATTERY_THRESHOLD )); then - if [[ ! -f $NOTIFICATION_FLAG ]]; then - send_notification $BATTERY_LEVEL - touch $NOTIFICATION_FLAG - fi - else - rm -f $NOTIFICATION_FLAG - fi -fi diff --git a/bin/omarchy-battery-status b/bin/omarchy-battery-status index 0390df1f..ac81e9d6 100755 --- a/bin/omarchy-battery-status +++ b/bin/omarchy-battery-status @@ -1,27 +1,108 @@ #!/bin/bash # omarchy:summary=Returns a formatted battery status string with percentage and power draw/charge. +# omarchy:args=[--shell] -battery_info=$(upower -i $(upower -e | grep BAT)) +shell_output=false -percentage=$(echo "$battery_info" | awk '/percentage/ { - print int($2) - exit -}') +case "${1:-}" in + "") + ;; + --shell) + shell_output=true + ;; + *) + echo "Usage: omarchy-battery-status [--shell]" >&2 + exit 2 + ;; +esac -power_rate=$(echo "$battery_info" | awk '/energy-rate/ { - rounded = sprintf("%.1f", $2) - sub(/\.0$/, "", rounded) - print rounded - exit -}') +battery=$(upower -e 2>/dev/null | grep BAT | head -n 1) +[[ -z $battery ]] && exit 0 -state=$(echo "$battery_info" | awk '/state/ { print $2; exit }') -time_remaining=$(omarchy-battery-remaining-time) -capacity=$(omarchy-battery-capacity) +battery_info=$(upower -i "$battery") -if [[ $state == "charging" ]]; then - echo "󰁹 Battery ${percentage}% · ${time_remaining} to full ·  ${power_rate}W / ${capacity}Wh" +percentage=$(awk '/percentage/ { print int($2); exit }' <<<"$battery_info") +power_rate_raw=$(awk '/energy-rate/ { print $2; exit }' <<<"$battery_info") +power_rate=$(awk '/energy-rate/ { + rounded = sprintf("%.1f", $2) + sub(/\.0$/, "", rounded) + print rounded + exit +}' <<<"$battery_info") +state=$(awk '/state/ { print $2; exit }' <<<"$battery_info") +time_remaining=$(omarchy-battery-remaining-time 2>/dev/null) +capacity=$(omarchy-battery-capacity 2>/dev/null) +threshold_start=$(awk '/charge-start-threshold:/ { gsub(/%/, "", $2); print int($2); exit }' <<<"$battery_info") +threshold_end=$(awk '/charge-end-threshold:/ { gsub(/%/, "", $2); print int($2); exit }' <<<"$battery_info") + +[[ -z $threshold_end ]] && threshold_end=$(cat /sys/class/power_supply/BAT*/charge_control_end_threshold 2>/dev/null | head -1) +[[ -z $threshold_start ]] && threshold_start=$(cat /sys/class/power_supply/BAT*/charge_control_start_threshold 2>/dev/null | head -1) + +ac_online=false +for supply in /sys/class/power_supply/*; do + [[ -r $supply/type ]] || continue + [[ $(<"$supply/type") == "Mains" ]] || continue + [[ -r $supply/online ]] || continue + + if [[ $(<"$supply/online") == "1" ]]; then + ac_online=true + break + fi +done + +charge_idle=false +if awk -v rate="${power_rate_raw:-0}" 'BEGIN { exit !(rate <= 0.2) }'; then + charge_idle=true +fi + +charge_holding=false +if [[ $ac_online == "true" && -n $threshold_end ]]; then + if [[ $state == "pending-charge" ]]; then + charge_holding=true + elif [[ $state == "fully-charged" ]] && (( percentage < 99 )); then + charge_holding=true + elif [[ $state == "charging" && $charge_idle == "true" ]] && (( threshold_end < 99 && percentage >= threshold_end )); then + charge_holding=true + fi +fi + +if [[ $shell_output == "true" ]]; then + printf 'percentage\t%s\n' "${percentage}%" + if [[ $charge_holding == "true" ]]; then + printf 'state\tholding\n' + else + printf 'state\t%s\n' "$state" + fi + printf 'rate\t%s\n' "${power_rate}W" + printf 'size\t%s\n' "${capacity}Wh" + printf 'time\t%s\n' "$time_remaining" + + cycles=$(cat /sys/class/power_supply/BAT*/cycle_count 2>/dev/null | head -1) + + [[ -n $cycles ]] && printf 'cycles\t%s\n' "$cycles" + + if [[ -n $threshold_end ]]; then + if [[ -n $threshold_start && $threshold_start != $threshold_end ]]; then + printf 'threshold\t%s-%s%%\n' "$threshold_start" "$threshold_end" + else + printf 'threshold\t%s%%\n' "$threshold_end" + fi + fi + + exit 0 +fi + +if [[ $charge_holding == "true" ]]; then + if [[ -n $threshold_start && $threshold_start != $threshold_end ]]; then + threshold_label="${threshold_start}-${threshold_end}%" + else + threshold_label="${threshold_end}%" + fi + + echo "Battery ${percentage}% · Holding at ${threshold_label} · ${power_rate}W / ${capacity}Wh" +elif [[ $state == "charging" ]]; then + echo "Battery ${percentage}% · ${time_remaining} to full ·  ${power_rate}W / ${capacity}Wh" else - echo "󰁹 Battery ${percentage}% · ${time_remaining} left ·  ${power_rate}W / ${capacity}Wh" + echo "Battery ${percentage}% · ${time_remaining} left ·  ${power_rate}W / ${capacity}Wh" fi diff --git a/bin/omarchy-bluetooth-device b/bin/omarchy-bluetooth-device new file mode 100755 index 00000000..d121b7c7 --- /dev/null +++ b/bin/omarchy-bluetooth-device @@ -0,0 +1,50 @@ +#!/bin/bash + +# omarchy:summary=Control a Bluetooth device +# omarchy:group=bluetooth +# omarchy:args=[pair|connect|disconnect|forget]
+# omarchy:examples=omarchy bluetooth device connect 00:11:22:33:44:55 + +set -e + +usage() { + echo "Usage: omarchy-bluetooth-device [pair|connect|disconnect|forget]
" >&2 + exit 1 +} + +action=${1:-} +address=${2:-} + +[[ $action == "pair" || $action == "connect" || $action == "disconnect" || $action == "forget" ]] || usage +[[ $address =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]] || usage + +power_on() { + bluetoothctl power on >/dev/null 2>&1 || true + sleep 0.5 +} + +trust_device() { + bluetoothctl trust "$address" >/dev/null 2>&1 || true +} + +case "$action" in + pair) + power_on + timeout 20s bluetoothctl pair "$address" >/dev/null 2>&1 || true + trust_device + timeout 20s bluetoothctl connect "$address" >/dev/null 2>&1 || true + ;; + connect) + power_on + trust_device + timeout 20s bluetoothctl connect "$address" >/dev/null 2>&1 || true + ;; + disconnect) + timeout 10s bluetoothctl disconnect "$address" >/dev/null 2>&1 || true + ;; + forget) + power_on + timeout 10s bluetoothctl disconnect "$address" >/dev/null 2>&1 || true + timeout 10s bluetoothctl remove "$address" >/dev/null 2>&1 || true + ;; +esac diff --git a/bin/omarchy-branch-set b/bin/omarchy-branch-set deleted file mode 100755 index 931bca25..00000000 --- a/bin/omarchy-branch-set +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Set the branch for Omarchy's git repository. -# omarchy:args= - -if (($# == 0)); then - echo "Usage: omarchy-branch-set [master|rc|dev]" - exit 1 -else - branch="$1" -fi - -if [[ $branch != "master" && $branch != "rc" && $branch != "dev" ]]; then - echo "Error: Invalid branch '$branch'. Must be one of: master, rc, dev" - exit 1 -fi - -git -C $OMARCHY_PATH switch $branch diff --git a/bin/omarchy-branding-about b/bin/omarchy-branding-about index aca234de..f8bfd966 100755 --- a/bin/omarchy-branding-about +++ b/bin/omarchy-branding-about @@ -8,10 +8,12 @@ set -euo pipefail +SOURCE_DIR="${XDG_PICTURES_DIR:-$HOME/Pictures}" + case "${1:-}" in image) - image=$(omarchy-menu-file "Logo image" "$HOME" "svg png") - if [[ -n $image ]] && omarchy-transcode-ascii "$image" ~/.config/omarchy/branding/about.txt --width 54 --height 26 --mode block; then + image=$(omarchy-menu-file "Pick png/svg from $SOURCE_DIR" "${SOURCE_DIR}" "svg png") + if [[ -n $image ]] && omarchy-transcode-ascii "$image" ~/.config/omarchy/branding/about.txt --width 54 --height 26; then omarchy-launch-about >/dev/null 2>&1 fi ;; diff --git a/bin/omarchy-branding-screensaver b/bin/omarchy-branding-screensaver index 57012af8..4aaafc58 100755 --- a/bin/omarchy-branding-screensaver +++ b/bin/omarchy-branding-screensaver @@ -8,9 +8,11 @@ set -euo pipefail +SOURCE_DIR="${XDG_PICTURES_DIR:-$HOME/Pictures}" + case "${1:-}" in image) - image=$(omarchy-menu-file "Logo image" "$HOME" "svg png") + image=$(omarchy-menu-file "Pick png/svg from $SOURCE_DIR" "${SOURCE_DIR}" "svg png") if [[ -n $image ]] && omarchy-transcode-ascii "$image" ~/.config/omarchy/branding/screensaver.txt; then omarchy-launch-screensaver force >/dev/null 2>&1 fi diff --git a/bin/omarchy-brightness-display b/bin/omarchy-brightness-display index 193bdb1b..f672b483 100755 --- a/bin/omarchy-brightness-display +++ b/bin/omarchy-brightness-display @@ -1,35 +1,67 @@ #!/bin/bash -# omarchy:summary=Adjust brightness on the most likely display device. -# omarchy:args=<+N%|N%-|N%|off|on> -# omarchy:examples=omarchy brightness display +5% | omarchy brightness display 5%- | omarchy brightness display 50% | omarchy brightness display off | omarchy brightness display on +# omarchy:summary=Show or adjust brightness on the most likely display device. +# omarchy:args=[--no-osd] [+N%|N%-|N%|off|on] +# omarchy:examples=omarchy brightness display | omarchy brightness display +5% | omarchy brightness display --no-osd 50% | omarchy brightness display off | omarchy brightness display on -step="${1:-+5%}" +no_osd=0 +args=() -# Start with the first possible output, then refine to the most likely given an order heuristic. -device="$(ls -1 /sys/class/backlight 2>/dev/null | head -n1)" -for candidate in amdgpu_bl* intel_backlight acpi_video*; do - if [[ -e /sys/class/backlight/$candidate ]]; then - device="$candidate" - break +for arg in "$@"; do + if [[ $arg == "--no-osd" ]]; then + no_osd=1 + else + args+=("$arg") fi done -if [[ $step == "off" ]]; then - hyprctl dispatch dpms off >/dev/null 2>&1 - exit 0 -elif [[ $step == "on" ]]; then - hyprctl dispatch dpms on >/dev/null 2>&1 - exit 0 -fi +set -- "${args[@]}" -if omarchy-hyprland-monitor-focused-apple; then - omarchy-brightness-display-apple "$step" +# Get the brightness of the passed display +display_brightness() { + brightnessctl -d "$1" -m 2>/dev/null | awk -F, '{ gsub("%", "", $4); print $4; found=1 } END{ exit !found }' +} + +if (( $# == 0 )); then + if omarchy-hyprland-monitor-focused-apple; then + omarchy-brightness-display-apple + exit + fi + + device="$(omarchy-hw-display)" || exit 1 + display_brightness "$device" exit fi +step="$1" + +if [[ $step == "off" ]]; then + hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })' >/dev/null 2>&1 + exit 0 +elif [[ $step == "on" ]]; then + hyprctl dispatch 'hl.dsp.dpms({ action = "enable" })' >/dev/null 2>&1 + exit 0 +fi + +# Drop overlapping brightness key events so concurrent invocations do not race. +# Hardware key repeat present on some devices can otherwise glitch OSD rendering. +exec {lock_fd}>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display.lock" +flock -n "$lock_fd" || exit 0 + +if omarchy-hyprland-monitor-focused-apple; then + if (( no_osd )); then + omarchy-brightness-display-apple --no-osd "$step" + else + omarchy-brightness-display-apple "$step" + fi + exit +fi + +# Current device highlighted +device="$(omarchy-hw-display)" || exit 1 + # Current brightness percentage -current=$(brightnessctl -d "$device" -m | cut -d',' -f4 | tr -d '%') +current=$(display_brightness "$device") || exit 1 # Apply non-uniform step size: 1% steps if at or below 5%, otherwise set an # absolute target percentage to avoid raw backlight rounding causing uneven OSD steps. @@ -53,8 +85,8 @@ elif [[ $step == "5%-" ]]; then step="$target%" fi -# Set the actual brightness of the display device. +# Set brightness of the display device. brightnessctl -d "$device" set "$step" >/dev/null -# Use SwayOSD to display the new brightness setting. -omarchy-swayosd-brightness "$(brightnessctl -d "$device" -m | cut -d',' -f4 | tr -d '%')" +# Show the new brightness in OSD +(( no_osd )) || omarchy-osd -i brightness -p "$(display_brightness "$device")" diff --git a/bin/omarchy-brightness-display-apple b/bin/omarchy-brightness-display-apple index 480b9fe7..3e0d5c8b 100755 --- a/bin/omarchy-brightness-display-apple +++ b/bin/omarchy-brightness-display-apple @@ -1,34 +1,101 @@ #!/bin/bash -# omarchy:summary=Adjust the brightness on Apple Studio Displays and Apple XDR Displays using asdcontrol. -# omarchy:args=<+N%|N%-|N%> -# omarchy:examples=omarchy brightness display apple +5% | omarchy brightness display apple 5%- | omarchy brightness display apple 50% +# omarchy:summary=Show or adjust Apple Studio Display and Apple XDR Display brightness using asdcontrol. +# omarchy:args=[--no-osd] [+N%|N%-|N%] +# omarchy:examples=omarchy brightness display apple | omarchy brightness display apple +5% | omarchy brightness display apple --no-osd 50% -if (( $# == 0 )); then - echo "Adjust Apple Display brightness by passing +5%, 5%-, or 100%" -else - step="$1" - if [[ $step =~ ^([0-9]+)%-$ ]]; then - step="-${BASH_REMATCH[1]}%" +device_cache="${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display-apple.device" +no_osd=0 +args=() + +for arg in "$@"; do + if [[ $arg == "--no-osd" ]]; then + no_osd=1 + else + args+=("$arg") fi +done + +set -- "${args[@]}" + +detect_apple_display_device() { + local devices=() + local path="" - devices=() for path in /dev/usb/hiddev* /dev/hiddev*; do [[ -e $path ]] && devices+=("$path") done - if (( ${#devices[@]} == 0 )); then - echo "No Apple Display HID device found" - exit 1 + (( ${#devices[@]} > 0 )) || return 1 + + sudo asdcontrol --detect "${devices[@]}" 2>/dev/null | awk -F: '/^\/dev\/(usb\/)?hiddev/{ print $1; exit }' +} + +find_apple_display_device() { + local cached="" + local device="" + + if [[ -r $device_cache ]]; then + read -r cached <"$device_cache" || true + if [[ -n $cached && -e $cached ]]; then + printf '%s\n' "$cached" + return 0 + fi fi - device="$(sudo asdcontrol --detect "${devices[@]}" | grep -E '^/dev/(usb/)?hiddev' | cut -d: -f1 | head -n1)" + device="$(detect_apple_display_device)" || return 1 + [[ -n $device ]] || return 1 + + printf '%s\n' "$device" >"$device_cache" + printf '%s\n' "$device" +} + +current_brightness() { + local device="$1" + + sudo asdcontrol "$device" 2>/dev/null | awk -F= ' + /BRIGHTNESS=/ { + print int($2 * 100 / 60000) + found = 1 + } + + END { exit !found } + ' +} + +retry_with_fresh_device() { + rm -f "$device_cache" + device="$(find_apple_display_device || true)" if [[ -z $device ]]; then - echo "No Apple Display HID device found" + echo "No Apple Display HID device found" >&2 exit 1 fi +} - sudo asdcontrol "$device" -- "$step" >/dev/null - value="$(sudo asdcontrol "$device" | awk -F= '/BRIGHTNESS=/{print $2+0}')" - omarchy-swayosd-brightness "$(( value * 100 / 60000 ))" +device="$(find_apple_display_device || true)" +if [[ -z $device ]]; then + echo "No Apple Display HID device found" >&2 + exit 1 fi + +if (( $# == 0 )); then + if current_brightness "$device"; then + exit + else + retry_with_fresh_device + current_brightness "$device" + exit + fi +fi + +step="$1" +if [[ $step =~ ^([0-9]+)%-$ ]]; then + step="-${BASH_REMATCH[1]}%" +fi + +if ! sudo asdcontrol "$device" -- "$step" >/dev/null; then + retry_with_fresh_device + sudo asdcontrol "$device" -- "$step" >/dev/null +fi + +(( no_osd )) || omarchy-osd -i brightness -p "$(current_brightness "$device")" diff --git a/bin/omarchy-brightness-keyboard b/bin/omarchy-brightness-keyboard index 7e5febe6..318c6b1b 100755 --- a/bin/omarchy-brightness-keyboard +++ b/bin/omarchy-brightness-keyboard @@ -1,7 +1,20 @@ #!/bin/bash # omarchy:summary=Adjust keyboard backlight brightness using available steps. -# omarchy:args= +# omarchy:args=[--no-osd] + +no_osd=0 +args=() + +for arg in "$@"; do + if [[ $arg == "--no-osd" ]]; then + no_osd=1 + else + args+=("$arg") + fi +done + +set -- "${args[@]}" direction="${1:-up}" @@ -49,7 +62,4 @@ fi # Set the new brightness. brightnessctl -d "$device" set "$new_brightness" >/dev/null - -# Use SwayOSD to display the new brightness setting. -percent=$((new_brightness * 100 / max_brightness)) -omarchy-swayosd-kbd-brightness "$percent" +(( no_osd )) || omarchy-osd -i keyboard -p "$(( new_brightness * 100 / max_brightness ))" diff --git a/bin/omarchy-capture-screenrecording b/bin/omarchy-capture-screenrecording index 75a5c207..6ed81d9c 100755 --- a/bin/omarchy-capture-screenrecording +++ b/bin/omarchy-capture-screenrecording @@ -2,7 +2,7 @@ # omarchy:summary=Start or stop screen recording # omarchy:group=capture -# omarchy:args=[--with-desktop-audio] [--with-microphone-audio] [--with-webcam] [--webcam-device=] [--resolution=] [--stop-recording] +# omarchy:args=[--fullscreen] [--with-desktop-audio] [--with-microphone-audio] [--with-webcam] [--webcam-device=] [--resolution=] [--stop-recording] # omarchy:examples=omarchy screenrecord | omarchy capture screenrecord --with-desktop-audio # omarchy:aliases=omarchy screenrecord # @@ -31,6 +31,7 @@ MICROPHONE_AUDIO="false" WEBCAM="false" WEBCAM_DEVICE="" RESOLUTION="" +FULLSCREEN="false" STOP_RECORDING="false" RECORDING_FILE="/tmp/omarchy-screenrecord-filename" LOG_FILE=$([[ ${OMARCHY_SCREENRECORD_DEBUG:-false} == "true" ]] && echo "/tmp/omarchy-screenrecord.log" || echo "/dev/null") @@ -42,6 +43,7 @@ for arg in "$@"; do --with-webcam) WEBCAM="true" ;; --webcam-device=*) WEBCAM_DEVICE="${arg#*=}" ;; --resolution=*) RESOLUTION="${arg#*=}" ;; + --fullscreen) FULLSCREEN="true" ;; --stop-recording) STOP_RECORDING="true" ;; esac done @@ -167,7 +169,10 @@ start_screenrecording() { # the portal backend supports and the kms backend doesn't). Default flow uses # slurp + the kms backend, which avoids the EGL DMA-BUF modifier import # failures the portal path can hit on some configurations. - if [[ ${OMARCHY_SCREENRECORD_USE_PORTAL:-false} == "true" ]]; then + if [[ $FULLSCREEN == "true" ]]; then + target="monitor:$(omarchy-hyprland-monitor-focused)" + capture_args=(-w "${target#monitor:}" -s "${RESOLUTION:-$(default_resolution)}") + elif [[ ${OMARCHY_SCREENRECORD_USE_PORTAL:-false} == "true" ]]; then target="portal" capture_args=(-w portal -s "${RESOLUTION:-$(default_resolution)}") else @@ -240,8 +245,9 @@ stop_screenrecording() { ffmpeg -y -i "$filename" -ss 00:00:00.1 -vframes 1 -q:v 2 "$preview" -loglevel quiet 2>/dev/null ( - ACTION=$(notify-send "Screen recording saved" "Open with Super + Alt + , (or click this)" -t 10000 -i "${preview:-$filename}" -A "default=open") - [[ $ACTION == "default" ]] && mpv "$filename" + if [[ -n $(omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" -t 10000 --image "${preview:-$filename}" -a) ]]; then + mpv "$filename" + fi rm -f "$preview" ) & fi @@ -250,7 +256,7 @@ stop_screenrecording() { } toggle_screenrecording_indicator() { - pkill -RTMIN+8 waybar + omarchy-shell -q Indicators refresh } screenrecording_active() { diff --git a/bin/omarchy-capture-screenrecording-with-webcam b/bin/omarchy-capture-screenrecording-with-webcam new file mode 100755 index 00000000..fb35cb8e --- /dev/null +++ b/bin/omarchy-capture-screenrecording-with-webcam @@ -0,0 +1,35 @@ +#!/bin/bash + +# omarchy:summary=Pick a webcam and start a screen recording with it +# omarchy:examples=omarchy capture screenrecording-with-webcam + +set -o pipefail + +webcam_list() { + v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do + if [[ $line != $'\t'* && -n $line ]]; then + local name="$line" + local device + IFS= read -r device || break + device=$(tr -d '\t' <<<"$device" | head -1) + [[ -n $device ]] && echo "$device $name" + fi + done +} + +mapfile -t devices < <(webcam_list) +if (( ${#devices[@]} == 0 )); then + omarchy-notification-send "No webcam devices found" -u critical -t 3000 + exit 1 +fi + +if (( ${#devices[@]} == 1 )); then + device="${devices[0]%%[[:space:]]*}" +else + selection=$(omarchy-menu-select "Select Webcam" "${devices[@]}" -- --width 520 --maxheight 520) || exit 1 + device="${selection%%[[:space:]]*}" +fi + +exec omarchy-capture-screenrecording \ + --with-desktop-audio --with-microphone-audio \ + --with-webcam --webcam-device="$device" diff --git a/bin/omarchy-capture-screenshot b/bin/omarchy-capture-screenshot index 572330f7..ca18d125 100755 --- a/bin/omarchy-capture-screenshot +++ b/bin/omarchy-capture-screenshot @@ -11,7 +11,7 @@ OUTPUT_DIR="${OMARCHY_SCREENSHOT_DIR:-${XDG_PICTURES_DIR:-$HOME/Pictures}}" if [[ ! -d $OUTPUT_DIR ]]; then mkdir -p "$OUTPUT_DIR" - notify-send "Created screenshot directory: $OUTPUT_DIR" -u normal -t 2000 + omarchy-notification-send "Created screenshot directory: $OUTPUT_DIR" -t 2000 fi pkill slurp && exit 0 @@ -36,7 +36,7 @@ open_editor() { --output-filename "$filepath" \ --actions-on-enter save-to-clipboard \ --save-after-copy \ - --copy-command 'wl-copy' + --copy-command 'wl-copy --type image/png' else $SCREENSHOT_EDITOR "$filepath" fi @@ -130,15 +130,16 @@ case "$PROCESSING" in slurp) grim -g "$SELECTION" "$FILEPATH" || exit 1 echo "$FILEPATH" - wl-copy <"$FILEPATH" + wl-copy --type image/png <"$FILEPATH" ( - ACTION=$(notify-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" -t 10000 -i "$FILEPATH" -A "default=edit") - [[ $ACTION == "default" ]] && open_editor "$FILEPATH" + if [[ -n $(omarchy-notification-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" -t 10000 --image "$FILEPATH" -a) ]]; then + open_editor "$FILEPATH" + fi ) >/dev/null 2>&1 & ;; copy) - grim -g "$SELECTION" - | wl-copy + grim -g "$SELECTION" - | wl-copy --type image/png ;; save) grim -g "$SELECTION" "$FILEPATH" || exit 1 diff --git a/bin/omarchy-capture-text-extraction b/bin/omarchy-capture-text-extraction index 4b49b16b..b9b31302 100755 --- a/bin/omarchy-capture-text-extraction +++ b/bin/omarchy-capture-text-extraction @@ -23,4 +23,4 @@ TEXT=$(grim -g "$SELECTION" - | tesseract stdin stdout --oem 1 --psm 6 -l "${OMA [[ -z $TEXT ]] && exit 1 printf "%s" "$TEXT" | wl-copy -notify-send "󰴑 Copied text from selection to clipboard" +omarchy-notification-send -g 󰴑 "Copied text from selection to clipboard" diff --git a/bin/omarchy-channel-set b/bin/omarchy-channel-set index b6c15d71..55d1e1bb 100755 --- a/bin/omarchy-channel-set +++ b/bin/omarchy-channel-set @@ -1,22 +1,21 @@ #!/bin/bash -# omarchy:summary=Set the Omarchy channel, which dictates what git branch and package repository is used. +# omarchy:summary=Set the Omarchy package channel. # omarchy:args= # omarchy:requires-sudo=true +set -e + if (($# == 0)); then echo "Usage: omarchy-channel-set [stable|rc|edge|dev]" exit 1 -else - channel="$1" fi +channel="$1" case "$channel" in -"stable") omarchy-branch-set "master" && omarchy-refresh-pacman "stable" ;; -"rc") omarchy-branch-set "rc" && omarchy-refresh-pacman "rc" ;; -"edge") omarchy-branch-set "master" && omarchy-refresh-pacman "edge" ;; -"dev") omarchy-branch-set "dev" && omarchy-refresh-pacman "edge" ;; -*) echo "Unknown channel: $channel"; exit 1; ;; + stable|rc|edge) omarchy-refresh-pacman "$channel" ;; + dev) omarchy-refresh-pacman edge ;; + *) echo "Unknown channel: $channel"; exit 1 ;; esac omarchy-update -y diff --git a/bin/omarchy-chromium-ytdlp-host b/bin/omarchy-chromium-ytdlp-host new file mode 100755 index 00000000..5fc5842d --- /dev/null +++ b/bin/omarchy-chromium-ytdlp-host @@ -0,0 +1,136 @@ +#!/bin/bash + +# omarchy:summary=Native messaging host: download the URL sent by the yt-dlp Chromium extension +# omarchy:hidden=true + +set -euo pipefail + +SCRIPT_PATH="${BASH_SOURCE[0]}" + +# The browser launches us without Omarchy's environment, so locate the repo from +# our own path when OMARCHY_PATH isn't already set. Export it — omarchy-shell (used +# by omarchy-osd) needs it to find the running shell, and silently no-ops without it. +export OMARCHY_PATH="${OMARCHY_PATH:-$(cd -- "$(dirname -- "$SCRIPT_PATH")/.." && pwd)}" + +# Make sure the Omarchy bin and yt-dlp are reachable when launched by the browser. +export PATH="$OMARCHY_PATH/bin:/usr/local/bin:/usr/bin:$PATH" + +DOWNLOAD_DIR="${OMARCHY_YTDLP_DIR:-$HOME/Videos}" + +parse_url() { + jq -r '.url // empty' 2>/dev/null <<<"$1" || true +} + +valid_url() { + [[ $1 =~ ^https?:// ]] +} + +# Drive the Quickshell OSD — a single overlay that updates in place (like the +# volume/brightness bar), so download progress never stacks like notifications. +osd_progress() { + omarchy-osd -i 󰇚 -p "$1" -d 8000 >/dev/null 2>&1 || true +} + +osd_close() { + omarchy-shell -q osd close >/dev/null 2>&1 || true +} + +download_url() { + local url="$1" + + mkdir -p "$DOWNLOAD_DIR" + + # Don't show anything until yt-dlp confirms there's actually a video to grab. + if ! yt-dlp --no-playlist --simulate --quiet --no-warnings "$url" >/dev/null 2>&1; then + omarchy-notification-send -u critical -g 󰅖 "No video found for download" "$url" + exit 0 + fi + + osd_progress 0 + + # Stream the download: OMARCHY_PROG carries the percent (drives the OSD), + # OMARCHY_FILE (printed only after a successful move) carries title + path. + local line rest pct intpct last="" er nowms lastms=0 title="" filepath="" + while IFS= read -r line; do + case $line in + OMARCHY_PROG*) + pct=${line#OMARCHY_PROG$'\t'} + intpct=${pct%%.*} + intpct=${intpct//[^0-9]/} + [[ -n $intpct && $intpct != "$last" ]] || continue # skip no-op repeats + # Throttle to ~4 redraws/sec so fast downloads don't spawn a flurry of processes. + er=$EPOCHREALTIME + nowms=$((${er%[.,]*} * 1000 + 10#${er##*[.,]} / 1000)) + ((nowms - lastms >= 250)) || continue + last=$intpct + lastms=$nowms + osd_progress "$intpct" + ;; + OMARCHY_FILE*) + rest=${line#OMARCHY_FILE$'\t'} + title=${rest%%$'\t'*} + filepath=${rest#*$'\t'} + ;; + esac + done < <(PYTHONUNBUFFERED=1 yt-dlp --no-playlist --restrict-filenames --no-simulate \ + --quiet --no-warnings --progress --newline \ + --progress-template $'download:OMARCHY_PROG\t%(progress._percent_str)s' \ + --paths "$DOWNLOAD_DIR" -o '%(title)s [%(id)s].%(ext)s' \ + --print $'after_move:OMARCHY_FILE\t%(title)s\t%(filepath)s' \ + "$url" 2>&1) + + osd_close + + # after_move only prints on a successful download+move, so a captured path == success. + if [[ -n $filepath ]]; then + ((${#title} > 50)) && title="${title:0:50}…" # keep the toast compact + + # Square, center-cropped thumbnail so the notification preview isn't stretched. + local preview + preview="$(mktemp --suffix=.jpg)" + ffmpeg -y -i "$filepath" -ss 00:00:00.1 -vframes 1 \ + -vf "crop='min(iw,ih)':'min(iw,ih)',scale=256:256" -q:v 2 \ + "$preview" -loglevel quiet 2>/dev/null || true + + # Clicking the toast opens the video in mpv (-a blocks until clicked or timed out). + ( + if [[ -n $(omarchy-notification-send -g 󰄬 "Download complete" "$title" -t 10000 --image "${preview:-$filepath}" -a) ]]; then + mpv "$filepath" + fi + rm -f "$preview" + ) & + else + omarchy-notification-send -u critical -g 󰅖 "Download failed" "$url" + fi + + exit 0 +} + +main() { + local length payload url + + # Detached worker: this is what actually runs yt-dlp and fires notifications. + if [[ "${1:-}" == "--download" ]]; then + download_url "$2" + fi + + # Native messaging frame: 4-byte little-endian length prefix, then UTF-8 JSON. + length=$(head -c4 | od -An -v -tu4 --endian=little | tr -d ' ') + [[ -n ${length:-} ]] && ((length > 0)) || exit 0 + + payload=$(head -c "$length") + + # Ack with an empty message so the extension's sendNativeMessage callback resolves cleanly. + printf '\x02\x00\x00\x00{}' + + url=$(parse_url "$payload") + [[ -n $url ]] || exit 0 + valid_url "$url" || exit 0 + + # Detach the download so this host exits promptly and frees the browser's port. + setsid -f "$SCRIPT_PATH" --download "$url" /dev/null 2>&1 +} + +if [[ ${BASH_SOURCE[0]} == "$0" ]]; then + main "$@" +fi diff --git a/bin/omarchy-clipboard-open b/bin/omarchy-clipboard-open new file mode 100755 index 00000000..04ea3d77 --- /dev/null +++ b/bin/omarchy-clipboard-open @@ -0,0 +1,69 @@ +#!/bin/bash + +# omarchy:summary=Open a clipboard history entry +# omarchy:group=clipboard +# omarchy:args=--history-index + +history_index="" +history_path="$HOME/.local/state/omarchy/clipboard-history.json" + +while [[ $# -gt 0 ]]; do + case "$1" in + --history-index) + history_index="${2:-}" + shift 2 + ;; + *) + echo "Usage: omarchy-clipboard-open --history-index " >&2 + exit 1 + ;; + esac +done + +[[ $history_index =~ ^[0-9]+$ ]] || exit 1 +[[ -r $history_path ]] || exit 1 + +entry_type=$(jq -er --argjson index "$history_index" '.[$index].type' "$history_path") || exit 1 + +open_image() { + local path="$1" + + [[ -r $path ]] || exit 1 + exec satty --filename "$path" --output-filename "$path" +} + +open_text() { + local text="$1" + local url="" + local open_dir="" + local open_file="" + + url=$(grep -Eom1 'https?://[^[:space:]"'\''<>]+' <<<"$text" || true) + if [[ -z $url && $text =~ ^[[:space:]]*([[:alnum:]][[:alnum:].-]+\.[[:alpha:]]{2,})(/[^[:space:]]*)?[[:space:]]*$ ]]; then + url="https://${BASH_REMATCH[1]}${BASH_REMATCH[2]}" + fi + + if [[ -n $url ]]; then + exec omarchy-launch-browser "$url" + fi + + open_dir="${XDG_STATE_HOME:-$HOME/.local/state}/omarchy/clipboard-open" + mkdir -p "$open_dir" + open_file=$(mktemp --tmpdir="$open_dir" clipboard.XXXXXX.txt) || exit 1 + printf '%s' "$text" >"$open_file" + exec omarchy-launch-editor "$open_file" +} + +case "$entry_type" in + image) + path=$(jq -er --argjson index "$history_index" '.[$index].path' "$history_path") || exit 1 + open_image "$path" + ;; + text) + text=$(jq -er --argjson index "$history_index" '.[$index].text' "$history_path") || exit 1 + open_text "$text" + ;; + *) + exit 1 + ;; +esac diff --git a/bin/omarchy-clipboard-paste-file b/bin/omarchy-clipboard-paste-file new file mode 100755 index 00000000..a656931b --- /dev/null +++ b/bin/omarchy-clipboard-paste-file @@ -0,0 +1,32 @@ +#!/bin/bash + +# omarchy:summary=Copy a file to the clipboard and paste it +# omarchy:group=clipboard +# omarchy:args=[--copy-only] +# omarchy:examples=omarchy clipboard paste file image/png /tmp/screenshot.png + +copy_only=false + +if [[ ${1:-} == "--copy-only" ]]; then + copy_only=true + shift +fi + +mime=${1:-} +path=${2:-} + +if [[ -z $mime || -z $path ]]; then + echo "Usage: omarchy-clipboard-paste-file [--copy-only] " >&2 + exit 1 +fi + +[[ -r $path ]] || exit 1 + +wl-copy --type "$mime" < "$path" + +if [[ $copy_only == "true" ]]; then + exit +fi + +sleep 0.15 +wtype -M shift -k Insert -m shift 2>/dev/null || true diff --git a/bin/omarchy-clipboard-paste-text b/bin/omarchy-clipboard-paste-text new file mode 100755 index 00000000..a1cf064e --- /dev/null +++ b/bin/omarchy-clipboard-paste-text @@ -0,0 +1,62 @@ +#!/bin/bash + +# omarchy:summary=Copy text to the clipboard and type or paste it +# omarchy:group=clipboard +# omarchy:args=[--shift-insert] [--copy-only] [--history-index |] +# omarchy:examples=omarchy clipboard paste text "hello" | omarchy clipboard paste text --shift-insert "hello" + +use_shift_insert=false +copy_only=false +history_index="" +text="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --shift-insert) + use_shift_insert=true + shift + ;; + --copy-only) + copy_only=true + shift + ;; + --history-index) + history_index="${2:-}" + shift 2 + ;; + *) + break + ;; + esac +done + +copy_history_entry() { + local history_path="$HOME/.local/state/omarchy/clipboard-history.json" + + [[ $history_index =~ ^[0-9]+$ ]] || exit + jq -e --argjson index "$history_index" '.[$index].type == "text" and (.[$index].text | type == "string")' "$history_path" >/dev/null || exit + jq -j --argjson index "$history_index" '.[$index].text' "$history_path" | wl-copy +} + +if [[ -n $history_index ]]; then + copy_history_entry + if [[ $copy_only != "true" ]]; then + use_shift_insert=true + fi +else + text=${1:-} + [[ -n $text ]] || exit + printf '%s' "$text" | wl-copy +fi + +if [[ $copy_only == "true" ]]; then + exit +fi + +sleep 0.15 + +if [[ $use_shift_insert == "true" ]]; then + wtype -M shift -k Insert -m shift 2>/dev/null || true +else + wtype "$text" 2>/dev/null || true +fi diff --git a/bin/omarchy-config-shell-bar b/bin/omarchy-config-shell-bar new file mode 100755 index 00000000..56b2c394 --- /dev/null +++ b/bin/omarchy-config-shell-bar @@ -0,0 +1,615 @@ +#!/bin/bash + +# omarchy:summary=Mutate the user shell.json bar layout and active bar option +# omarchy:args=show | options [--json] | use | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position | transparent +# omarchy:examples=omarchy config shell bar show | omarchy config shell bar options | omarchy config shell bar use local.neon-bar | omarchy config shell bar reset | omarchy config shell bar list | omarchy config shell bar add omarchy.tailscale | omarchy config shell bar position top | omarchy config shell bar transparent true + +set -euo pipefail + +CONFIG_FILE="$HOME/.config/omarchy/shell.json" +OMARCHY_ROOT="${OMARCHY_PATH:-}" +DEFAULTS_FILE="$OMARCHY_ROOT/config/omarchy/shell.json" + +usage() { + echo "Usage: omarchy-config-shell-bar show | options [--json] | use | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position | transparent " >&2 +} + +fail() { + echo "omarchy-config-shell-bar: $*" >&2 + exit 1 +} + +refresh_shell_config() { + if ! omarchy-shell shell reloadConfig >/dev/null 2>&1; then + omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true + fi +} + +source_file() { + local source="$CONFIG_FILE" + + if [[ ! -s $source ]]; then + [[ -n $OMARCHY_ROOT ]] || fail "OMARCHY_PATH is not set" + source="$DEFAULTS_FILE" + fi + + [[ -s $source ]] || fail "could not find shell config or defaults" + printf '%s\n' "$source" +} + +bar_widget_manifest_paths() { + { + if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then + find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null + fi + + if [[ -d $HOME/.config/omarchy/plugins ]]; then + find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null + fi + } | sort -u +} + +bar_option_manifest_paths() { + { + if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then + find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null + fi + + if [[ -d $HOME/.config/omarchy/plugins ]]; then + find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null + fi + } | sort -u +} + +current_bar_option_id() { + jq -r '(.bar.id // "omarchy.bar") | tostring' "$(source_file)" +} + +available_bar_options_json() { + local current_id + local manifest_paths=() + + current_id=$(current_bar_option_id) + mapfile -t manifest_paths < <(bar_option_manifest_paths) + + if (( ${#manifest_paths[@]} == 0 )); then + printf '[]\n' + return + fi + + jq -s --arg currentId "$current_id" ' + map( + select(((.kinds // []) | index("bar")) and ((.entryPoints.bar // "") != "")) | + { + id: (.id // ""), + name: (.name // .id // ""), + description: (.description // ""), + firstParty: ((.id // "") | startswith("omarchy.")), + active: ((.id // "") == $currentId) + } | + select(.id != "") + ) | + unique_by(.id) | + sort_by((if .id == "omarchy.bar" then 0 else 1 end), .name, .id) + ' "${manifest_paths[@]}" +} + +print_bar_options() { + local json="false" + + while (( $# > 0 )); do + case "$1" in + --json) + json="true" + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "unknown options option: $1" + ;; + esac + shift + done + + if [[ $json == "true" ]]; then + available_bar_options_json + return + fi + + available_bar_options_json | jq -r ' + .[] | + [ + .id, + (if .active then "active" else "available" end), + (if .firstParty then "built-in" else "plugin" end), + .name, + .description + ] | @tsv + ' | awk -F '\t' ' + BEGIN { printf "%-32s %-10s %-8s %-22s %s\n", "ID", "STATUS", "SOURCE", "NAME", "DESCRIPTION" } + { printf "%-32s %-10s %-8s %-22s %s\n", $1, $2, $3, $4, $5 } + ' +} + +bar_option_exists() { + local id="$1" + available_bar_options_json | jq -e --arg id "$id" 'any(.[]; .id == $id)' >/dev/null +} + +current_bar_ids_json() { + jq -c ' + def array_or_empty: if type == "array" then . else [] end; + def entry_id: if type == "object" then (.id // "" | tostring) else tostring end; + + ["left", "center", "right"] as $sections | + [ $sections[] as $section | + ((.bar.layout[$section] // []) | array_or_empty)[] | + entry_id | + select(length > 0) + ] + ' "$(source_file)" +} + +current_bar_entries_json() { + jq -c ' + def array_or_empty: if type == "array" then . else [] end; + def entry_id($entry): if ($entry | type) == "object" then ($entry.id // "" | tostring) else ($entry | tostring) end; + + ["left", "center", "right"] as $sections | + [ $sections[] as $section | + ((.bar.layout[$section] // []) | array_or_empty | to_entries[]) | + { section: $section, index: .key, id: entry_id(.value) } | + select(.id | length > 0) + ] + ' "$(source_file)" +} + +available_widgets_json() { + local include_all="$1" + local current_ids + local manifest_paths=() + + current_ids=$(current_bar_ids_json) + mapfile -t manifest_paths < <(bar_widget_manifest_paths) + + if (( ${#manifest_paths[@]} == 0 )); then + printf '[]\n' + return + fi + + jq -s --argjson currentIds "$current_ids" --argjson includeAll "$include_all" ' + map( + select(((.kinds // []) | index("bar-widget")) and ((.entryPoints.barWidget // "") != "")) | + { + id: (.id // ""), + name: (.barWidget.displayName // .name // .id // ""), + description: (.barWidget.description // .description // ""), + category: (.barWidget.category // "Plugin"), + allowMultiple: (.barWidget.allowMultiple == true) + } | + select(.id != "") + ) | + unique_by(.id) | + map(. as $widget | + $widget + { + inBar: (($currentIds | index($widget.id)) != null), + addable: ($widget.allowMultiple or (($currentIds | index($widget.id)) == null)) + } + ) | + map(select($includeAll or .addable)) | + sort_by(.category, .name, .id) + ' "${manifest_paths[@]}" +} + +print_available_widgets() { + local include_all="false" + local json="false" + + while (( $# > 0 )); do + case "$1" in + --all) + include_all="true" + ;; + --json) + json="true" + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "unknown list option: $1" + ;; + esac + shift + done + + if [[ $json == "true" ]]; then + available_widgets_json "$include_all" + return + fi + + available_widgets_json "$include_all" | jq -r ' + .[] | + [ + .id, + (if .inBar and .allowMultiple then "addable*" elif .inBar then "in-bar" else "addable" end), + .category, + .name, + .description + ] | @tsv + ' | awk -F '\t' ' + BEGIN { printf "%-28s %-10s %-14s %-22s %s\n", "ID", "STATUS", "CATEGORY", "NAME", "DESCRIPTION" } + { printf "%-28s %-10s %-14s %-22s %s\n", $1, $2, $3, $4, $5 } + ' +} + +choose_widget() { + local widgets + local options=() + local selected="" + + widgets=$(available_widgets_json false) + + if ! jq -e 'length > 0' <<<"$widgets" >/dev/null; then + fail "no addable bar widgets found" + fi + + mapfile -t options < <(jq -r ' + .[] | + [.id, .name, .category, .description] | @tsv + ' <<<"$widgets" | awk -F '\t' '{ printf "%-28s %-22s [%s] %s\n", $1, $2, $3, $4 }') + + if [[ -t 0 || -t 2 ]]; then + if omarchy-cmd-present gum; then + selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Add bar widget" --placeholder "Search addable widgets..." --limit 1 --height 14) || return 1 + elif omarchy-cmd-present fzf; then + selected=$(printf '%s\n' "${options[@]}" | fzf --prompt "Add bar widget > " --height 40% --layout reverse --border) || return 1 + else + print_available_widgets >&2 + fail "gum or fzf is required for interactive widget selection" + fi + else + print_available_widgets >&2 + fail "widget id is required; use list to see addable widgets" + fi + + selected="${selected%%[[:space:]]*}" + [[ -n $selected ]] || return 1 + printf '%s\n' "$selected" +} + +choose_removal() { + local entries + local widgets + local options=() + local selected="" + local location="" + local section="" + local index="" + + entries=$(current_bar_entries_json) + widgets=$(available_widgets_json true) + + if ! jq -e 'length > 0' <<<"$entries" >/dev/null; then + fail "no bar widgets are currently configured" + fi + + mapfile -t options < <(jq -r --argjson widgets "$widgets" ' + def widget_meta($id): first($widgets[]? | select(.id == $id)) // {}; + + .[] as $entry | + (widget_meta($entry.id)) as $meta | + [ + "\($entry.section)[\($entry.index)]", + $entry.id, + ($meta.name // $entry.id), + ($meta.category // "") + ] | @tsv + ' <<<"$entries" | awk -F '\t' '{ printf "%-10s %-28s %-22s %s\n", $1, $2, $3, $4 }') + + if [[ -t 0 || -t 2 ]]; then + if omarchy-cmd-present gum; then + selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Remove bar widget" --placeholder "Search current widgets..." --limit 1 --height 14) || return 1 + elif omarchy-cmd-present fzf; then + selected=$(printf '%s\n' "${options[@]}" | fzf --prompt "Remove bar widget > " --height 40% --layout reverse --border) || return 1 + else + jq -r '.[] | "\(.section)[\(.index)]\t\(.id)"' <<<"$entries" >&2 + fail "gum or fzf is required for interactive widget selection" + fi + else + jq -r '.[] | "\(.section)[\(.index)]\t\(.id)"' <<<"$entries" >&2 + fail "widget id is required; use show to see current widgets" + fi + + location="${selected%%[[:space:]]*}" + section="${location%%[*}" + index="${location#*[}" + index="${index%]}" + + [[ $section =~ ^(left|center|right)$ ]] || return 1 + [[ $index =~ ^[0-9]+$ ]] || return 1 + printf '%s\t%s\n' "$section" "$index" +} + +command="${1:-}" + +case "$command" in + show|current) + if (( $# != 1 )); then + usage + exit 1 + fi + + jq '.bar // {}' "$(source_file)" + ;; + selected|active) + if (( $# != 1 )); then + usage + exit 1 + fi + + current_bar_option_id + ;; + options|option) + shift + print_bar_options "$@" + ;; + use) + if (( $# != 2 )); then + usage + exit 1 + fi + + plugin="${2:-}" + [[ -n $plugin ]] || fail "bar option id is required" + if [[ $plugin == "default" || $plugin == "built-in" ]]; then + plugin="omarchy.bar" + fi + bar_option_exists "$plugin" || fail "$plugin is not a known bar option; run 'omarchy config shell bar options'" + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + if [[ $plugin == "omarchy.bar" ]]; then + jq ' + def object_or_empty: if type == "object" then . else {} end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | del(.bar.id) + ' "$source" >"$tmp" + else + jq --arg plugin "$plugin" ' + def object_or_empty: if type == "object" then . else {} end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.id = $plugin + ' "$source" >"$tmp" + fi + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true + refresh_shell_config + ;; + reset) + if (( $# != 1 )); then + usage + exit 1 + fi + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + jq ' + def object_or_empty: if type == "object" then . else {} end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | del(.bar.id) + ' "$source" >"$tmp" + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + refresh_shell_config + ;; + list|available|widgets) + shift + print_available_widgets "$@" + ;; + add) + if (( $# > 3 )); then + usage + exit 1 + fi + + if (( $# == 1 )); then + plugin=$(choose_widget) || exit 1 + else + plugin="${2:-}" + fi + section="${3:-right}" + + [[ -n $plugin ]] || fail "plugin is required" + [[ $section =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right" + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + jq --arg section "$section" --arg plugin "$plugin" ' + def object_or_empty: if type == "object" then . else {} end; + def array_or_empty: if type == "array" then . else [] end; + def entry_id: if type == "object" then (.id // "" | tostring) else tostring end; + def append_anchor($section): + { + left: "omarchy.workspaces", + center: "omarchy.weather", + right: "omarchy.tray" + }[$section]; + def insert_after_anchor($entries; $entry; $anchor): + ($entries | map(entry_id) | index($anchor)) as $index + | if $index == null then + $entries + [$entry] + else + $entries[0:$index + 1] + [$entry] + $entries[$index + 1:] + end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.layout = (.bar.layout | object_or_empty) + | .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin))) + | .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin))) + | .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin))) + | .plugins = (.plugins | array_or_empty) + | .bar.layout[$section] = insert_after_anchor(.bar.layout[$section]; { id: $plugin }; append_anchor($section)) + ' "$source" >"$tmp" + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + refresh_shell_config + ;; + position) + if (( $# != 2 )); then + usage + exit 1 + fi + + position="${2:-}" + + [[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right" + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + jq --arg position "$position" ' + def object_or_empty: if type == "object" then . else {} end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.position = $position + ' "$source" >"$tmp" + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + refresh_shell_config + ;; + transparent) + if (( $# != 2 )); then + usage + exit 1 + fi + + transparent="${2:-}" + + [[ $transparent =~ ^(true|false)$ ]] || fail "transparent must be true or false" + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + jq --argjson transparent "$transparent" ' + def object_or_empty: if type == "object" then . else {} end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.transparent = $transparent + ' "$source" >"$tmp" + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + refresh_shell_config + ;; + drop|remove|rm) + if (( $# > 2 )); then + usage + exit 1 + fi + + plugin="" + target_section="" + target_index="" + + if (( $# == 1 )); then + removal=$(choose_removal) || exit 1 + target_section="${removal%%$'\t'*}" + target_index="${removal#*$'\t'}" + else + plugin="${2:-}" + [[ -n $plugin ]] || fail "plugin is required" + fi + + mkdir -p "$(dirname "$CONFIG_FILE")" + + source="$(source_file)" + tmp=$(mktemp) + trap 'rm -f "$tmp"' EXIT + + if [[ -n $target_section ]]; then + jq --arg section "$target_section" --argjson index "$target_index" ' + def object_or_empty: if type == "object" then . else {} end; + def array_or_empty: if type == "array" then . else [] end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.layout = (.bar.layout | object_or_empty) + | .bar.layout.left = (.bar.layout.left | array_or_empty) + | .bar.layout.center = (.bar.layout.center | array_or_empty) + | .bar.layout.right = (.bar.layout.right | array_or_empty) + | if (.bar.layout[$section] | length) <= $index then + error("no widget at " + $section + "[" + ($index | tostring) + "]") + else + .bar.layout[$section] = (.bar.layout[$section][0:$index] + .bar.layout[$section][($index + 1):]) + end + | .plugins = (.plugins | array_or_empty) + ' "$source" >"$tmp" + else + jq --arg plugin "$plugin" ' + def object_or_empty: if type == "object" then . else {} end; + def array_or_empty: if type == "array" then . else [] end; + def entry_id: if type == "object" then (.id // "" | tostring) else tostring end; + + object_or_empty + | .version = 1 + | .bar = (.bar | object_or_empty) + | .bar.layout = (.bar.layout | object_or_empty) + | .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin))) + | .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin))) + | .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin))) + | .plugins = (.plugins | array_or_empty) + ' "$source" >"$tmp" + fi + + mv "$tmp" "$CONFIG_FILE" + trap - EXIT + refresh_shell_config + ;; + *) + usage + exit 1 + ;; +esac diff --git a/bin/omarchy-debug b/bin/omarchy-debug index c163e171..237a07d7 100755 --- a/bin/omarchy-debug +++ b/bin/omarchy-debug @@ -37,7 +37,7 @@ fi cat > "$LOG_FILE" </dev/null || echo "unknown") +Omarchy Package: $(pacman -Q omarchy 2>/dev/null || echo "unknown") ========================================= SYSTEM INFORMATION @@ -75,7 +75,7 @@ ACTION=$(gum choose "${OPTIONS[@]}") case "$ACTION" in "Upload log") echo "Uploading debug log to logs.omarchy.org..." - URL=$(curl -sf -F "file=@$LOG_FILE" https://logs.omarchy.org/) + URL=$(curl -sf -F "file=@$LOG_FILE" -Fexpires=24 https://logs.omarchy.org/) if (( $? == 0 )) && [[ -n $URL ]]; then echo "✓ Log uploaded successfully!" echo "Share this URL:" diff --git a/bin/omarchy-debug-idle b/bin/omarchy-debug-idle new file mode 100755 index 00000000..308df73f --- /dev/null +++ b/bin/omarchy-debug-idle @@ -0,0 +1,58 @@ +#!/bin/bash + +# omarchy:summary=Show idle, screensaver, and lock diagnostics +# omarchy:group=debug +# omarchy:args=[log-lines] +# omarchy:examples=omarchy debug idle | omarchy-debug-idle 400 + +lines=${1:-200} +if [[ ! $lines =~ ^[0-9]+$ ]]; then + lines=200 +fi + +section() { + printf '\n== %s ==\n' "$1" +} + +section "Time" +date -Is + +section "Idle IPC status" +omarchy-shell idle status 2>&1 | jq . 2>/dev/null || omarchy-shell idle status 2>&1 || true + +section "Quickshell instances" +quickshell list -p "$OMARCHY_PATH/shell" --any-display 2>&1 || true + +section "Recent idle logs" +quickshell --no-color log -p "$OMARCHY_PATH/shell" --any-display --tail "$lines" --log-times -r 'quickshell.wayland.idle_notify=true' 2>&1 \ + | grep -Ei 'omarchy idle|idle_notify|screensaver|lock|error|warn|failed' || true + +section "Relevant processes" +ps -eo pid=,args= \ + | grep -E 'quickshell -n -p|omarchy-system-sleep-monitor|systemd-inhibit.*Lock screen before suspend|org\.omarchy\.screensaver|omarchy-screensaver|(^|/| )tte( |$)' \ + | grep -v grep || true + +section "Sleep lock service" +systemctl --user status omarchy-sleep-lock.service --no-pager 2>/dev/null || true + +section "Hyprland screensaver clients" +hyprctl clients -j 2>/dev/null \ + | jq -r '.[] | select(.class == "org.omarchy.screensaver" or .initialClass == "org.omarchy.screensaver") | [.pid,.class,.initialClass,.title,.focusHistoryID] | @tsv' || true + +section "Idle inhibitors" +hyprctl clients -j 2>/dev/null \ + | jq -r '.[] | select(.inhibitingIdle == true or ((.tags // []) | index("noidle"))) | [.pid,.class,.title,((.tags // []) | join(",")),.inhibitingIdle] | @tsv' || true + +section "Screensaver detector" +if hyprctl clients -j 2>/dev/null | jq -e '.[] | select(.class == "org.omarchy.screensaver" or .initialClass == "org.omarchy.screensaver")' >/dev/null; then + echo "running-window" +elif pgrep -f '[o]rg.omarchy.screensaver' >/dev/null; then + echo "running-process" +elif omarchy-toggle-enabled screensaver-off; then + echo "disabled" +else + echo "stopped" +fi + +section "Lock detector" +omarchy-shell lock status 2>&1 | jq . 2>/dev/null || omarchy-shell lock status 2>&1 || true diff --git a/bin/omarchy-default-ai b/bin/omarchy-default-ai new file mode 100755 index 00000000..e1d40280 --- /dev/null +++ b/bin/omarchy-default-ai @@ -0,0 +1,32 @@ +#!/bin/bash + +# omarchy:summary=Set the default AI harness used by omarchy-launch-ai +# omarchy:args=[pi|claude|codex|opencode] +# omarchy:examples=omarchy default ai | omarchy default ai pi | omarchy default ai claude + +ai_file="$HOME/.local/state/omarchy/defaults/ai" + +if (( $# == 0 )); then + if [[ -f $ai_file ]]; then + read -r harness <"$ai_file" + fi + + [[ -n ${harness:-} ]] && echo "$harness" || echo "pi" + exit 0 +fi + +case "$1" in +pi) harness="pi"; name="Pi"; glyph=󰚩 ;; +claude) harness="claude"; name="Claude Code"; glyph=󰚩 ;; +codex) harness="codex"; name="Codex"; glyph=󰚩 ;; +opencode) harness="opencode"; name="OpenCode"; glyph=󰚩 ;; +*) + echo "Usage: omarchy-default-ai " >&2 + exit 1 + ;; +esac + +mkdir -p "$(dirname "$ai_file")" +printf '%s\n' "$harness" >"$ai_file" + +omarchy-notification-send -g "$glyph" "$name is now the default AI harness" diff --git a/bin/omarchy-default-browser b/bin/omarchy-default-browser index 0b38d760..10f96736 100755 --- a/bin/omarchy-default-browser +++ b/bin/omarchy-default-browser @@ -19,13 +19,13 @@ if (($# == 0)); then fi case "$1" in -chromium) desktop_id="chromium.desktop"; name="Chromium"; glyph="" ;; -chrome) desktop_id="google-chrome.desktop"; name="Chrome"; glyph="󰊯" ;; -brave) desktop_id="brave-browser.desktop"; name="Brave"; glyph="󰖟" ;; -brave-origin) desktop_id="brave-origin-beta.desktop"; name="Brave Origin"; glyph="󰖟" ;; -edge) desktop_id="microsoft-edge.desktop"; name="Edge"; glyph="󰇩" ;; -firefox) desktop_id="firefox.desktop"; name="Firefox"; glyph="󰈹" ;; -zen) desktop_id="zen.desktop"; name="Zen"; glyph="󰰷" ;; +chromium) desktop_id="chromium.desktop"; name="Chromium"; glyph= ;; +chrome) desktop_id="google-chrome.desktop"; name="Chrome"; glyph=󰊯 ;; +brave) desktop_id="brave-browser.desktop"; name="Brave"; glyph=󰖟 ;; +brave-origin) desktop_id="brave-origin-beta.desktop"; name="Brave Origin"; glyph=󰖟 ;; +edge) desktop_id="microsoft-edge.desktop"; name="Edge"; glyph=󰇩 ;; +firefox) desktop_id="firefox.desktop"; name="Firefox"; glyph=󰈹 ;; +zen) desktop_id="zen.desktop"; name="Zen"; glyph=󰖟 ;; *) echo "Usage: omarchy-default-browser " exit 1 @@ -37,4 +37,4 @@ xdg-mime default "$desktop_id" x-scheme-handler/http xdg-mime default "$desktop_id" x-scheme-handler/https xdg-mime default "$desktop_id" text/html -notify-send -u low "$glyph $name is now the default browser" +omarchy-notification-send -g $glyph "$name is now the default browser" diff --git a/bin/omarchy-default-editor b/bin/omarchy-default-editor index 31846450..bf14dc44 100755 --- a/bin/omarchy-default-editor +++ b/bin/omarchy-default-editor @@ -1,30 +1,36 @@ #!/bin/bash -# omarchy:summary=Set the default editor for $EDITOR +# omarchy:summary=Set the default editor used by omarchy-launch-editor # omarchy:args=[code|cursor|zed|sublime_text|helix|vim|emacs|nvim] # omarchy:examples=omarchy default editor | omarchy default editor code | omarchy default editor helix +editor_file="$HOME/.local/state/omarchy/defaults/editor" + if (($# == 0)); then - sed -n 's/^export EDITOR=//p' ~/.config/uwsm/default | head -n 1 + if [[ -f $editor_file ]]; then + read -r editor <"$editor_file" + fi + + [[ -n $editor ]] && echo "$editor" || echo "nvim" exit 0 fi case "$1" in -code) editor="code"; name="VSCode"; glyph="" ;; -cursor) editor="cursor"; name="Cursor"; glyph="" ;; -zed | zeditor) editor="zeditor"; name="Zed"; glyph="" ;; -sublime_text) editor="sublime_text"; name="Sublime Text"; glyph="" ;; -helix) editor="helix"; name="Helix"; glyph="" ;; -vim) editor="vim"; name="Vim"; glyph="" ;; -emacs) editor="emacs"; name="Emacs"; glyph="" ;; -nvim) editor="nvim"; name="Neovim"; glyph="" ;; +code) editor="code"; name="VSCode"; glyph= ;; +cursor) editor="cursor"; name="Cursor"; glyph= ;; +zed | zeditor) editor="zeditor"; name="Zed"; glyph= ;; +sublime_text) editor="sublime_text"; name="Sublime Text"; glyph= ;; +helix) editor="helix"; name="Helix"; glyph= ;; +vim) editor="vim"; name="Vim"; glyph= ;; +emacs) editor="emacs"; name="Emacs"; glyph= ;; +nvim) editor="nvim"; name="Neovim"; glyph= ;; *) echo "Usage: omarchy-default-editor " exit 1 ;; esac -sed -i "s/^export EDITOR=.*/export EDITOR=$editor/" ~/.config/uwsm/default +mkdir -p "$(dirname "$editor_file")" +printf '%s\n' "$editor" >"$editor_file" -export EDITOR="$editor" -notify-send -u low "$glyph $name is now the default editor" " Effective after logging out" +omarchy-notification-send -g $glyph "$name is now the default editor" diff --git a/bin/omarchy-default-terminal b/bin/omarchy-default-terminal index b44b0511..ca0ee1d3 100755 --- a/bin/omarchy-default-terminal +++ b/bin/omarchy-default-terminal @@ -5,7 +5,8 @@ # omarchy:examples=omarchy default terminal ghostty | omarchy default terminal kitty if (($# == 0)); then - desktop_id=$(grep -vE '^($|#)' ~/.config/xdg-terminals.list 2>/dev/null | head -n 1) + desktop_id=$(xdg-terminal-exec --print-id 2>/dev/null || true) + desktop_id=${desktop_id%%:*} case "$desktop_id" in Alacritty.desktop) echo "alacritty" ;; foot.desktop) echo "foot" ;; @@ -17,10 +18,10 @@ if (($# == 0)); then fi case "$1" in -alacritty) desktop_id="Alacritty.desktop"; name="Alacritty"; glyph="" ;; -foot) desktop_id="foot.desktop"; name="Foot"; glyph="" ;; -ghostty) desktop_id="com.mitchellh.ghostty.desktop"; name="Ghostty"; glyph="" ;; -kitty) desktop_id="kitty.desktop"; name="Kitty"; glyph="" ;; +alacritty) desktop_id="Alacritty.desktop"; name="Alacritty"; glyph= ;; +foot) desktop_id="foot.desktop"; name="Foot"; glyph= ;; +ghostty) desktop_id="com.mitchellh.ghostty.desktop"; name="Ghostty"; glyph= ;; +kitty) desktop_id="kitty.desktop"; name="Kitty"; glyph= ;; *) echo "Usage: omarchy-default-terminal " exit 1 @@ -33,4 +34,4 @@ cat >~/.config/xdg-terminals.list < [--no-edit] -cd ~/.local/share/omarchy -migration_file="$HOME/.local/share/omarchy/migrations/$(git log -1 --format=%cd --date=unix).sh" -touch $migration_file +set -euo pipefail -if [[ $1 != "--no-edit" ]]; then - nvim $migration_file +scope="${1:-}" +case "$scope" in + system|user) + shift + ;; + -h|--help|"") + echo "Usage: omarchy-dev-add-migration [--no-edit]" + exit 0 + ;; + *) + echo "Unknown migration scope: $scope" >&2 + echo "Usage: omarchy-dev-add-migration [--no-edit]" >&2 + exit 1 + ;; +esac + +cd "${OMARCHY_PATH:-$(pwd)}" +mkdir -p "migrations/$scope" +migration_file="migrations/$scope/$(git log -1 --format=%cd --date=unix).sh" +touch "$migration_file" + +if [[ ${1:-} != "--no-edit" ]]; then + nvim "$migration_file" fi -echo $migration_file +printf '%s\n' "$PWD/$migration_file" diff --git a/bin/omarchy-dev-benchmark-theme-switcher b/bin/omarchy-dev-benchmark-theme-switcher new file mode 100755 index 00000000..9bbc0424 --- /dev/null +++ b/bin/omarchy-dev-benchmark-theme-switcher @@ -0,0 +1,160 @@ +#!/bin/bash + +# omarchy:summary=Measure theme switcher cache and selector prep times +# omarchy:args=[--repeat=] [--keep-cache] +# omarchy:examples=omarchy dev benchmark theme switcher | omarchy dev benchmark theme-switcher --repeat=10 +# omarchy:aliases=omarchy dev benchmark theme-switcher + +set -euo pipefail + +OMARCHY_BIN_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +REPEAT=5 +KEEP_CACHE=false + +show_help() { + cat <<'EOF' +Usage: + omarchy dev benchmark theme switcher [--repeat=] [--keep-cache] + +Measure the non-interactive parts of the theme switcher: +- theme preview index build (omarchy-theme-switcher before UI handoff) +- lazy selector row prep used by the interactive theme switcher +- full thumbnail cache warmup cost (omarchy-menu-images --cache-only) + +Options: + --repeat= Number of warm runs to measure for each case (default: 5) + --keep-cache Keep the temporary benchmark cache and print its path +EOF +} + +now_us() { + local now="${EPOCHREALTIME/./}" + printf '%s' "$now" +} + +format_ms() { + local us="$1" + printf '%d.%03d' "$(( us / 1000 ))" "$(( us % 1000 ))" +} + +measure_once() { + local start_us end_us + start_us=$(now_us) + "$@" >/dev/null + end_us=$(now_us) + printf '%s' "$(( end_us - start_us ))" +} + +run_case() { + local label="$1" + shift + local total_us=0 + local min_us=0 + local max_us=0 + local elapsed_us=0 + + for (( i = 1; i <= REPEAT; i++ )); do + elapsed_us=$(measure_once "$@") + total_us=$(( total_us + elapsed_us )) + + if (( i == 1 || elapsed_us < min_us )); then + min_us=$elapsed_us + fi + + if (( elapsed_us > max_us )); then + max_us=$elapsed_us + fi + done + + printf '%-34s avg %8s ms min %8s ms max %8s ms\n' \ + "$label" \ + "$(format_ms "$(( total_us / REPEAT ))")" \ + "$(format_ms "$min_us")" \ + "$(format_ms "$max_us")" +} + +while (( $# > 0 )); do + case "$1" in + --repeat=*) + REPEAT="${1#*=}" + ;; + --keep-cache) + KEEP_CACHE=true + ;; + --help | -h) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + show_help >&2 + exit 2 + ;; + esac + shift +done + +if [[ ! $REPEAT =~ ^[0-9]+$ ]] || (( REPEAT < 1 )); then + echo "--repeat must be a positive integer" >&2 + exit 2 +fi + +benchmark_cache=$(mktemp -d) +thumbnail_cache=$(mktemp -d) +stub_bin=$(mktemp -d) + +cleanup() { + rm -rf "$stub_bin" + + if [[ $KEEP_CACHE == "true" ]]; then + printf 'Benchmark cache: %s\n' "$benchmark_cache" + printf 'Thumbnail cache: %s\n' "$thumbnail_cache" + else + rm -rf "$benchmark_cache" "$thumbnail_cache" + fi +} +trap cleanup EXIT + +cat >"$stub_bin/omarchy-menu-images" <<'EOF' +#!/bin/bash +exit 0 +EOF +chmod +x "$stub_bin/omarchy-menu-images" + +benchmark_env=( + env + "XDG_CACHE_HOME=$benchmark_cache" + "PATH=$stub_bin:$OMARCHY_BIN_DIR:$PATH" +) + +thumbnail_env=( + env + "XDG_CACHE_HOME=$thumbnail_cache" + "PATH=$stub_bin:$OMARCHY_BIN_DIR:$PATH" +) + +preview_dir="$benchmark_cache/omarchy/theme-selector/previews" +thumbnail_preview_dir="$thumbnail_cache/omarchy/theme-selector/previews" + +build_theme_index() { + "${benchmark_env[@]}" "$OMARCHY_BIN_DIR/omarchy-theme-switcher" +} + +prepare_selector_lazy() { + "${benchmark_env[@]}" "$OMARCHY_BIN_DIR/omarchy-menu-images" --prepare-only --lazy-thumbnails --show-labels --filterable "$preview_dir" +} + +prepare_image_cache() { + "${thumbnail_env[@]}" "$OMARCHY_BIN_DIR/omarchy-menu-images" --cache-only "$thumbnail_preview_dir" +} + +printf 'Theme switcher benchmark (%d warm runs each)\n\n' "$REPEAT" +printf '%-34s %s ms\n' "theme index cold" "$(format_ms "$(measure_once build_theme_index)")" +run_case "theme index warm" build_theme_index +printf '%-34s %s ms\n' "selector prep cold (lazy)" "$(format_ms "$(measure_once prepare_selector_lazy)")" +run_case "selector prep warm (lazy)" prepare_selector_lazy +"${thumbnail_env[@]}" "$OMARCHY_BIN_DIR/omarchy-theme-switcher" >/dev/null +printf '%-34s %s ms\n' "thumbnail cache cold" "$(format_ms "$(measure_once prepare_image_cache)")" +run_case "thumbnail cache warm" prepare_image_cache + +printf '\nTheme previews: %d\n' "$(find -L "$preview_dir" -maxdepth 1 -type f 2>/dev/null | wc -l)" diff --git a/bin/omarchy-dev-bin-metadata b/bin/omarchy-dev-bin-metadata index 99e87e86..a9e97214 100755 --- a/bin/omarchy-dev-bin-metadata +++ b/bin/omarchy-dev-bin-metadata @@ -61,7 +61,7 @@ Do not define: false flags or empty args Examples: - # omarchy:summary=Restart Walker and related user services + # omarchy:summary=Restart the shell # omarchy:summary=Take a screenshot # omarchy:group=capture diff --git a/bin/omarchy-dev-install-ydoo b/bin/omarchy-dev-install-ydoo new file mode 100755 index 00000000..91c4c502 --- /dev/null +++ b/bin/omarchy-dev-install-ydoo @@ -0,0 +1,50 @@ +#!/bin/bash + +# omarchy:summary=Install and enable ydotool mouse automation for Omarchy development +# omarchy:requires-sudo=true + +set -euo pipefail + +RULE_FILE="/etc/udev/rules.d/80-uinput.rules" + +if ! getent group input >/dev/null; then + echo "omarchy-dev-install-ydoo: input group does not exist" >&2 + exit 1 +fi + +if ! id -nG "$USER" | tr ' ' '\n' | grep -qx input; then + echo "Adding $USER to the input group. You may need to log out and back in before this applies." + pkexec usermod -aG input "$USER" +fi + +if omarchy-cmd-missing ydotool || omarchy-pkg-missing ydotool; then + omarchy-pkg-add ydotool +fi + +pkexec /bin/bash -c ' +set -euo pipefail + +cat >"'"$RULE_FILE"'" <<'"'"'EOF'"'"' +KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput" +EOF + +modprobe uinput +udevadm control --reload-rules +udevadm trigger /dev/uinput 2>/dev/null || true + +if [[ -e /dev/uinput ]]; then + chgrp input /dev/uinput + chmod 0660 /dev/uinput +fi +' + +systemctl --user reset-failed ydotool.service >/dev/null 2>&1 || true +systemctl --user start ydotool.service + +if ! systemctl --user is-active --quiet ydotool.service; then + echo "omarchy-dev-install-ydoo: ydotool.service did not start" >&2 + systemctl --user status ydotool.service --no-pager >&2 || true + exit 1 +fi + +echo "ydotool is ready." diff --git a/bin/omarchy-dev-link b/bin/omarchy-dev-link new file mode 100755 index 00000000..3345e29d --- /dev/null +++ b/bin/omarchy-dev-link @@ -0,0 +1,80 @@ +#!/bin/bash + +# omarchy:summary=Point Omarchy at a local checkout for live editing +# omarchy:group=dev +# omarchy:args= +# omarchy:examples=omarchy dev link ~/Work/omarchy/omarchy-installer + +set -euo pipefail + +# Sudo wipes HYPRLAND_INSTANCE_SIGNATURE, so the live-session refresh below +# can't reach hyprctl. Run as user; we sudo internally for the conf write. +if [[ $EUID -eq 0 ]]; then + echo "Error: run omarchy-dev-link as your user, not under sudo." >&2 + exit 1 +fi + +if [[ $# -ne 1 || $1 == "-h" || $1 == "--help" ]]; then + cat < + +Writes /etc/omarchy.conf so OMARCHY_PATH resolves to +in all new shells, the Hyprland session, and Quickshell. Restarts +omarchy-shell and reloads hyprctl so changes take effect immediately. + +Affects only \$OMARCHY_PATH-resolved trees: bin/, default/, shell/, +themes/, applications/, config/. Files installed at fixed system paths +(/etc/, /usr/lib/systemd/, udev rule bodies, /etc/skel after user +creation, /usr/share/plymouth) are NOT covered — for those, use +omarchy-dev-pkg-test to build and install the package from the checkout. +USAGE + exit 0 +fi + +target=$(realpath -e "$1" 2>/dev/null) || { + echo "Error: path does not exist: $1" >&2 + exit 1 +} + +for required in bin default shell; do + if [[ ! -d "$target/$required" ]]; then + echo "Warning: $target/$required not found — does this look like an Omarchy source checkout?" >&2 + fi +done + +# Strip any previous dev-link's bin/ from PATH before adding the new one, +# so repeated link calls don't accumulate stale entries. +prior_target="" +if [[ -f /etc/omarchy.conf ]]; then + prior_target=$(sed -n 's/^[[:space:]]*export[[:space:]]\+OMARCHY_PATH="\?\([^"]*\)"\?/\1/p' /etc/omarchy.conf | tail -1) +fi + +echo "Pointing Omarchy at $target" +printf 'export OMARCHY_PATH="%s"\n' "$target" | sudo tee /etc/omarchy.conf >/dev/null + +export OMARCHY_PATH="$target" +if [[ -n $prior_target && $prior_target != "$target" ]]; then + PATH=$(printf '%s' "$PATH" | tr ':' '\n' | grep -vFx "$prior_target/bin" | paste -sd:) +fi +export PATH="$target/bin:$PATH" + +if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then + hyprctl setenv OMARCHY_PATH "$target" >/dev/null + hyprctl setenv PATH "$PATH" >/dev/null + echo " Updated Hyprland session env." + + systemctl --user import-environment OMARCHY_PATH PATH 2>/dev/null || true + echo " Updated systemd --user env." + + if pgrep -x quickshell >/dev/null 2>&1; then + omarchy-restart-shell + echo " Restarted omarchy-shell." + fi + + hyprctl reload >/dev/null + echo " Reloaded Hyprland config." +fi + +echo +echo "Done. Open a new shell (or restart existing ones) to pick up the new" +echo "OMARCHY_PATH. Run 'omarchy dev unlink' to restore the package install." diff --git a/bin/omarchy-dev-pkg-test b/bin/omarchy-dev-pkg-test new file mode 100755 index 00000000..514d56ed --- /dev/null +++ b/bin/omarchy-dev-pkg-test @@ -0,0 +1,137 @@ +#!/bin/bash + +# omarchy:summary=Build and install an Omarchy package from a local checkout +# omarchy:group=dev +# omarchy:args=[package-name] [path-to-checkout] +# omarchy:examples=omarchy dev pkg-test | omarchy dev pkg-test omarchy-dev ~/Work/omarchy/omarchy-installer + +set -euo pipefail + +if [[ ${1:-} == "-h" || ${1:-} == "--help" ]]; then + cat <[.dirty]' so +'pacman -Q' makes it obvious where the installed version came from. + +PKGBUILDs are read from \${OMARCHY_PKGBUILDS_DIR:-~/Work/omarchy/omarchy-pkgs/pkgbuilds}//. +USAGE + exit 0 +fi + +remove_pkgver_function() { + local pkgbuild="$1" + local tmp="$pkgbuild.tmp" + + awk ' + /^pkgver\(\)[[:space:]]*\{/ { + in_pkgver = 1 + depth = 0 + } + in_pkgver { + line = $0 + opens = gsub(/\{/, "{", line) + line = $0 + closes = gsub(/\}/, "}", line) + depth += opens - closes + if (depth <= 0) { + in_pkgver = 0 + } + next + } + { print } + ' "$pkgbuild" >"$tmp" + mv "$tmp" "$pkgbuild" +} + +dev_package_name() { + local pkg="$1" + + case "$pkg" in + omarchy | omarchy-settings) + printf '%s-dev\n' "$pkg" + ;; + *) + printf '%s\n' "$pkg" + ;; + esac +} + +if (( $# == 0 )); then + PKGS=(omarchy-settings-dev omarchy-dev) + CHECKOUT="$HOME/Work/omarchy/omarchy-installer" + MAKEPKG_ARGS=() +else + PKGS=("$(dev_package_name "$1")") + CHECKOUT="${2:-$HOME/Work/omarchy/omarchy-installer}" + MAKEPKG_ARGS=("${@:3}") +fi +PKGBUILDS_ROOT="${OMARCHY_PKGBUILDS_DIR:-$HOME/Work/omarchy/omarchy-pkgs/pkgbuilds}" + +if [[ ! -d "$CHECKOUT" ]]; then + echo "Error: checkout not found at $CHECKOUT" >&2 + exit 1 +fi +for PKG in "${PKGS[@]}"; do + PKGBUILD_DIR="$PKGBUILDS_ROOT/$PKG" + if [[ ! -f "$PKGBUILD_DIR/PKGBUILD" ]]; then + echo "Error: PKGBUILD not found at $PKGBUILD_DIR/PKGBUILD" >&2 + echo " Pass a different package name as arg 1, or set OMARCHY_PKGBUILDS_DIR." >&2 + exit 1 + fi +done + +build_dir=$(mktemp -d -t omarchy-dev-pkg-test.XXXXXX) +trap 'rm -rf "$build_dir"' EXIT + +# pkgver=dev.[.dirty] so pacman -Q makes the source obvious. +short_sha=$(git -C "$CHECKOUT" rev-parse --short HEAD 2>/dev/null || echo "local") +dirty="" +if [[ -d "$CHECKOUT/.git" ]] && [[ -n "$(git -C "$CHECKOUT" status --porcelain)" ]]; then + dirty=".dirty" +fi +new_pkgver="dev.${short_sha}${dirty}" + +for PKG in "${PKGS[@]}"; do + PKGBUILD_DIR="$PKGBUILDS_ROOT/$PKG" + package_build_dir="$build_dir/$PKG" + mkdir -p "$package_build_dir" + cp -a "$PKGBUILD_DIR/." "$package_build_dir/" + + remove_pkgver_function "$package_build_dir/PKGBUILD" + sed -i "s/^pkgver=.*/pkgver=${new_pkgver}/" "$package_build_dir/PKGBUILD" + + echo "Building $PKG ${new_pkgver} from $CHECKOUT" + echo " build dir: $package_build_dir" + echo " PKGBUILD : $PKGBUILD_DIR/PKGBUILD" + echo + + ( + cd "$package_build_dir" + OMARCHY_SRC="$CHECKOUT" makepkg -s --skipchecksums --noconfirm "${MAKEPKG_ARGS[@]}" + ) + + # Install separately so we can pass --overwrite='*' (makepkg -i can't). + # Dev builds frequently conflict with files left behind by previous + # script-installed Omarchy versions; the build is the authoritative state. + built_pkg=$(ls -t "$package_build_dir"/*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | head -1) + if [[ -z $built_pkg ]]; then + echo "Error: no built package found in $package_build_dir" >&2 + exit 1 + fi + sudo pacman -U --noconfirm --overwrite='*' "$built_pkg" +done diff --git a/bin/omarchy-dev-status b/bin/omarchy-dev-status new file mode 100755 index 00000000..8cf621a9 --- /dev/null +++ b/bin/omarchy-dev-status @@ -0,0 +1,51 @@ +#!/bin/bash + +# omarchy:summary=Show the current Omarchy dev-link state +# omarchy:group=dev + +default_target="/usr/share/omarchy" +configured="$default_target" +conf_present=0 +linked=0 + +if [[ -f /etc/omarchy.conf ]]; then + conf_present=1 + configured=$( + OMARCHY_PATH= + # shellcheck disable=SC1091 + . /etc/omarchy.conf + printf '%s' "${OMARCHY_PATH:-}" + ) + if [[ $configured != "$default_target" ]]; then + linked=1 + fi +fi + +if (( linked )); then + echo "dev-link: ACTIVE" + echo " /etc/omarchy.conf -> OMARCHY_PATH=$configured" +else + echo "dev-link: inactive" + if (( conf_present )); then + echo " /etc/omarchy.conf -> OMARCHY_PATH=$configured (default guard)" + fi +fi + +echo " current shell: OMARCHY_PATH=${OMARCHY_PATH:-}" + +if (( linked )) && [[ ${OMARCHY_PATH:-} != "$configured" ]]; then + echo + echo "Note: this shell predates dev-link. Open a new shell to pick up the change," + echo "or 'export OMARCHY_PATH=$configured' to update just this shell." +elif (( ! linked )) && [[ ${OMARCHY_PATH:-$default_target} != "$default_target" ]]; then + echo + echo "Note: no dev-link is configured, but this shell still has a stale OMARCHY_PATH." + echo "Open a new shell or run 'export OMARCHY_PATH=$default_target'." +fi + +if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then + hypr_path=$(hyprctl getoption -j env 2>/dev/null | sed -n 's/.*OMARCHY_PATH=\([^"]*\).*/\1/p' | head -1) + if [[ -n "$hypr_path" ]]; then + echo " hyprland session: OMARCHY_PATH=$hypr_path" + fi +fi diff --git a/bin/omarchy-dev-ui-preview b/bin/omarchy-dev-ui-preview new file mode 100755 index 00000000..17ed2579 --- /dev/null +++ b/bin/omarchy-dev-ui-preview @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Open the omarchy-shell dev gallery (qs.Ui kit preview) +# omarchy:args=[section] +# omarchy:examples=omarchy dev ui-preview | omarchy dev ui-preview button | omarchy dev ui-preview button-group | omarchy dev ui-preview slider +# +# Pass a section name to jump straight to that component instead of +# scrolling from the top. Section names match the cursor section ids in +# shell/plugins/dev-gallery/GalleryPanel.qml — `button`, `button-group`, +# `cursor-surface`, `slider`, `toggle`, `dropdown`, etc. Unknown names +# are ignored and the gallery opens at its normal default position. + +if (( $# > 0 )) && [[ -n $1 ]]; then + section="$1" + payload=$(printf '{"section":"%s"}' "${section//\"/}") +else + payload='{}' +fi + +omarchy-shell shell summon omarchy.dev-gallery "$payload" diff --git a/bin/omarchy-dev-unlink b/bin/omarchy-dev-unlink new file mode 100755 index 00000000..15f5783a --- /dev/null +++ b/bin/omarchy-dev-unlink @@ -0,0 +1,82 @@ +#!/bin/bash + +# omarchy:summary=Restore Omarchy to the package install (undo omarchy-dev-link) +# omarchy:group=dev + +set -euo pipefail + +if [[ $EUID -eq 0 ]]; then + echo "Error: run omarchy-dev-unlink as your user, not under sudo." >&2 + exit 1 +fi + +if [[ ${1:-} == "-h" || ${1:-} == "--help" ]]; then + cat <}" + else + echo "/etc/omarchy.conf already points at $default_target." + fi +elif [[ ${OMARCHY_PATH:-$default_target} != "$default_target" ]]; then + prior_target="$OMARCHY_PATH" + echo "Not currently linked: /etc/omarchy.conf is absent." + echo "This shell still has OMARCHY_PATH=$OMARCHY_PATH; writing the default guard." +else + echo "Not currently linked. Writing the default OMARCHY_PATH guard." +fi + +printf 'export OMARCHY_PATH="%s"\n' "$default_target" | sudo tee /etc/omarchy.conf >/dev/null +echo "Set /etc/omarchy.conf -> OMARCHY_PATH=$default_target" + +export OMARCHY_PATH="$default_target" +if [[ -n $prior_target && $prior_target != "$default_target" ]]; then + PATH=$(printf '%s' "$PATH" | tr ':' '\n' | awk -v drop="$prior_target/bin" '$0 != drop' | paste -sd:) + export PATH +fi + +if command -v systemctl >/dev/null 2>&1; then + if systemctl --user import-environment OMARCHY_PATH PATH 2>/dev/null; then + echo " Updated systemd --user env." + fi +fi + +if command -v hyprctl >/dev/null 2>&1 && hyprctl version &>/dev/null; then + hyprctl setenv OMARCHY_PATH "$default_target" >/dev/null + hyprctl setenv PATH "$PATH" >/dev/null + echo " Updated Hyprland session env." + + if pgrep -x quickshell >/dev/null 2>&1; then + omarchy-restart-shell + echo " Restarted omarchy-shell." + fi + + hyprctl reload >/dev/null + echo " Reloaded Hyprland config." +fi + +echo +if (( linked )) || [[ -n $prior_target ]]; then + echo "Done. Existing shells still have the old OMARCHY_PATH until restarted." + echo "For this shell, run: export OMARCHY_PATH=$default_target" +else + echo "Done." +fi diff --git a/bin/omarchy-dns b/bin/omarchy-dns new file mode 100755 index 00000000..928743bf --- /dev/null +++ b/bin/omarchy-dns @@ -0,0 +1,133 @@ +#!/bin/bash + +# omarchy:summary=Show or configure the system DNS provider +# omarchy:args=[Cloudflare|Google|DHCP|Custom] +# omarchy:examples=omarchy dns | omarchy dns Cloudflare | omarchy dns Custom + +set -euo pipefail + +current_dns_provider() { + local dns="" + local compact="" + + dns=$(awk -F= ' + /^[[:space:]]*#/ { next } + /^[[:space:]]*DNS[[:space:]]*=/ { + value=$0 + sub(/^[^=]*=/, "", value) + print value + exit + } + ' /etc/systemd/resolved.conf 2>/dev/null || true) + + compact=$(printf '%s' "$dns" | tr -d '[:space:]') + + if [[ -z $compact ]]; then + echo "DHCP" + elif [[ $dns == *"cloudflare-dns.com"* || $dns == *"1.1.1.1"* || $dns == *"2606:4700:4700::1111"* ]]; then + echo "Cloudflare" + elif [[ $dns == *"dns.google"* || $dns == *"8.8.8.8"* || $dns == *"2001:4860:4860::8888"* ]]; then + echo "Google" + else + echo "Custom" + fi +} + +require_root() { + if (( EUID != 0 )); then + exec pkexec omarchy-dns "$@" + fi +} + +lock_dns_to_resolved() { + local file="" + + for file in /etc/systemd/network/*.network; do + [[ -f $file ]] || continue + if ! grep -q "^\[DHCPv4\]" "$file"; then continue; fi + + if ! sed -n '/^\[DHCPv4\]/,/^\[/p' "$file" | grep -q "^UseDNS="; then + sed -i '/^\[DHCPv4\]/a UseDNS=no' "$file" + fi + + if grep -q "^\[IPv6AcceptRA\]" "$file" && ! sed -n '/^\[IPv6AcceptRA\]/,/^\[/p' "$file" | grep -q "^UseDNS="; then + sed -i '/^\[IPv6AcceptRA\]/a UseDNS=no' "$file" + fi + done +} + +unlock_dns_to_dhcp() { + local file="" + + for file in /etc/systemd/network/*.network; do + [[ -f $file ]] || continue + sed -i '/^\[DHCPv4\]/{n;/^UseDNS=no$/d}' "$file" + sed -i '/^\[IPv6AcceptRA\]/{n;/^UseDNS=no$/d}' "$file" + done +} + +if (( $# == 0 )); then + current_dns_provider + exit 0 +fi + +provider="$1" + +case "$provider" in +Cloudflare) + require_root "$provider" + tee /etc/systemd/resolved.conf >/dev/null <<'EOF' +[Resolve] +DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com +FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net +DNSOverTLS=opportunistic +EOF + lock_dns_to_resolved + ;; + +Google) + require_root "$provider" + tee /etc/systemd/resolved.conf >/dev/null <<'EOF' +[Resolve] +DNS=8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google +FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net +DNSOverTLS=opportunistic +EOF + lock_dns_to_resolved + ;; + +DHCP) + require_root "$provider" + tee /etc/systemd/resolved.conf >/dev/null <<'EOF' +[Resolve] +DNSOverTLS=no +EOF + unlock_dns_to_dhcp + ;; + +Custom) + require_root "$provider" + echo "Enter your DNS servers (space-separated, e.g. '192.168.1.1 1.1.1.1'):" + if ! read -r dns_servers; then + dns_servers="" + fi + + if [[ -z $dns_servers ]]; then + echo "Error: No DNS servers provided." >&2 + exit 1 + fi + + tee /etc/systemd/resolved.conf >/dev/null <&2 + exit 1 + ;; +esac + +systemctl restart systemd-networkd systemd-resolved diff --git a/bin/omarchy-finalize-user b/bin/omarchy-finalize-user new file mode 100755 index 00000000..1775c3f2 --- /dev/null +++ b/bin/omarchy-finalize-user @@ -0,0 +1,133 @@ +#!/bin/bash + +# omarchy:summary=Finalize Omarchy user setup (runtime tweaks /etc/skel can't do) +# omarchy:group=finalize +# omarchy:examples=omarchy finalize user | omarchy finalize user --force + +set -euo pipefail + +usage() { + cat <&2 + exit 1 +fi + +force=0 +first_install=0 +while (($#)); do + case "$1" in + --force) + force=1 + shift + ;; + --first-install) + first_install=1 + force=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +state_dir="$HOME/.local/state/omarchy" +marker="$state_dir/finalize-user.done" +mkdir -p "$state_dir" + +if [[ -f $marker && $force -eq 0 ]]; then + echo "User finalization already complete (rerun with --force to refresh)." + exit 0 +fi + +export OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}" +export OMARCHY_INSTALL="${OMARCHY_INSTALL:-$OMARCHY_PATH/install}" +export OMARCHY_SETUP_CONTEXT="${OMARCHY_SETUP_CONTEXT:-runtime}" +export PATH="$OMARCHY_PATH/bin:$PATH" + +if (( first_install )); then + export OMARCHY_SETUP_CONTEXT=iso-chroot +fi + +if [[ -n ${OMARCHY_INSTALL_LOG_FILE:-} && -f $OMARCHY_INSTALL/helpers/logging.sh ]]; then + source "$OMARCHY_INSTALL/helpers/logging.sh" +else + run_logged() { + local script="$1" + bash -eE -c 'source "$1"' bash "$script" + } +fi + +# Dev-aware skill symlinks. Cannot live in /etc/skel because OMARCHY_PATH may +# point at a dev checkout (omarchy dev link) where the target differs. +mkdir -p ~/.agents/skills ~/.claude/skills ~/.codex/skills ~/.pi/agent/skills +ln -sfn "$OMARCHY_PATH/default/omarchy-skill" ~/.agents/skills/omarchy +ln -sfn "$OMARCHY_PATH/default/omarchy-skill" ~/.claude/skills/omarchy +ln -sfn "$OMARCHY_PATH/default/omarchy-skill" ~/.codex/skills/omarchy +ln -sfn "$OMARCHY_PATH/default/omarchy-skill" ~/.pi/agent/skills/omarchy + +mkdir -p ~/Downloads ~/Pictures ~/Videos ~/.config/gtk-3.0 +xdg-user-dirs-update --set TEMPLATES "$HOME" +xdg-user-dirs-update --set PUBLICSHARE "$HOME" +xdg-user-dirs-update --set DESKTOP "$HOME" +rmdir ~/Templates ~/Public ~/Desktop 2>/dev/null || true +touch ~/.config/gtk-3.0/bookmarks +for dir in Downloads Projects Pictures Videos; do + bookmark="file://$HOME/$dir $dir" + grep -qxF "$bookmark" ~/.config/gtk-3.0/bookmarks || echo "$bookmark" >>~/.config/gtk-3.0/bookmarks +done + +conf=/etc/vconsole.conf +hyprlua="$HOME/.config/hypr/input.lua" +if [[ -f $conf && -f $hyprlua ]]; then + sed -i '/^[[:space:]]*kb_layout[[:space:]]*=/d' "$hyprlua" + sed -i '/^[[:space:]]*kb_variant[[:space:]]*=/d' "$hyprlua" + + if grep -q '^XKBLAYOUT=' "$conf"; then + layout=$(grep '^XKBLAYOUT=' "$conf" | cut -d= -f2 | tr -d '"') + sed -i "/^[[:space:]]*kb_options *=/i\ kb_layout = \"$layout\"," "$hyprlua" + fi + if grep -q '^XKBVARIANT=' "$conf"; then + variant=$(grep '^XKBVARIANT=' "$conf" | cut -d= -f2 | tr -d '"') + sed -i "/^[[:space:]]*kb_options *=/i\ kb_variant = \"$variant\"," "$hyprlua" + fi +fi + +source "$OMARCHY_INSTALL/user/all.sh" + +omarchy-refresh-applications +xdg-settings set default-web-browser chromium.desktop +xdg-mime default HEY.desktop x-scheme-handler/mailto + +if (( first_install )); then + mkdir -p "$state_dir/migrations/user" + for migration in "$OMARCHY_PATH"/migrations/user/*.sh; do + [[ -f $migration ]] && touch "$state_dir/migrations/user/$(basename "$migration")" + done +fi + +touch "$marker" +echo "User finalization complete." diff --git a/bin/omarchy-first-run b/bin/omarchy-first-run index 023d1a65..6f719d53 100755 --- a/bin/omarchy-first-run +++ b/bin/omarchy-first-run @@ -1,27 +1,135 @@ #!/bin/bash -# omarchy:summary=Finish the installation of Omarchy with items that can only be done after logging in. -# omarchy:requires-sudo=true +# omarchy:summary=Finish first-login setup for Omarchy. +# omarchy:args=[--force] set -e -FIRST_RUN_MODE=~/.local/state/omarchy/first-run.mode +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done - bash "$OMARCHY_PATH/install/first-run/welcome.sh" - bash "$OMARCHY_PATH/install/first-run/wifi.sh" +omarchy-finalize-user "${finalize_user_args[@]}" || true + +state_dir=~/.local/state/omarchy +mkdir -p "$state_dir" + +FIRST_RUN_LOG="$state_dir/first-run.log" +first_run_failed=0 + +log_first_run() { + printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$FIRST_RUN_LOG" +} + +notification_server_ready() { + if command -v gdbus >/dev/null 2>&1; then + gdbus call --session \ + --dest org.freedesktop.Notifications \ + --object-path /org/freedesktop/Notifications \ + --method org.freedesktop.Notifications.GetServerInformation >/dev/null 2>&1 + elif command -v busctl >/dev/null 2>&1; then + busctl --user call \ + org.freedesktop.Notifications \ + /org/freedesktop/Notifications \ + org.freedesktop.Notifications \ + GetServerInformation >/dev/null 2>&1 + else + return 0 + fi +} + +wait_for_notifications() { + command -v omarchy-shell >/dev/null || return 0 + + for _ in {1..100}; do + if omarchy-shell notifications ping >/dev/null 2>&1 && notification_server_ready; then + return 0 + fi + sleep 0.1 + done + + log_first_run "Timed out waiting for notification service; continuing" + return 0 +} + +run_first_run_step() { + local name="$1" + shift + + log_first_run "Starting: $name" + if "$@"; then + log_first_run "Completed: $name" + else + local status=$? + first_run_failed=1 + log_first_run "Failed: $name (exit code: $status)" + fi +} + +wait_for_notifications + +MIGRATION_NOTIFY_WATCH_MARKER="$state_dir/user-migration-notify-watch-enabled" +if [[ ! -f $MIGRATION_NOTIFY_WATCH_MARKER || $force -eq 1 ]]; then + if systemctl --user enable --now omarchy-update-user-notify.path >/dev/null 2>&1; then + touch "$MIGRATION_NOTIFY_WATCH_MARKER" + fi +fi + +run_first_run_step "notify about pending user migrations" omarchy-migrate-notify + +USER_MARKER="$state_dir/first-run-user.done" +if [[ ! -f $USER_MARKER || $force -eq 1 ]]; then + run_first_run_step "install Voxtype post-update hook" \ + omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/install-voxtype.hook" + + run_first_run_step "enable user systemd units" \ + bash "$OMARCHY_PATH/install/user/first-run/enable-user-units.sh" + run_first_run_step "set GNOME theme" \ + bash "$OMARCHY_PATH/install/user/first-run/gnome-theme.sh" + run_first_run_step "set GTK primary paste" \ + bash "$OMARCHY_PATH/install/user/first-run/gtk-primary-paste.sh" + + wait_for_notifications + run_first_run_step "show welcome notification" \ + bash "$OMARCHY_PATH/install/user/first-run/welcome.sh" + # The first-run notification scripts register action callbacks in background + # notify-send processes. Give the notification server a tick to ingest the + # welcome toast before queueing the Wi-Fi/update toasts. + sleep 0.3 + run_first_run_step "show Wi-Fi/update notifications" \ + bash "$OMARCHY_PATH/install/user/first-run/wifi.sh" + + if (( first_run_failed == 0 )); then + touch "$USER_MARKER" + else + log_first_run "One or more first-run steps failed; first-run will retry next login" + fi +else + echo "First-run already complete (rerun with --force to refresh)." fi diff --git a/bin/omarchy-font-current b/bin/omarchy-font-current index ead8c3a3..839bd4db 100755 --- a/bin/omarchy-font-current +++ b/bin/omarchy-font-current @@ -3,4 +3,7 @@ # omarchy:summary=Show current monospace font # omarchy:examples=omarchy font current -grep -oP 'font-family:\s*["'\'']?\K[^;"'\'']+' ~/.config/waybar/style.css | head -n1 +# fontconfig is the source of truth. fc-match returns a comma-separated +# alias list (e.g. "JetBrainsMono Nerd Font,JetBrainsMono NF") so take +# the first entry. +fc-match monospace -f '%{family}\n' | head -n1 | cut -d, -f1 diff --git a/bin/omarchy-font-set b/bin/omarchy-font-set index 44bb9833..59c22ca5 100755 --- a/bin/omarchy-font-set +++ b/bin/omarchy-font-set @@ -4,52 +4,87 @@ # omarchy:args= # omarchy:examples=omarchy font list | omarchy font set "CaskaydiaMono Nerd Font" -font_name="$1" +usage() { + echo "Usage: omarchy-font-set " +} -if [[ -n $font_name ]]; then - if fc-list | grep -iq "$font_name"; then - if [[ -f ~/.config/alacritty/alacritty.toml ]]; then - sed -i "s/family = \".*\"/family = \"$font_name\"/g" ~/.config/alacritty/alacritty.toml - fi +font_name="${1:-}" +omarchy_root="${OMARCHY_PATH:-/usr/share/omarchy}" - if [[ -f ~/.config/kitty/kitty.conf ]]; then - sed -i "s/^font_family .*/font_family $font_name/g" ~/.config/kitty/kitty.conf - pkill -USR1 kitty - fi +case "$font_name" in + -h|--help) + usage + exit 0 + ;; + "") + usage >&2 + exit 1 + ;; +esac - if [[ -f ~/.config/ghostty/config ]]; then - sed -i "s/font-family = \".*\"/font-family = \"$font_name\"/g" ~/.config/ghostty/config - pkill -SIGUSR2 ghostty - fi +if ! fc-list | grep -Fqi -- "$font_name"; then + echo "Font '$font_name' not found." + exit 1 +fi - if [[ -f ~/.config/foot/foot.ini ]]; then - sed -i "s/^font=.*/font=$font_name:size=9/g" ~/.config/foot/foot.ini - fi +if [[ -f ~/.config/alacritty/alacritty.toml ]]; then + sed -i "s/family = \".*\"/family = \"$font_name\"/g" ~/.config/alacritty/alacritty.toml +fi - sed -i "s/font_family = .*/font_family = $font_name/g" ~/.config/hypr/hyprlock.conf - sed -i "s/font-family: .*/font-family: '$font_name';/g" ~/.config/waybar/style.css - sed -i "s/font-family: .*/font-family: '$font_name';/g" ~/.config/swayosd/style.css - xmlstarlet ed -L \ - -u '//match[@target="pattern"][test/string="monospace"]/edit[@name="family"]/string' \ - -v "$font_name" \ - ~/.config/fontconfig/fonts.conf +if [[ -f ~/.config/kitty/kitty.conf ]]; then + sed -i "s/^font_family .*/font_family $font_name/g" ~/.config/kitty/kitty.conf + pkill -USR1 kitty +fi - omarchy-restart-waybar - omarchy-restart-swayosd +if [[ -f ~/.config/ghostty/config ]]; then + sed -i "s/font-family = \".*\"/font-family = \"$font_name\"/g" ~/.config/ghostty/config + pkill -SIGUSR2 ghostty +fi - if pgrep -x ghostty; then - notify-send -u low " You must restart Ghostty to see font change" - fi +if [[ -f ~/.config/foot/foot.ini ]]; then + sed -i "s/^font=.*/font=$font_name:size=9/g" ~/.config/foot/foot.ini +fi - if pgrep -x foot; then - notify-send -u low " You must restart Foot to see font change" - fi - - omarchy-hook font-set "$font_name" - else - echo "Font '$font_name' not found." +# fontconfig is the canonical source of truth — the omarchy shell, Qt apps, +# and anything resolving "monospace" all read from here. The shipped default +# is package-owned; create a user override only when the user changes fonts. +fontconfig_file="$HOME/.config/fontconfig/fonts.conf" +if [[ ! -f $fontconfig_file ]]; then + fontconfig_default="$omarchy_root/default/fontconfig/conf.avail/50-omarchy.conf" + if [[ ! -f $fontconfig_default ]]; then + echo "Default fontconfig file not found: $fontconfig_default" >&2 exit 1 fi -else - echo "Usage: omarchy-font-set " + mkdir -p "$(dirname "$fontconfig_file")" + cp "$fontconfig_default" "$fontconfig_file" fi + +# We own the markup in 50-omarchy.conf, so the monospace block is predictable: +# the FAMILY immediately after monospace is +# the family we're replacing. +tmp=$(mktemp) +if ! awk -v new_font="$font_name" ' + in_mono && /[^<]*<\/string>/ { + sub(/[^<]*<\/string>/, "" new_font "") + in_mono = 0 + } + /monospace<\/string>/ { in_mono = 1 } + { print } +' "$fontconfig_file" >"$tmp"; then + rm -f "$tmp" + echo "Failed to update $fontconfig_file" >&2 + exit 1 +fi +mv "$tmp" "$fontconfig_file" + +omarchy-restart-shell + +if pgrep -x ghostty; then + omarchy-notification-send -g "You must restart Ghostty to see font change" +fi + +if pgrep -x foot; then + omarchy-notification-send -g "You must restart Foot to see font change" +fi + +omarchy-hook font-set "$font_name" diff --git a/bin/omarchy-games-retro-cores b/bin/omarchy-games-retro-cores new file mode 100755 index 00000000..a81e9bb0 --- /dev/null +++ b/bin/omarchy-games-retro-cores @@ -0,0 +1,39 @@ +#!/bin/bash + +# omarchy:summary=List installed RetroArch core names + +set -e + +core_dir="/usr/lib/libretro" +preferred_cores=( + "Amstrad CPC|cap32" + "Arcade FBNeo|fbneo" + "Arcade MAME|mame" + "Commodore Amiga|puae" + "Commodore C128|vice_x128" + "Commodore C64|vice_x64" + "Commodore VIC-20|vice_xvic" + "Nintendo DS|desmume" + "Nintendo Game Boy / Color|gambatte" + "Nintendo Game Boy Advance|mgba" + "Nintendo GameCube / Wii|dolphin" + "Nintendo NES / Famicom|mesen" + "Nintendo 64|parallel_n64" + "Nintendo SNES / SFC|snes9x" + "NEC PC Engine / TurboGrafx-16|mednafen_pce_fast" + "NEC PC Engine CD / TurboGrafx-CD|mednafen_pce" + "NEC PC Engine SuperGrafx|mednafen_supergrafx" + "Sega Dreamcast|flycast" + "Sega Mega Drive / Master System / Game Gear|genesis_plus_gx" + "Sega Saturn|kronos" + "Sony PlayStation|mednafen_psx_hw" + "Sony PlayStation Portable|ppsspp" +) + +[[ -d $core_dir ]] || exit 0 + +for preferred_core in "${preferred_cores[@]}"; do + label="${preferred_core%%|*}" + core="${preferred_core#*|}" + [[ -f $core_dir/${core}_libretro.so ]] && printf '%s (%s)\n' "$label" "$core" +done diff --git a/bin/omarchy-games-retro-install b/bin/omarchy-games-retro-install new file mode 100755 index 00000000..9b30bb74 --- /dev/null +++ b/bin/omarchy-games-retro-install @@ -0,0 +1,72 @@ +#!/bin/bash + +# omarchy:summary=Create a desktop launcher for a RetroArch game +# omarchy:args=[core path-to-game] +# omarchy:examples=omarchy games retro install snes9x ~/Games/roms/snes/game.sfc | omarchy-games-retro-install /usr/lib/libretro/mgba_libretro.so ~/Games/roms/gba/game.gba + +set -e + +if (( $# == 0 )); then + mapfile -t cores < <(omarchy-games-retro-cores) + + if (( ${#cores[@]} == 0 )); then + omarchy-notification-send -g 󰯉 "No RetroArch cores found" "/usr/lib/libretro" + exit 1 + fi + + core=$(omarchy-menu-select "RetroArch core" "${cores[@]}") || exit 0 + [[ -n $core ]] || exit 0 + core="${core##*(}" + core="${core%)}" + + game_path=$(omarchy-menu-file "Retro game" "$HOME/Games/roms" "7z bin ccd chd cue dmg elf fds gb gba gbc iso lha m3u md n64 nds nes pbp sfc smc swc zip z64") || exit 0 + [[ -n $game_path ]] || exit 0 +elif (( $# == 2 )); then + core="$1" + game_path="$2" +else + echo "Usage: omarchy-games-retro-install [core path-to-game]" + echo "Example: omarchy-games-retro-install snes9x ~/Games/roms/snes/game.sfc" + exit 1 +fi + +if [[ ! -f $game_path ]]; then + echo "Game not found: $game_path" + exit 1 +fi + +if [[ $core == */* ]]; then + core_path="$core" +else + core_path="/usr/lib/libretro/${core}_libretro.so" +fi + +if [[ ! -f $core_path ]]; then + echo "Core not found: $core_path" + exit 1 +fi + +game_name=$(printf '%s' "${game_path##*/}" | sed 's/\.[^.]*$//; s/[[:space:]]*([^)]*)//g; s/[[:space:]]\+/ /g; s/^ //; s/ $//' | perl -Mopen=locale -pe 's/(^|[[:space:]])([^[:space:]])/$1\U$2/g') +desktop_name="$game_name" +desktop_id=$(printf '%s' "$desktop_name" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//; s/-$//') +desktop_dir="$HOME/.local/share/applications" +desktop_file="$desktop_dir/$desktop_id.desktop" +mkdir -p "$desktop_dir" + +cat >"$desktop_file" </dev/null || true + +omarchy-notification-send -g 󰯉 "$game_name installed" "Start it with Super + Space" diff --git a/bin/omarchy-hibernation-remove b/bin/omarchy-hibernation-remove index 49bad5bd..f06396b5 100755 --- a/bin/omarchy-hibernation-remove +++ b/bin/omarchy-hibernation-remove @@ -44,11 +44,6 @@ if grep -Fq "$SWAP_FILE" /etc/fstab; then sudo sed -i '/^# Btrfs swapfile for system hibernation$/d' /etc/fstab fi -# Remove suspend-then-hibernate configuration -echo "Removing suspend-then-hibernate configuration" -sudo rm -f /etc/systemd/logind.conf.d/lid.conf -sudo rm -f /etc/systemd/sleep.conf.d/hibernate.conf - # Remove mkinitcpio resume hook echo "Removing resume hook" sudo rm "$MKINITCPIO_CONF" diff --git a/bin/omarchy-hibernation-setup b/bin/omarchy-hibernation-setup index d8e32c94..775c4e2c 100755 --- a/bin/omarchy-hibernation-setup +++ b/bin/omarchy-hibernation-setup @@ -38,8 +38,9 @@ if [[ -f $MKINITCPIO_CONF ]] && grep -q "^HOOKS+=(resume)$" "$MKINITCPIO_CONF"; if [[ -n $RESUME_OFFSET ]]; then echo "Fixing empty resume_offset ($RESUME_OFFSET)" sudo sed -i "s/resume_offset=\"$/resume_offset=$RESUME_OFFSET\"/" "$RESUME_DROP_IN" - sudo sed -i "s/resume_offset=\"$/resume_offset=$RESUME_OFFSET\"/" /etc/default/limine - $NO_REBUILD || sudo limine-mkinitcpio + if ! $NO_REBUILD; then + sudo limine-mkinitcpio + fi fi fi echo "Hibernation is already set up" @@ -100,7 +101,6 @@ if [[ ! -f $RESUME_DROP_IN ]]; then if [[ -n $RESUME_OFFSET ]]; then sudo mkdir -p /etc/limine-entry-tool.d echo "KERNEL_CMDLINE[default]+=\" resume=$RESUME_DEVICE resume_offset=$RESUME_OFFSET\"" | sudo tee "$RESUME_DROP_IN" >/dev/null - sudo tee -a /etc/default/limine < "$RESUME_DROP_IN" >/dev/null else echo "Warning: Could not determine resume offset for $SWAP_FILE" >&2 fi @@ -113,7 +113,6 @@ if grep -q "\[s2idle\]" /sys/power/mem_sleep 2>/dev/null; then echo "Enabling ACPI RTC alarm for s2idle suspend" sudo mkdir -p /etc/limine-entry-tool.d echo 'KERNEL_CMDLINE[default]+=" rtc_cmos.use_acpi_alarm=1"' | sudo tee "$LIMINE_DROP_IN" >/dev/null - sudo tee -a /etc/default/limine < "$LIMINE_DROP_IN" >/dev/null fi fi diff --git a/bin/omarchy-hw-display b/bin/omarchy-hw-display new file mode 100755 index 00000000..dcab14ac --- /dev/null +++ b/bin/omarchy-hw-display @@ -0,0 +1,19 @@ +#!/bin/bash + +# omarchy:summary=Print the most likely display backlight device. +# omarchy:examples=omarchy-hw-display + +# Start with the first possible output, then refine to the most likely given an order heuristic. +device="$(ls -1 /sys/class/backlight 2>/dev/null | head -n1)" +for candidate in amdgpu_bl* intel_backlight acpi_video*; do + if [[ -e /sys/class/backlight/$candidate ]]; then + device="$candidate" + break + fi +done + +if [[ -n $device ]]; then + printf '%s\n' "$device" +else + exit 1 +fi diff --git a/bin/omarchy-hw-external-monitors b/bin/omarchy-hw-external-monitors index 08ab52ff..3a5a837f 100755 --- a/bin/omarchy-hw-external-monitors +++ b/bin/omarchy-hw-external-monitors @@ -3,7 +3,7 @@ # omarchy:summary=Returns true when an external monitor is physically connected. for status in /sys/class/drm/card*-*/status; do - [[ "$status" == *-eDP-*/status ]] && continue - [[ "$(<"$status")" == "connected" ]] && exit 0 + [[ $status == *-eDP-*/status ]] && continue + [[ $(< $status) == "connected" ]] && exit 0 done exit 1 diff --git a/bin/omarchy-hw-nvidia b/bin/omarchy-hw-nvidia new file mode 100644 index 00000000..b50ca6e4 --- /dev/null +++ b/bin/omarchy-hw-nvidia @@ -0,0 +1,5 @@ +#!/bin/bash + +# omarchy:summary=Detect whether the computer has an NVIDIA GPU. + +lspci | grep -qi 'nvidia' &>/dev/null diff --git a/bin/omarchy-hw-recover-internal-monitor b/bin/omarchy-hw-recover-internal-monitor index 2ff125e7..182f54f3 100755 --- a/bin/omarchy-hw-recover-internal-monitor +++ b/bin/omarchy-hw-recover-internal-monitor @@ -2,7 +2,7 @@ # omarchy:summary=Clear the internal-monitor-disable toggle if no external display is connected. -TOGGLE="$HOME/.local/state/omarchy/toggles/hypr/internal-monitor-disable.conf" +TOGGLE="$HOME/.local/state/omarchy/toggles/hypr/internal-monitor-disable.lua" if [[ -f $TOGGLE ]] && ! omarchy-hw-external-monitors; then rm -f "$TOGGLE" diff --git a/bin/omarchy-hyprland-focus-app b/bin/omarchy-hyprland-focus-app new file mode 100755 index 00000000..d22d360c --- /dev/null +++ b/bin/omarchy-hyprland-focus-app @@ -0,0 +1,23 @@ +#!/bin/bash + +# omarchy:summary=Focus a Hyprland window by application class +# omarchy:args= +# omarchy:examples=omarchy hyprland focus app Slack + +usage() { + echo "Usage: omarchy-hyprland-focus-app " >&2 + exit 1 +} + +app=${1:-} +[[ -n $app ]] || usage + +address=$( + hyprctl clients -j 2>/dev/null | + jq -r --arg name "${app,,}" \ + '[.[] | select((.class // "") | ascii_downcase | startswith($name))] | first.address // empty' +) + +[[ -n $address ]] || exit 1 + +hyprctl dispatch focuswindow "address:$address" >/dev/null diff --git a/bin/omarchy-hyprland-monitor-internal b/bin/omarchy-hyprland-monitor-internal index 29f101d4..6b72c2bd 100755 --- a/bin/omarchy-hyprland-monitor-internal +++ b/bin/omarchy-hyprland-monitor-internal @@ -4,40 +4,50 @@ # omarchy:args= TOGGLE="internal-monitor-disable" -TOGGLE_FLAG="$HOME/.local/state/omarchy/toggles/hypr/$TOGGLE.conf" +TOGGLE_FLAG="$HOME/.local/state/omarchy/toggles/hypr/$TOGGLE.lua" MIRROR_TOGGLE="internal-monitor-mirror" # Get internal monitor name dynamically INTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | contains("eDP")).name' | head -n 1) -enable() { - if omarchy-hyprland-toggle-enabled "$TOGGLE"; then - omarchy-hyprland-toggle --disabled-notification "󰍹 Laptop display enabled" "$TOGGLE" +on() { + if omarchy-hyprland-toggle-enabled $TOGGLE; then + omarchy-hyprland-toggle $TOGGLE off + omarchy-notification-send -g 󰍹 "Laptop display enabled" fi } -disable() { +off() { if ! omarchy-hw-external-monitors; then - notify-send -u low "󰍹 Can't disable the only active display" + omarchy-notification-send -g 󰍹 "Can't disable the only active display" exit 1 fi - if omarchy-hyprland-toggle-disabled "$TOGGLE" && omarchy-hyprland-toggle-disabled "$MIRROR_TOGGLE"; then - echo "monitor=$INTERNAL,disable" >"$TOGGLE_FLAG" - notify-send -u low "󰍹 Laptop display disabled" + + if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then + printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG" + omarchy-notification-send -g 󰍹 "Laptop display disabled" hyprctl reload fi } recover() { - if ! omarchy-hw-external-monitors && omarchy-hyprland-toggle-enabled "$TOGGLE"; then - omarchy-hyprland-toggle "$TOGGLE" + if ! omarchy-hw-external-monitors && omarchy-hyprland-toggle-enabled $TOGGLE; then + omarchy-hyprland-toggle $TOGGLE off + fi +} + +toggle() { + if omarchy-hyprland-toggle-enabled $TOGGLE; then + on + else + off fi } case "$1" in - on) enable ;; - off) disable ;; - toggle) if omarchy-hyprland-toggle-enabled "$TOGGLE"; then enable; else disable; fi ;; + on) on ;; + off) off ;; + toggle) toggle ;; recover) recover ;; *) echo "Usage: $(basename "$0") {on|off|toggle|recover}" >&2 diff --git a/bin/omarchy-hyprland-monitor-internal-mirror b/bin/omarchy-hyprland-monitor-internal-mirror index 07a0ccad..451d4305 100755 --- a/bin/omarchy-hyprland-monitor-internal-mirror +++ b/bin/omarchy-hyprland-monitor-internal-mirror @@ -4,7 +4,7 @@ # omarchy:args= TOGGLE="internal-monitor-mirror" -TOGGLE_FLAG="$HOME/.local/state/omarchy/toggles/hypr/$TOGGLE.conf" +TOGGLE_FLAG="$HOME/.local/state/omarchy/toggles/hypr/$TOGGLE.lua" DISABLE_TOGGLE="internal-monitor-disable" # Get names dynamically @@ -12,44 +12,51 @@ INTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | contains("eDP")).na # Get the first available external monitor EXTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | contains("eDP") | not).name' | head -n 1) -enable() { - if [[ -z "$EXTERNAL" ]]; then - notify-send -u low "󰍹 No external monitors found for mirror" +on() { + if [[ -z $EXTERNAL ]]; then + omarchy-notification-send -g 󰍹 "No external monitors found for mirror" exit 1 fi - if [[ -z "$INTERNAL" ]]; then - notify-send -u low "󰍹 No laptop monitor found to mirror" + if [[ -z $INTERNAL ]]; then + omarchy-notification-send -g 󰍹 "No laptop monitor found to mirror" exit 1 fi - if omarchy-hyprland-toggle-enabled "$DISABLE_TOGGLE"; then - omarchy-hyprland-toggle "$DISABLE_TOGGLE" - fi + omarchy-hyprland-toggle $DISABLE_TOGGLE off - if omarchy-hyprland-toggle-disabled "$TOGGLE"; then - echo "monitor=$EXTERNAL, preferred, auto, 1, mirror, $INTERNAL" > "$TOGGLE_FLAG" - notify-send -u low "󰍹 Mirroring enabled ($EXTERNAL)" + if omarchy-hyprland-toggle-disabled $TOGGLE; then + printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG" + omarchy-notification-send -g 󰍹 "Mirroring enabled ($EXTERNAL)" hyprctl reload fi } -disable() { - if omarchy-hyprland-toggle-enabled "$TOGGLE"; then - omarchy-hyprland-toggle --disabled-notification "󰍹 Extended mode restored" "$TOGGLE" +off() { + if omarchy-hyprland-toggle-enabled $TOGGLE; then + omarchy-hyprland-toggle $TOGGLE off + omarchy-notification-send -g 󰍹 "Extended mode restored" + fi +} + +toggle() { + if omarchy-hyprland-toggle-enabled $TOGGLE; then + off + else + on fi } recover() { - if ! omarchy-hw-external-monitors && omarchy-hyprland-toggle-enabled "$TOGGLE"; then - omarchy-hyprland-toggle "$TOGGLE" + if ! omarchy-hw-external-monitors && omarchy-hyprland-toggle-enabled $TOGGLE; then + omarchy-hyprland-toggle $TOGGLE off fi } case "$1" in - on) enable ;; - off) disable ;; - toggle) if omarchy-hyprland-toggle-enabled "$TOGGLE"; then disable; else enable; fi ;; + on) on ;; + off) off ;; + toggle) toggle ;; recover) recover ;; *) echo "Usage: $(basename "$0") {on|off|toggle|recover}" >&2 diff --git a/bin/omarchy-hyprland-monitor-scaling b/bin/omarchy-hyprland-monitor-scaling new file mode 100755 index 00000000..004a2743 --- /dev/null +++ b/bin/omarchy-hyprland-monitor-scaling @@ -0,0 +1,87 @@ +#!/bin/bash + +# omarchy:summary=Show, set, or adjust focused Hyprland monitor scaling +# omarchy:args=[up|down|1|1.25|1.6|2|3|4] +# omarchy:examples=omarchy hyprland monitor scaling | omarchy hyprland monitor scaling 1.6 | omarchy hyprland monitor scaling up | omarchy hyprland monitor scaling down + +SCALES=(1 1.25 1.6 2 3 4) + +usage() { + echo "Usage: omarchy-hyprland-monitor-scaling [up|down|1|1.25|1.6|2|3|4]" +} + +focused_monitor_scale() { + hyprctl monitors -j | jq -er '.[] | select(.focused == true) | .scale' +} + +set_scale() { + local new_scale="$1" + local monitor_info="$(hyprctl monitors -j | jq -e -c '.[] | select(.focused == true)')" + local active_monitor="$(echo "$monitor_info" | jq -r '.name')" + local width="$(echo "$monitor_info" | jq -r '.width')" + local height="$(echo "$monitor_info" | jq -r '.height')" + local refresh_rate="$(echo "$monitor_info" | jq -r '.refreshRate')" + local monitor_lua="$HOME/.config/hypr/monitors.lua" + + hyprctl eval "hl.monitor({ output = \"$active_monitor\", mode = \"${width}x${height}@${refresh_rate}\", position = \"auto\", scale = $new_scale })" >/dev/null + + # Persist to monitors.lua if the user still has Omarchy's generic catch-all + # defaults, so the scale survives reboots. + if [[ -f $monitor_lua ]] && grep -q '^local omarchy_monitor_scale = ' "$monitor_lua"; then + sed -i -E \ + -e "s|^local omarchy_monitor_scale = .*|local omarchy_monitor_scale = ${new_scale}|" \ + -e "s|^local omarchy_gdk_scale = .*|local omarchy_gdk_scale = ${new_scale}|" \ + "$monitor_lua" + elif [[ -f $monitor_lua ]] && grep -Eq '^hl\.monitor\(\{ output = "", mode = "preferred", position = "auto", scale = ("auto"|[0-9.]+) \}\)' "$monitor_lua"; then + sed -i -E \ + -e "s|^(hl\.monitor\(\{ output = \"\", mode = \"preferred\", position = \"auto\", scale = )([^ ]+)( \}\))|\\1${new_scale}\\3|" \ + -e 's|^hl\.env\("GDK_SCALE", ".*"\)|hl.env("GDK_SCALE", "'"$new_scale"'")|' \ + "$monitor_lua" + fi +} + +scale_from_current() { + local direction="${1:-}" + + # Find the preset closest to the current scale (Hyprland may snap fractional + # scales to nearby values, so we can't match exactly). + awk -v direction="$direction" -v list="${SCALES[*]}" ' + NR == 1 { scale = $0; found = 1 } + END { + if (!found) exit 1 + + n = split(list, scales, " ") + best = 1; best_diff = 1e9 + for (i = 1; i <= n; i++) { + diff = scale - scales[i]; if (diff < 0) diff = -diff + if (diff < best_diff) { best_diff = diff; best = i } + } + + if (direction == "next" && best < n) best++ + else if (direction == "previous" && best > 1) best-- + + print scales[best] + }' +} + +case "${1:-}" in +"") + focused_monitor_scale | scale_from_current + ;; +-h | --help) + usage + ;; +up) + set_scale "$(focused_monitor_scale | scale_from_current next)" + ;; +down) + set_scale "$(focused_monitor_scale | scale_from_current previous)" + ;; +1 | 1.25 | 1.6 | 2 | 3 | 4) + set_scale "$1" + ;; +*) + usage >&2 + exit 1 + ;; +esac diff --git a/bin/omarchy-hyprland-monitor-scaling-cycle b/bin/omarchy-hyprland-monitor-scaling-cycle deleted file mode 100755 index 8628c17c..00000000 --- a/bin/omarchy-hyprland-monitor-scaling-cycle +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Cycle focused Hyprland monitor scaling through 1x, 1.25x, 1.6x, 2x, 3x, and 4x - -MONITOR_INFO=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true)') -ACTIVE_MONITOR=$(echo "$MONITOR_INFO" | jq -r '.name') -CURRENT_SCALE=$(echo "$MONITOR_INFO" | jq -r '.scale') -WIDTH=$(echo "$MONITOR_INFO" | jq -r '.width') -HEIGHT=$(echo "$MONITOR_INFO" | jq -r '.height') -REFRESH_RATE=$(echo "$MONITOR_INFO" | jq -r '.refreshRate') - -# Cycle through scales: 1 → 1.25 → 1.6 → 2 → 3 → 4 → 1 (or reverse with --reverse) -SCALES=(1 1.25 1.6 2 3 4) - -# Find the index of the scale closest to the current one (Hyprland may -# snap fractional scales to nearby values, so we can't match exactly) -CURRENT_IDX=$(awk -v s="$CURRENT_SCALE" -v list="${SCALES[*]}" 'BEGIN { - n = split(list, arr, " ") - best = 0; best_diff = 1e9 - for (i = 1; i <= n; i++) { - d = s - arr[i]; if (d < 0) d = -d - if (d < best_diff) { best_diff = d; best = i - 1 } - } - print best -}') - -if [[ "$1" == "--reverse" ]]; then - NEW_IDX=$(( (CURRENT_IDX - 1 + ${#SCALES[@]}) % ${#SCALES[@]} )) -else - NEW_IDX=$(( (CURRENT_IDX + 1) % ${#SCALES[@]} )) -fi - -NEW_SCALE=${SCALES[$NEW_IDX]} - -hyprctl keyword monitor "$ACTIVE_MONITOR,${WIDTH}x${HEIGHT}@${REFRESH_RATE},auto,$NEW_SCALE" - -# Persist to monitors.conf if the user has a single generic catch-all line -# (ignoring disabled monitors), so the scale survives reboots. -MONITOR_CONF="$HOME/.config/hypr/monitors.conf" -if [[ -f $MONITOR_CONF ]]; then - mapfile -t ACTIVE_LINES < <(grep -E '^[[:space:]]*monitor=' "$MONITOR_CONF" | grep -vE 'disable[[:space:]]*$') - if [[ ${#ACTIVE_LINES[@]} -eq 1 ]] && [[ "${ACTIVE_LINES[0]}" =~ ^monitor=,preferred,auto, ]]; then - sed -i -E "s|^(monitor=,preferred,auto,).*|\\1${NEW_SCALE}|" "$MONITOR_CONF" - fi -fi - -notify-send -u low "󰍹 Display scaling set to ${NEW_SCALE}x" diff --git a/bin/omarchy-hyprland-toggle b/bin/omarchy-hyprland-toggle index 363ac855..158e28ac 100755 --- a/bin/omarchy-hyprland-toggle +++ b/bin/omarchy-hyprland-toggle @@ -1,32 +1,54 @@ #!/bin/bash # omarchy:summary=Toggle permanent Hyprland flags by copying them into a directory that's sourced entirely. -# omarchy:args=[--enabled-notification ] [--disabled-notification ] +# omarchy:args= [on|off|toggle] -ENABLED_NOTIFICATION="" -DISABLED_NOTIFICATION="" +usage() { + echo "Usage: omarchy-hyprland-toggle [on|off|toggle]" >&2 +} -while [[ $# -gt 1 ]]; do - case $1 in - --enabled-notification) ENABLED_NOTIFICATION="$2"; shift 2 ;; - --disabled-notification) DISABLED_NOTIFICATION="$2"; shift 2 ;; - *) break ;; - esac -done - -FLAG_NAME="$1" -FLAG="$HOME/.local/state/omarchy/toggles/hypr/$FLAG_NAME.conf" -FLAG_SOURCE="$OMARCHY_PATH/default/hypr/toggles/$FLAG_NAME.conf" - -if [[ -f $FLAG ]]; then - rm $FLAG - [[ -n $DISABLED_NOTIFICATION ]] && notify-send -u low "$DISABLED_NOTIFICATION" -elif [[ -f $FLAG_SOURCE ]]; then - cp $FLAG_SOURCE $FLAG - [[ -n $ENABLED_NOTIFICATION ]] && notify-send -u low "$ENABLED_NOTIFICATION" -else - echo "Flag not found: $FLAG_NAME" +if (($# < 1)); then + usage exit 1 fi -hyprctl reload +FLAG_NAME="$1" +ACTION="${2:-toggle}" +FLAG_FILE="$HOME/.local/state/omarchy/toggles/hypr/$FLAG_NAME.lua" +FLAG_SOURCE="$OMARCHY_PATH/default/hypr/toggles/$FLAG_NAME.lua" + +on() { + if [[ -f $FLAG_SOURCE ]]; then + mkdir -p "$(dirname "$FLAG_FILE")" + cp "$FLAG_SOURCE" "$FLAG_FILE" + else + echo "Flag not found: $FLAG_NAME" >&2 + exit 1 + fi +} + +off() { + rm -f "$FLAG_FILE" +} + +toggle() { + if [[ -f $FLAG_FILE ]]; then + off + echo "off" + else + on + echo "on" + fi +} + +case $ACTION in + on) on ;; + off) off ;; + toggle) toggle ;; + *) + usage + exit 1 + ;; +esac + +hyprctl reload >/dev/null diff --git a/bin/omarchy-hyprland-toggle-disabled b/bin/omarchy-hyprland-toggle-disabled index 51707491..5414e2b5 100755 --- a/bin/omarchy-hyprland-toggle-disabled +++ b/bin/omarchy-hyprland-toggle-disabled @@ -3,4 +3,4 @@ # omarchy:summary=Check if a Hyprland toggle is currently disabled (missing). # omarchy:args= -[[ ! -f "$HOME/.local/state/omarchy/toggles/hypr/$1.conf" ]] +[[ ! -f "$HOME/.local/state/omarchy/toggles/hypr/$1.lua" ]] diff --git a/bin/omarchy-hyprland-toggle-enabled b/bin/omarchy-hyprland-toggle-enabled index cdc4e718..73bb7ba1 100755 --- a/bin/omarchy-hyprland-toggle-enabled +++ b/bin/omarchy-hyprland-toggle-enabled @@ -3,4 +3,4 @@ # omarchy:summary=Check if a Hyprland toggle is currently enabled. # omarchy:args= -[[ -f "$HOME/.local/state/omarchy/toggles/hypr/$1.conf" ]] +[[ -f "$HOME/.local/state/omarchy/toggles/hypr/$1.lua" ]] diff --git a/bin/omarchy-hyprland-window-close-all b/bin/omarchy-hyprland-window-close-all index dbf0d758..c88a640c 100755 --- a/bin/omarchy-hyprland-window-close-all +++ b/bin/omarchy-hyprland-window-close-all @@ -4,7 +4,9 @@ hyprctl clients -j | \ jq -r ".[].address" | \ - xargs -I{} hyprctl dispatch closewindow address:{} + while read -r addr; do + hyprctl dispatch "hl.dsp.window.close(\"address:$addr\")" >/dev/null 2>&1 || hyprctl dispatch closewindow "address:$addr" + done # Move to first workspace -hyprctl dispatch workspace 1 +hyprctl dispatch 'hl.dsp.focus({ workspace = "1" })' >/dev/null 2>&1 || hyprctl dispatch workspace 1 diff --git a/bin/omarchy-hyprland-window-pop b/bin/omarchy-hyprland-window-pop index 05451076..c115e091 100755 --- a/bin/omarchy-hyprland-window-pop +++ b/bin/omarchy-hyprland-window-pop @@ -11,24 +11,30 @@ y=${4:-} active=$(hyprctl activewindow -j) pinned=$(echo "$active" | jq ".pinned") addr=$(echo "$active" | jq -r ".address") +window="address:$addr" + +hypr_dispatch() { + local lua="$1" + shift + + hyprctl dispatch "$lua" >/dev/null 2>&1 || hyprctl dispatch "$@" >/dev/null +} if [[ $pinned == "true" ]]; then - hyprctl -q --batch \ - "dispatch pin address:$addr;" \ - "dispatch togglefloating address:$addr;" \ - "dispatch tagwindow -pop address:$addr;" + hypr_dispatch "hl.dsp.window.pin({ window = \"$window\" })" pin "$window" + hypr_dispatch "hl.dsp.window.float({ window = \"$window\", action = \"toggle\" })" togglefloating "$window" + hypr_dispatch "hl.dsp.window.tag({ window = \"$window\", tag = \"-pop\" })" tagwindow -pop "$window" elif [[ -n $addr ]]; then - hyprctl dispatch togglefloating address:$addr - hyprctl dispatch resizeactive exact $width $height address:$addr + hypr_dispatch "hl.dsp.window.float({ window = \"$window\", action = \"toggle\" })" togglefloating "$window" + hypr_dispatch "hl.dsp.window.resize({ window = \"$window\", x = $width, y = $height })" resizeactive exact "$width" "$height" "$window" if [[ -n $x && -n $y ]]; then - hyprctl dispatch moveactive $x $y address:$addr + hypr_dispatch "hl.dsp.window.move({ window = \"$window\", x = $x, y = $y })" moveactive "$x" "$y" "$window" else - hyprctl dispatch centerwindow address:$addr + hypr_dispatch "hl.dsp.window.center({ window = \"$window\" })" centerwindow "$window" fi - hyprctl -q --batch \ - "dispatch pin address:$addr;" \ - "dispatch alterzorder top address:$addr;" \ - "dispatch tagwindow +pop address:$addr;" + hypr_dispatch "hl.dsp.window.pin({ window = \"$window\" })" pin "$window" + hypr_dispatch "hl.dsp.window.alter_zorder({ window = \"$window\", mode = \"top\" })" alterzorder top "$window" + hypr_dispatch "hl.dsp.window.tag({ window = \"$window\", tag = \"+pop\" })" tagwindow +pop "$window" fi diff --git a/bin/omarchy-hyprland-window-single-square-aspect-toggle b/bin/omarchy-hyprland-window-single-square-aspect-toggle index d600c408..dd51f28d 100755 --- a/bin/omarchy-hyprland-window-single-square-aspect-toggle +++ b/bin/omarchy-hyprland-window-single-square-aspect-toggle @@ -2,7 +2,7 @@ # omarchy:summary=Toggle single-window square aspect ratio. -omarchy-hyprland-toggle \ - --enabled-notification " Enable single-window square aspect ratio" \ - --disabled-notification " Disable single-window square aspect ratio" \ - single-window-aspect-ratio +case $(omarchy-hyprland-toggle single-window-aspect-ratio) in + on) omarchy-notification-send -g  "Enable single-window square aspect ratio" ;; + off) omarchy-notification-send -g  "Disable single-window square aspect ratio" ;; +esac diff --git a/bin/omarchy-hyprland-window-tiled-fullscreen-toggle b/bin/omarchy-hyprland-window-tiled-fullscreen-toggle new file mode 100755 index 00000000..daa06ee2 --- /dev/null +++ b/bin/omarchy-hyprland-window-tiled-fullscreen-toggle @@ -0,0 +1,16 @@ +#!/bin/bash + +# omarchy:summary=Toggle tiled fullscreen for the focused Hyprland window + +set -euo pipefail + +active=$(hyprctl activewindow -j) +fullscreen_client=$(jq -r '.fullscreenClient // 0' <<<"$active") + +if [[ $fullscreen_client == "2" ]]; then + hyprctl dispatch 'hl.dsp.window.fullscreen_state({ internal = 0, client = 0 })' >/dev/null 2>&1 || \ + hyprctl dispatch fullscreenstate 0 0 >/dev/null +else + hyprctl dispatch 'hl.dsp.window.fullscreen_state({ internal = 0, client = 2 })' >/dev/null 2>&1 || \ + hyprctl dispatch fullscreenstate 0 2 >/dev/null +fi diff --git a/bin/omarchy-hyprland-window-transparency-toggle b/bin/omarchy-hyprland-window-transparency-toggle index 81afc272..1235969d 100755 --- a/bin/omarchy-hyprland-window-transparency-toggle +++ b/bin/omarchy-hyprland-window-transparency-toggle @@ -2,4 +2,6 @@ # omarchy:summary=Toggles transparency for the currently focused window. -hyprctl dispatch setprop "address:$(hyprctl activewindow -j | jq -r '.address')" opaque toggle +addr=$(hyprctl activewindow -j | jq -r '.address') +hyprctl dispatch "hl.dsp.window.set_prop({ window = \"address:$addr\", prop = \"opaque\", value = \"toggle\" })" >/dev/null 2>&1 || \ + hyprctl dispatch setprop "address:$addr" opaque toggle diff --git a/bin/omarchy-hyprland-window-width b/bin/omarchy-hyprland-window-width new file mode 100755 index 00000000..78bf5d89 --- /dev/null +++ b/bin/omarchy-hyprland-window-width @@ -0,0 +1,168 @@ +#!/bin/bash + +# omarchy:summary=Save or restore the focused Hyprland window width +# omarchy:args= +# omarchy:examples=omarchy hyprland window width save | omarchy-hyprland-window-width restore + +set -euo pipefail + +STATE_DIR="$HOME/.local/state/omarchy/windows" + +usage() { + echo "Usage: omarchy-hyprland-window-width save|restore" >&2 + exit 1 +} + +active_window() { + hyprctl activewindow -j 2>/dev/null +} + +window_key() { + jq -r '[.class, .initialClass, .title] | map(select(. != null and . != "")) | first // empty' <<<"$1" +} + +workspace_key() { + jq -r '.workspace.id // .workspace.name // empty' <<<"$1" +} + +state_file_for() { + local key="$1" + local workspace="$2" + local filename="workspace-${workspace}-${key}" + + filename="${filename//\//_}" + filename="${filename//$'\n'/_}" + + printf '%s/%s.width' "$STATE_DIR" "$filename" +} + +notify_missing_width() { + local key="$1" + local workspace="$2" + + omarchy-notification-send -g  "No saved width found for $key on workspace $workspace" "Use Super + Alt + Home to save one for this workspace." +} + +notify_saved_width() { + local key="$1" + local workspace="$2" + + omarchy-notification-send -g  "Saved width for $key on workspace $workspace" "Restore using Super + Home on this workspace." +} + +hypr_dispatch() { + local lua="$1" + shift + + hyprctl dispatch "$lua" >/dev/null 2>&1 || hyprctl dispatch "$@" >/dev/null +} + +window_width() { + local address="$1" + + hyprctl clients -j | jq -er --arg address "$address" '.[] | select(.address == $address) | .size[0]' +} + +resize_width_by() { + local window="$1" + local delta="$2" + + hypr_dispatch "hl.dsp.window.resize({ window = \"$window\", x = $delta, y = 0, relative = true })" resizeactive "$delta" 0 "$window" +} + +save_width() { + local active="$1" + local key="$2" + local workspace="$3" + local state_file="$4" + local tmp="" + local width="" + + width=$(jq -er '.size[0]' <<<"$active") + + mkdir -p "$STATE_DIR" + tmp=$(mktemp "$STATE_DIR/.width.XXXXXX") + printf '%s\n' "$width" >"$tmp" + mv "$tmp" "$state_file" + + notify_saved_width "$key" "$workspace" + echo "Saved width for $key on workspace $workspace" +} + +restore_width() { + local active="$1" + local key="$2" + local workspace="$3" + local state_file="$4" + local address="" + local current_width="" + local delta="" + local direction="" + local next_width="" + local probe="" + local probe_delta="" + local width="" + local window="" + + if [[ ! -f $state_file ]]; then + notify_missing_width "$key" "$workspace" + exit 1 + fi + + width=$(<"$state_file") + [[ $width =~ ^[0-9]+$ ]] || exit 1 + + address=$(jq -r '.address // empty' <<<"$active") + [[ -n $address ]] || exit 1 + + window="address:$address" + + current_width=$(window_width "$address") + ((current_width == width)) && return + + for probe in 10 -10; do + resize_width_by "$window" "$probe" + next_width=$(window_width "$address") + + if ((next_width != current_width)); then + if (((next_width - current_width) * probe > 0)); then + direction=1 + else + direction=-1 + fi + + current_width=$next_width + break + fi + done + + [[ -n $direction ]] || exit 1 + + for _ in {1..6}; do + delta=$((width - current_width)) + ((delta == 0)) && break + + probe_delta=$((delta * direction)) + resize_width_by "$window" "$probe_delta" + next_width=$(window_width "$address") + + ((next_width == current_width)) && break + current_width=$next_width + done +} + +action=${1:-} +[[ $action == "save" || $action == "restore" ]] || usage + +active=$(active_window) +key=$(window_key "$active") +[[ -n $key ]] || exit 1 +workspace=$(workspace_key "$active") +[[ -n $workspace ]] || exit 1 + +state_file=$(state_file_for "$key" "$workspace") + +case "$action" in +save) save_width "$active" "$key" "$workspace" "$state_file" ;; +restore) restore_width "$active" "$key" "$workspace" "$state_file" ;; +esac diff --git a/bin/omarchy-hyprland-workspace-layout-toggle b/bin/omarchy-hyprland-workspace-layout-toggle index 4636e38e..28f76337 100755 --- a/bin/omarchy-hyprland-workspace-layout-toggle +++ b/bin/omarchy-hyprland-workspace-layout-toggle @@ -10,5 +10,6 @@ case "$CURRENT_LAYOUT" in *) NEW_LAYOUT=dwindle ;; esac -hyprctl keyword workspace $ACTIVE_WORKSPACE, layout:$NEW_LAYOUT -notify-send -u low "󱂬 Workspace layout set to $NEW_LAYOUT" +hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \ + hyprctl keyword workspace $ACTIVE_WORKSPACE, layout:$NEW_LAYOUT +omarchy-notification-send -g 󱂬 "Workspace layout set to $NEW_LAYOUT" diff --git a/bin/omarchy-install-and-launch b/bin/omarchy-install-and-launch new file mode 100755 index 00000000..6a161102 --- /dev/null +++ b/bin/omarchy-install-and-launch @@ -0,0 +1,17 @@ +#!/bin/bash + +# omarchy:summary=Install a packaged app and gtk-launch it once it finishes +# omarchy:args= +# omarchy:examples=omarchy install and launch Cursor cursor-bin cursor + +name="${1-}" +packages="${2-}" +desktop_id="${3-}" + +if [[ -z $name || -z $packages || -z $desktop_id ]]; then + echo "Usage: omarchy-install-and-launch " >&2 + exit 1 +fi + +exec omarchy-launch-floating-terminal-with-presentation \ + "echo 'Installing ${name}...'; omarchy-pkg-add ${packages} && setsid gtk-launch ${desktop_id}" diff --git a/bin/omarchy-install-app b/bin/omarchy-install-app new file mode 100755 index 00000000..bae90897 --- /dev/null +++ b/bin/omarchy-install-app @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Install a packaged app, surfacing the install in a floating terminal +# omarchy:args= +# omarchy:examples=omarchy install app 'LM Studio' lmstudio-bin + +name="${1-}" +packages="${2-}" + +if [[ -z $name || -z $packages ]]; then + echo "Usage: omarchy-install-app " >&2 + exit 1 +fi + +exec omarchy-launch-floating-terminal-with-presentation "echo 'Installing ${name}...'; omarchy-pkg-add ${packages}" diff --git a/bin/omarchy-install-browser b/bin/omarchy-install-browser index ed067d8d..18a97c6e 100755 --- a/bin/omarchy-install-browser +++ b/bin/omarchy-install-browser @@ -16,7 +16,8 @@ announce_browser_installed() { copy_chromium_flags() { mkdir -p ~/.config - cp -f "${OMARCHY_PATH:-$HOME/.local/share/omarchy}/config/chromium-flags.conf" "$1" + cp -f "$OMARCHY_PATH/config/chromium-flags.conf" "$1" + omarchy-install-chromium-ytdlp } setup_firefox_preferences() { @@ -66,7 +67,8 @@ brave-origin) setup_policy_directory /etc/brave/policies/managed mkdir -p ~/.config # FIXME: Use normal chromium flags when Brave Origin wrapper has been fixed - echo "--load-extension=~/.local/share/omarchy/default/chromium/extensions/copy-url" > ~/.config/brave-origin-beta-flags.conf + echo "--load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp" > ~/.config/brave-origin-beta-flags.conf + omarchy-install-chromium-ytdlp omarchy-theme-set-browser announce_browser_installed "Brave Origin" ;; diff --git a/bin/omarchy-install-chromium-ytdlp b/bin/omarchy-install-chromium-ytdlp new file mode 100755 index 00000000..c700578c --- /dev/null +++ b/bin/omarchy-install-chromium-ytdlp @@ -0,0 +1,29 @@ +#!/bin/bash + +# omarchy:summary=Install the native messaging host for the yt-dlp Chromium extension + +set -euo pipefail + +HOST_NAME="com.omarchy.ytdlp" +HOST_PATH="$OMARCHY_PATH/bin/omarchy-chromium-ytdlp-host" +TEMPLATE="$OMARCHY_PATH/default/chromium/native-messaging-hosts/$HOST_NAME.json" + +# Chromium-based browser profile roots that use the NativeMessagingHosts layout. +browser_dirs=( + "$HOME/.config/chromium" + "$HOME/.config/google-chrome" + "$HOME/.config/google-chrome-beta" + "$HOME/.config/google-chrome-unstable" + "$HOME/.config/BraveSoftware/Brave-Browser" + "$HOME/.config/BraveSoftware/Brave-Browser-Beta" + "$HOME/.config/BraveSoftware/Brave-Browser-Nightly" + "$HOME/.config/microsoft-edge" + "$HOME/.config/microsoft-edge-dev" +) + +manifest=$(sed "s|__HOST_PATH__|$HOST_PATH|g" "$TEMPLATE") + +for dir in "${browser_dirs[@]}"; do + mkdir -p "$dir/NativeMessagingHosts" + printf '%s\n' "$manifest" >"$dir/NativeMessagingHosts/$HOST_NAME.json" +done diff --git a/bin/omarchy-install-helix b/bin/omarchy-install-editor-helix similarity index 100% rename from bin/omarchy-install-helix rename to bin/omarchy-install-editor-helix diff --git a/bin/omarchy-install-vscode b/bin/omarchy-install-editor-vscode similarity index 100% rename from bin/omarchy-install-vscode rename to bin/omarchy-install-editor-vscode diff --git a/bin/omarchy-install-zed b/bin/omarchy-install-editor-zed similarity index 100% rename from bin/omarchy-install-zed rename to bin/omarchy-install-editor-zed diff --git a/bin/omarchy-install-font b/bin/omarchy-install-font new file mode 100755 index 00000000..6f68892e --- /dev/null +++ b/bin/omarchy-install-font @@ -0,0 +1,17 @@ +#!/bin/bash + +# omarchy:summary=Install a Nerd Font package and switch the system to it +# omarchy:args= +# omarchy:examples=omarchy install font 'Cascadia Mono' ttf-cascadia-mono-nerd 'CaskaydiaMono Nerd Font' + +name="${1-}" +package="${2-}" +family="${3-}" + +if [[ -z $name || -z $package || -z $family ]]; then + echo "Usage: omarchy-install-font " >&2 + exit 1 +fi + +exec omarchy-launch-floating-terminal-with-presentation \ + "echo 'Installing ${name}...'; omarchy-pkg-add ${package} && sleep 2 && omarchy-font-set '${family}'" diff --git a/bin/omarchy-install-gaming-battlenet b/bin/omarchy-install-gaming-battlenet new file mode 100755 index 00000000..5df49955 --- /dev/null +++ b/bin/omarchy-install-gaming-battlenet @@ -0,0 +1,88 @@ +#!/bin/bash + +# omarchy:summary=Install Battle.net standalone via umu-launcher + GE-Proton (no Steam, no Lutris, no Heroic). +# omarchy:requires-sudo=true + +set -e + +PREFIX="$HOME/Games/battlenet" +LAUNCHER="$PREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe" +INSTALLER_URL="https://downloader.battle.net/download/getInstallerForGame?os=win&gameProgram=BATTLENET_APP&version=Live" + +echo "Installing Battle.net..." + +omarchy-pkg-add umu-launcher +omarchy-install-gaming-gpu-lib32 + +# Detect a half-finished prefix from a closed/crashed previous run and offer +# to wipe it before trying again. Battle.net's installer isn't idempotent. +if [[ -d $PREFIX && ! -f $LAUNCHER ]]; then + echo + echo "Found a partial Battle.net install at $PREFIX (no Launcher.exe)." + echo "Battle.net's installer can't resume from this state." + if gum confirm "Wipe the partial prefix and start fresh?"; then + pkill -f "$PREFIX" 2>/dev/null || true + sleep 1 + rm -rf "$PREFIX" + else + echo "Aborting. Re-run when ready to wipe." + exit 1 + fi +fi + +mkdir -p "$PREFIX" + +export WINEPREFIX="$PREFIX" +export PROTONPATH=GE-Proton +export GAMEID=umu-battlenet +export PROTON_VERB=run + +if [[ -f $LAUNCHER ]]; then + echo "Battle.net is already installed at $PREFIX." + launched_installer=0 +else + cache_dir="$HOME/.cache/omarchy" + mkdir -p "$cache_dir" + installer="$cache_dir/Battle.net-Setup.exe" + + echo + echo "Downloading Battle.net installer..." + curl --fail --location --retry 3 "$INSTALLER_URL" --output "$installer" + + cat <<'EOF' + +Launching the Battle.net setup wizard. Click through it normally — the +default install path is fine. When it finishes, Battle.net will be in your +app launcher. + +EOF + + log="/tmp/omarchy-battlenet-installer.log" + setsid -f sh -c "umu-run '$installer' >'$log' 2>&1" /dev/null 2>&1 + echo "Installer log: $log" + launched_installer=1 +fi + +mkdir -p "$HOME/.local/share/applications" +install -m 644 "$OMARCHY_PATH/applications/battlenet.desktop" \ + "$HOME/.local/share/applications/battlenet.desktop" +update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true + +if (( launched_installer )); then + cat < 0 )) && omarchy-pkg-add "${PACKAGES[@]}" diff --git a/bin/omarchy-install-gaming-moonlight b/bin/omarchy-install-gaming-moonlight deleted file mode 100755 index 1fe77e68..00000000 --- a/bin/omarchy-install-gaming-moonlight +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Install Moonlight (NVIDIA GameStream / Sunshine client) for streaming games to this PC. -# omarchy:requires-sudo=true - -set -e - -echo "Installing Moonlight..." -omarchy-pkg-add moonlight-qt - -setsid gtk-launch com.moonlight_stream.Moonlight.desktop >/dev/null 2>&1 & diff --git a/bin/omarchy-install-gaming-xbox-cloud b/bin/omarchy-install-gaming-xbox-cloud index 8622949d..2367328f 100755 --- a/bin/omarchy-install-gaming-xbox-cloud +++ b/bin/omarchy-install-gaming-xbox-cloud @@ -1,6 +1,8 @@ #!/bin/bash # omarchy:summary=Install Xbox Cloud Gaming as a web app and launch it. +# omarchy:group=install +# omarchy:name=gaming xbox-cloud set -e diff --git a/bin/omarchy-install-gaming-xbox-controllers b/bin/omarchy-install-gaming-xbox-controllers index 539d0297..f4134f76 100755 --- a/bin/omarchy-install-gaming-xbox-controllers +++ b/bin/omarchy-install-gaming-xbox-controllers @@ -1,6 +1,8 @@ #!/bin/bash # omarchy:summary=Install support for using Xbox controllers with Steam/RetroArch/etc. +# omarchy:group=install +# omarchy:name=gaming xbox-controllers # omarchy:requires-sudo=true set -e diff --git a/bin/omarchy-install-service-1password b/bin/omarchy-install-service-1password new file mode 100755 index 00000000..f59cab92 --- /dev/null +++ b/bin/omarchy-install-service-1password @@ -0,0 +1,34 @@ +#!/bin/bash + +# omarchy:summary=Install 1Password and its Chromium extension. +# omarchy:requires-sudo=true + +set -e + +EXTENSION_ID="aeblfdkhhhdcdjpifhhbdiojplfjncoa" +EXTENSION_DIR="/usr/share/chromium/extensions" +EXTENSION_FILE="$EXTENSION_DIR/$EXTENSION_ID.json" +WEBSTORE_UPDATE_URL="https://clients2.google.com/service/update2/crx" + +install_chromium_extension() { + if omarchy-cmd-missing chromium; then + echo "Chromium is not installed; skipping 1Password Chromium extension." + return + fi + + sudo mkdir -p "$EXTENSION_DIR" + printf '{ "external_update_url": "%s" }\n' "$WEBSTORE_UPDATE_URL" | sudo tee "$EXTENSION_FILE" >/dev/null + sudo chmod 644 "$EXTENSION_FILE" +} + +echo "Installing 1Password..." +omarchy-pkg-add 1password 1password-cli + +echo "Installing 1Password extension for Chromium..." +install_chromium_extension + +echo "Opening 1Password..." +uwsm-app -- 1password >/dev/null 2>&1 & + +echo "" +echo "1Password has been installed. Restart Chromium to load the browser extension." diff --git a/bin/omarchy-install-dropbox b/bin/omarchy-install-service-dropbox similarity index 82% rename from bin/omarchy-install-dropbox rename to bin/omarchy-install-service-dropbox index 4add3460..9de23983 100755 --- a/bin/omarchy-install-dropbox +++ b/bin/omarchy-install-service-dropbox @@ -5,6 +5,9 @@ echo "Installing all dependencies..." omarchy-pkg-add dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox +echo "Adding Dropbox to the bar..." +omarchy-config-shell-bar add omarchy.dropbox + echo "Starting Dropbox..." uwsm-app -- dropbox-cli start &>/dev/null & echo "See Dropbox icon behind  hover tray in top right and right-click for setup." diff --git a/bin/omarchy-install-nordvpn b/bin/omarchy-install-service-nordvpn similarity index 100% rename from bin/omarchy-install-nordvpn rename to bin/omarchy-install-service-nordvpn diff --git a/bin/omarchy-install-once b/bin/omarchy-install-service-once similarity index 100% rename from bin/omarchy-install-once rename to bin/omarchy-install-service-once diff --git a/bin/omarchy-install-service-signal b/bin/omarchy-install-service-signal new file mode 100755 index 00000000..f7679686 --- /dev/null +++ b/bin/omarchy-install-service-signal @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Install Signal and launch it. +# omarchy:requires-sudo=true + +set -e + +echo "Installing Signal..." +omarchy-pkg-add signal-desktop + +echo "Opening Signal..." +setsid uwsm-app -- /usr/bin/signal-desktop >/dev/null 2>&1 & + +echo "" +echo "Signal has been installed." diff --git a/bin/omarchy-install-service-spotify b/bin/omarchy-install-service-spotify new file mode 100755 index 00000000..beae9939 --- /dev/null +++ b/bin/omarchy-install-service-spotify @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Install Spotify. +# omarchy:requires-sudo=true + +set -e + +echo "Installing Spotify..." +omarchy-pkg-add spotify + +echo "Opening Spotify..." +setsid uwsm-app -- /usr/bin/spotify >/dev/null 2>&1 & + +echo "" +echo "Spotify has been installed." diff --git a/bin/omarchy-install-service-sunshine b/bin/omarchy-install-service-sunshine new file mode 100755 index 00000000..c215584b --- /dev/null +++ b/bin/omarchy-install-service-sunshine @@ -0,0 +1,87 @@ +#!/bin/bash + +# omarchy:summary=Install Sunshine and open Moonlight streaming ports for LAN and Tailscale. +# omarchy:requires-sudo=true + +set -e + +TCP_PORTS=(47984 47989 48010) +UDP_PORTS=(5353 47998 47999 48000 48002 48010) +PRIVATE_CIDRS=(10.0.0.0/8 172.16.0.0/12 192.168.0.0/16) +UFW_COMMENT="omarchy-sunshine" +SUNSHINE_ADMIN_APP="Sunshine Admin" +SUNSHINE_ADMIN_URL="https://localhost:47990" +SUNSHINE_ADMIN_EXEC="omarchy-launch-webapp $SUNSHINE_ADMIN_URL --ignore-certificate-errors" +SUNSHINE_ICON_SOURCE="/usr/share/sunshine/web/images/logo-sunshine-45.png" +HYPR_AUTOSTART_FILE="$HOME/.config/hypr/autostart.lua" +HYPR_AUTOSTART_ENTRY='o.launch_on_start("sunshine")' + +open_ufw_port_for_private_lans() { + local proto="$1" + local port="$2" + local cidr + + for cidr in "${PRIVATE_CIDRS[@]}"; do + sudo ufw allow in proto "$proto" from "$cidr" to any port "$port" comment "$UFW_COMMENT" >/dev/null + done +} + +open_ufw_port_for_tailscale() { + local proto="$1" + local port="$2" + + if ip link show tailscale0 >/dev/null 2>&1; then + sudo ufw allow in on tailscale0 to any port "$port" proto "$proto" comment "$UFW_COMMENT" >/dev/null + fi +} + +open_ufw_ports() { + local port + + if omarchy-cmd-missing ufw; then + echo "UFW is not installed; skipping Sunshine firewall rules." + return + fi + + for port in "${TCP_PORTS[@]}"; do + open_ufw_port_for_private_lans tcp "$port" + open_ufw_port_for_tailscale tcp "$port" + done + + for port in "${UDP_PORTS[@]}"; do + open_ufw_port_for_private_lans udp "$port" + open_ufw_port_for_tailscale udp "$port" + done + + sudo ufw reload +} + +install_admin_webapp() { + omarchy-webapp-install "$SUNSHINE_ADMIN_APP" "$SUNSHINE_ADMIN_URL" "$SUNSHINE_ICON_SOURCE" "$SUNSHINE_ADMIN_EXEC" +} + +enable_hyprland_autostart() { + mkdir -p "$(dirname "$HYPR_AUTOSTART_FILE")" + touch "$HYPR_AUTOSTART_FILE" + + if ! grep -Fxq "$HYPR_AUTOSTART_ENTRY" "$HYPR_AUTOSTART_FILE"; then + printf '\n%s\n' "$HYPR_AUTOSTART_ENTRY" >>"$HYPR_AUTOSTART_FILE" + fi +} + +echo "Installing Sunshine..." +omarchy-pkg-add sunshine +systemctl --user enable --now sunshine + +echo "Opening Sunshine firewall ports..." +open_ufw_ports + +echo "Installing Sunshine admin web app..." +install_admin_webapp +$SUNSHINE_ADMIN_EXEC >/dev/null 2>&1 & + +echo "Enabling Sunshine autostart..." +enable_hyprland_autostart + +echo "" +echo "Sunshine has been installed and its Moonlight streaming ports are open for private LANs and Tailscale." diff --git a/bin/omarchy-install-tailscale b/bin/omarchy-install-service-tailscale similarity index 72% rename from bin/omarchy-install-tailscale rename to bin/omarchy-install-service-tailscale index 1c37ba41..c93295c0 100755 --- a/bin/omarchy-install-tailscale +++ b/bin/omarchy-install-service-tailscale @@ -10,4 +10,10 @@ echo -e "\nStarting Tailscale..." sudo systemctl enable --now tailscaled.service sudo tailscale up --accept-routes +echo -e "\nAllowing $USER to manage Tailscale..." +sudo tailscale set --operator="$USER" + +echo -e "\nAdding Tailscale to the bar..." +omarchy-config-shell-bar add omarchy.tailscale + omarchy-webapp-install "Tailscale" "https://login.tailscale.com/admin/machines" https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/tailscale-light.png diff --git a/bin/omarchy-install-terminal b/bin/omarchy-install-terminal index 2fb040dd..a32a7a33 100755 --- a/bin/omarchy-install-terminal +++ b/bin/omarchy-install-terminal @@ -30,10 +30,10 @@ if omarchy-pkg-add $package; then # Copy custom desktop entries with X-TerminalArg* keys if [[ $package == "alacritty" ]]; then mkdir -p ~/.local/share/applications - cp "$OMARCHY_PATH/applications/$desktop_id" ~/.local/share/applications/ + cp "$OMARCHY_PATH/default/alacritty/$desktop_id" ~/.local/share/applications/ elif [[ $package == "foot" ]]; then mkdir -p ~/.local/share/applications - cp "$OMARCHY_PATH/default/foot/$desktop_id" ~/.local/share/applications/ + cp "$OMARCHY_PATH/applications/$desktop_id" ~/.local/share/applications/ fi # Copy default config for optional terminals when missing diff --git a/bin/omarchy-launch-ai b/bin/omarchy-launch-ai new file mode 100755 index 00000000..368c2ece --- /dev/null +++ b/bin/omarchy-launch-ai @@ -0,0 +1,105 @@ +#!/bin/bash + +# omarchy:summary=Launch the default AI coding harness in a project path +# omarchy:args=[--path ] [--prompt ] [--harness ] [path] +# omarchy:examples=omarchy launch ai --path ~/.config/omarchy/plugins/local.clock | omarchy launch ai --harness claude --path . --prompt "Review this plugin" + +set -euo pipefail + +path="." +prompt="" +harness="" + +usage() { + cat <<'USAGE' +Usage: omarchy-launch-ai [--path ] [--prompt ] [--harness ] [path] + +Launches an interactive AI coding harness in the given path. If no harness is +provided, uses the value from `omarchy default ai`. +USAGE +} + +fail() { + echo "omarchy-launch-ai: $*" >&2 + exit 1 +} + +while (( $# > 0 )); do + case "$1" in + --path) + path="${2:-}" + [[ -n $path ]] || fail "--path requires a value" + shift 2 + ;; + --prompt) + prompt="${2:-}" + shift 2 + ;; + --harness) + harness="${2:-}" + [[ -n $harness ]] || fail "--harness requires a value" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + --) + shift + break + ;; + --*) + fail "unknown option: $1" + ;; + *) + path="$1" + shift + ;; + esac +done + +[[ -d $path ]] || fail "path is not a directory: $path" +path=$(cd "$path" && pwd) + +if [[ -z $harness ]]; then + harness=$(omarchy-default-ai) +fi + +case "$harness" in +pi | claude | codex | opencode) + ;; +*) + fail "unknown AI harness: $harness" + ;; +esac + +launch_in_place() { + cd "$path" + case "$harness" in + pi) + omarchy-cmd-present pi || fail "pi is not installed" + if [[ -n $prompt ]]; then exec pi "$prompt"; else exec pi; fi + ;; + claude) + omarchy-cmd-present claude || fail "claude is not installed" + if [[ -n $prompt ]]; then exec claude "$prompt"; else exec claude; fi + ;; + codex) + omarchy-cmd-present codex || fail "codex is not installed" + if [[ -n $prompt ]]; then exec codex "$prompt"; else exec codex; fi + ;; + opencode) + omarchy-cmd-present opencode || fail "opencode is not installed" + if [[ -n $prompt ]]; then exec opencode --prompt "$prompt" .; else exec opencode .; fi + ;; + esac +} + +if [[ -t 0 && -t 1 ]]; then + launch_in_place +fi + +quoted_path=$(printf '%q' "$path") +quoted_prompt=$(printf '%q' "$prompt") +quoted_harness=$(printf '%q' "$harness") +exec omarchy-launch-tui bash -lc "cd $quoted_path && exec omarchy-launch-ai --harness $quoted_harness --prompt $quoted_prompt --path ." diff --git a/bin/omarchy-launch-audio b/bin/omarchy-launch-audio deleted file mode 100755 index c3d2592c..00000000 --- a/bin/omarchy-launch-audio +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Launch the Omarchy audio controls TUI (provided by wiremix). - -omarchy-launch-or-focus-tui wiremix diff --git a/bin/omarchy-launch-bar-settings b/bin/omarchy-launch-bar-settings new file mode 100755 index 00000000..23a557c9 --- /dev/null +++ b/bin/omarchy-launch-bar-settings @@ -0,0 +1,5 @@ +#!/bin/bash + +# omarchy:summary=Open the Omarchy bar config panel + +exec omarchy-shell shell openBarConfig diff --git a/bin/omarchy-launch-battlenet b/bin/omarchy-launch-battlenet new file mode 100755 index 00000000..abaa85e5 --- /dev/null +++ b/bin/omarchy-launch-battlenet @@ -0,0 +1,48 @@ +#!/bin/bash + +# omarchy:summary=Launch the installed Battle.net client via umu-launcher + GE-Proton. +# omarchy:args=[--with-mangohud] +# omarchy:examples=omarchy launch battlenet | omarchy launch battlenet --with-mangohud + +set -e + +PREFIX="$HOME/Games/battlenet" +LAUNCHER="$PREFIX/drive_c/Program Files (x86)/Battle.net/Battle.net Launcher.exe" + +with_mangohud=0 +for arg in "$@"; do + case "$arg" in + --with-mangohud) with_mangohud=1 ;; + -h|--help) + cat <<'EOF' +Usage: omarchy-launch-battlenet [--with-mangohud] + +Options: + --with-mangohud Enable the MangoHud FPS overlay for games launched from + Battle.net. Toggle perf logging in-game with Shift_L+F2; + CSV logs land in ~/mangohud/. +EOF + exit 0 + ;; + *) + echo "Unknown argument: $arg" >&2 + echo "Try: omarchy-launch-battlenet --help" >&2 + exit 1 + ;; + esac +done + +if [[ ! -f $LAUNCHER ]]; then + echo "Battle.net is not installed. Run omarchy-install-gaming-battlenet first." >&2 + exit 1 +fi + +env_args=( + WINEPREFIX="$PREFIX" + PROTONPATH=GE-Proton + GAMEID=umu-battlenet + PROTON_VERB=run +) +(( with_mangohud )) && env_args+=(MANGOHUD=1) + +env "${env_args[@]}" umu-run "$LAUNCHER" diff --git a/bin/omarchy-launch-bluetooth b/bin/omarchy-launch-bluetooth deleted file mode 100755 index 8c450261..00000000 --- a/bin/omarchy-launch-bluetooth +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Launch the Omarchy bluetooth controls TUI (provided by bluetui). - -rfkill unblock bluetooth -exec omarchy-launch-or-focus-tui bluetui diff --git a/bin/omarchy-launch-browser b/bin/omarchy-launch-browser index fefc935e..f1076e79 100755 --- a/bin/omarchy-launch-browser +++ b/bin/omarchy-launch-browser @@ -6,7 +6,7 @@ default_browser=$(xdg-settings get default-web-browser) browser_exec=$(sed -n 's/^Exec=\([^ ]*\).*/\1/p' {~/.local,~/.nix-profile,/usr}/share/applications/$default_browser 2>/dev/null | head -1) -if $browser_exec --help | grep -q MOZ_LOG; then +if $browser_exec --help 2>/dev/null | grep -q MOZ_LOG; then private_flag="--private-window" elif [[ $browser_exec =~ edge ]]; then private_flag="--inprivate" @@ -14,4 +14,6 @@ else private_flag="--incognito" fi -exec setsid uwsm-app -- "$browser_exec" "${@/--private/$private_flag}" +systemd-run --user --quiet --collect --unit="omarchy-browser-$(date +%s%N)" \ + --property=StandardOutput=null --property=StandardError=null \ + uwsm-app -- "$browser_exec" "${@/--private/$private_flag}" diff --git a/bin/omarchy-launch-config-editor b/bin/omarchy-launch-config-editor new file mode 100755 index 00000000..ffe100e9 --- /dev/null +++ b/bin/omarchy-launch-config-editor @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Open a config file in the user's editor and surface a toast +# omarchy:args= +# omarchy:examples=omarchy launch config-editor ~/.config/hypr/hyprland.lua + +path="${1-}" + +if [[ -z $path ]]; then + echo "Usage: omarchy-launch-config-editor " >&2 + exit 1 +fi + +omarchy-notification-send -u low "Editing config file" "$path" +exec omarchy-launch-editor "$path" diff --git a/bin/omarchy-launch-editor b/bin/omarchy-launch-editor index 9c3ec369..8a69dabd 100755 --- a/bin/omarchy-launch-editor +++ b/bin/omarchy-launch-editor @@ -1,15 +1,34 @@ #!/bin/bash -# omarchy:summary=Launch the default editor as determined by $EDITOR (set via ~/.config/uwsm/default) (or nvim if missing). -# omarchy:args= +# omarchy:summary=Launch the default editor selected via Omarchy defaults. +# omarchy:args=[--inline] -omarchy-cmd-present "$EDITOR" || EDITOR=nvim +default_editor="$HOME/.local/state/omarchy/defaults/editor" -case "$EDITOR" in +if [[ ${1:-} == "--inline" ]]; then + inline=true + shift +else + inline=false +fi + +if [[ -f $default_editor ]]; then + read -r editor <"$default_editor" +else + editor="nvim" +fi + +omarchy-cmd-present "$editor" || editor="nvim" + +case "${editor##*/}" in nvim | vim | nano | micro | hx | helix | fresh) - exec omarchy-launch-tui "$EDITOR" "$@" + if [[ $inline == "true" ]]; then + exec "$editor" "$@" + else + exec omarchy-launch-tui "$editor" "$@" + fi ;; *) - exec setsid uwsm-app -- "$EDITOR" "$@" + exec setsid uwsm-app -- "$editor" "$@" ;; esac diff --git a/bin/omarchy-launch-nautilus b/bin/omarchy-launch-nautilus new file mode 100755 index 00000000..0b10b7b2 --- /dev/null +++ b/bin/omarchy-launch-nautilus @@ -0,0 +1,5 @@ +#!/bin/bash + +# omarchy:summary=Launch Files + +exec setsid uwsm-app -- nautilus --new-window diff --git a/bin/omarchy-launch-nautilus-cwd b/bin/omarchy-launch-nautilus-cwd new file mode 100755 index 00000000..ae9a0d88 --- /dev/null +++ b/bin/omarchy-launch-nautilus-cwd @@ -0,0 +1,5 @@ +#!/bin/bash + +# omarchy:summary=Launch Files in the active terminal's current directory + +exec setsid uwsm-app -- nautilus --new-window "$(omarchy-cmd-terminal-cwd)" diff --git a/bin/omarchy-launch-or-focus b/bin/omarchy-launch-or-focus index 44164bb5..11f65c5f 100755 --- a/bin/omarchy-launch-or-focus +++ b/bin/omarchy-launch-or-focus @@ -13,7 +13,7 @@ LAUNCH_COMMAND="${2:-"uwsm-app -- $WINDOW_PATTERN"}" WINDOW_ADDRESS=$(hyprctl clients -j | jq -r --arg p "$WINDOW_PATTERN" '.[]|select((.class|test("\\b" + $p + "\\b";"i")) or (.title|test("\\b" + $p + "\\b";"i")))|.address' | head -n1) if [[ -n $WINDOW_ADDRESS ]]; then - hyprctl dispatch focuswindow "address:$WINDOW_ADDRESS" + hyprctl dispatch "hl.dsp.focus({ window = \"address:$WINDOW_ADDRESS\" })" >/dev/null 2>&1 || hyprctl dispatch focuswindow "address:$WINDOW_ADDRESS" else eval exec setsid $LAUNCH_COMMAND fi diff --git a/bin/omarchy-launch-screensaver b/bin/omarchy-launch-screensaver index 0cd64a70..846d7801 100755 --- a/bin/omarchy-launch-screensaver +++ b/bin/omarchy-launch-screensaver @@ -6,54 +6,47 @@ if ! command -v tte &>/dev/null; then exit 1 fi -# Exit early if screensave is already running -pgrep -f org.omarchy.screensaver && exit 0 +# Exit early if screensaver is already running +pgrep -f '[o]rg.omarchy.screensaver' && exit 0 # Allow screensaver to be turned off but also force started if omarchy-toggle-enabled screensaver-off && [[ $1 != "force" ]]; then exit 1 fi -# Silently quit Walker on overlay -walker -q - focused=$(omarchy-hyprland-monitor-focused) terminal=$(xdg-terminal-exec --print-id) +hypr_focus_monitor() { + hyprctl dispatch "hl.dsp.focus({ monitor = \"$1\" })" >/dev/null 2>&1 || hyprctl dispatch focusmonitor "$1" >/dev/null +} + +hypr_exec() { + local command="$1" + + hyprctl dispatch "hl.dsp.exec_cmd([[$command]])" >/dev/null 2>&1 || hyprctl dispatch exec -- bash -lc "$command" >/dev/null +} + for m in $(hyprctl monitors -j | jq -r '.[] | .name'); do - hyprctl dispatch focusmonitor $m + hypr_focus_monitor "$m" case $terminal in *Alacritty*) - hyprctl dispatch exec -- \ - alacritty --class=org.omarchy.screensaver \ - --config-file ~/.local/share/omarchy/default/alacritty/screensaver.toml \ - -e omarchy-screensaver + hypr_exec "alacritty --class=org.omarchy.screensaver --config-file $OMARCHY_PATH/default/alacritty/screensaver.toml -e omarchy-screensaver" ;; *ghostty*) - hyprctl dispatch exec -- \ - ghostty --class=org.omarchy.screensaver \ - --config-file=~/.local/share/omarchy/default/ghostty/screensaver \ - --font-size=18 \ - -e omarchy-screensaver + hypr_exec "ghostty --class=org.omarchy.screensaver --config-file=$OMARCHY_PATH/default/ghostty/screensaver --font-size=18 -e omarchy-screensaver" ;; *foot*) - hyprctl dispatch exec -- \ - foot --app-id=org.omarchy.screensaver \ - --config="$OMARCHY_PATH/default/foot/screensaver.ini" \ - -e omarchy-screensaver + hypr_exec "foot --app-id=org.omarchy.screensaver --config=\"$OMARCHY_PATH/default/foot/screensaver.ini\" -e omarchy-screensaver" ;; *kitty*) - hyprctl dispatch exec -- \ - kitty --class=org.omarchy.screensaver \ - --override font_size=18 \ - --override window_padding_width=0 \ - -e omarchy-screensaver + hypr_exec "kitty --class=org.omarchy.screensaver --override font_size=18 --override window_padding_width=0 -e omarchy-screensaver" ;; *) - notify-send -u low "✋ Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty" + omarchy-notification-send -g ✋ "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty" ;; esac done -hyprctl dispatch focusmonitor $focused +hypr_focus_monitor "$focused" diff --git a/bin/omarchy-launch-signal b/bin/omarchy-launch-signal new file mode 100755 index 00000000..31d94cce --- /dev/null +++ b/bin/omarchy-launch-signal @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Launch Signal or start its installer when missing. + +set -e + +WINDOW_ADDRESS=$(hyprctl clients -j | jq -r '.[]|select((.class|test("\\bsignal\\b";"i")) or (.title|test("\\bsignal\\b";"i")))|.address' | head -n1) + +if [[ -n $WINDOW_ADDRESS ]]; then + hyprctl dispatch "hl.dsp.focus({ window = \"address:$WINDOW_ADDRESS\" })" >/dev/null 2>&1 || hyprctl dispatch focuswindow "address:$WINDOW_ADDRESS" +elif [[ -x /usr/bin/signal-desktop ]]; then + exec setsid uwsm-app -- /usr/bin/signal-desktop +else + exec omarchy-launch-floating-terminal-with-presentation omarchy-install-service-signal +fi diff --git a/bin/omarchy-launch-spotify b/bin/omarchy-launch-spotify new file mode 100755 index 00000000..d94202a3 --- /dev/null +++ b/bin/omarchy-launch-spotify @@ -0,0 +1,15 @@ +#!/bin/bash + +# omarchy:summary=Launch Spotify or start its installer when missing. + +set -e + +WINDOW_ADDRESS=$(hyprctl clients -j | jq -r '.[]|select((.class|test("\\bspotify\\b";"i")) or (.title|test("\\bspotify\\b";"i")))|.address' | head -n1) + +if [[ -n $WINDOW_ADDRESS ]]; then + hyprctl dispatch "hl.dsp.focus({ window = \"address:$WINDOW_ADDRESS\" })" >/dev/null 2>&1 || hyprctl dispatch focuswindow "address:$WINDOW_ADDRESS" +elif [[ -x /usr/bin/spotify ]]; then + exec setsid uwsm-app -- /usr/bin/spotify +else + exec omarchy-launch-floating-terminal-with-presentation omarchy-install-service-spotify +fi diff --git a/bin/omarchy-launch-terminal b/bin/omarchy-launch-terminal new file mode 100755 index 00000000..a07a1dde --- /dev/null +++ b/bin/omarchy-launch-terminal @@ -0,0 +1,6 @@ +#!/bin/bash + +# omarchy:summary=Launch a terminal in the active terminal's current directory +# omarchy:args=[command...] + +exec setsid uwsm-app -- xdg-terminal-exec --dir="$(omarchy-cmd-terminal-cwd)" "$@" diff --git a/bin/omarchy-launch-terminal-tmux b/bin/omarchy-launch-terminal-tmux new file mode 100755 index 00000000..2b70179c --- /dev/null +++ b/bin/omarchy-launch-terminal-tmux @@ -0,0 +1,5 @@ +#!/bin/bash + +# omarchy:summary=Launch or attach to the Work tmux session in a terminal + +exec omarchy-launch-terminal bash -c "tmux attach || tmux new -s Work" diff --git a/bin/omarchy-launch-walker b/bin/omarchy-launch-walker deleted file mode 100755 index d6af04e1..00000000 --- a/bin/omarchy-launch-walker +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Launch Walker and ensure its Elephant data provider is running - -if ! pgrep -x elephant > /dev/null; then - setsid uwsm-app -- elephant & -fi - -# Ensure walker service is running -if ! pgrep -f "walker --gapplication-service" > /dev/null; then - setsid uwsm-app -- env GSK_RENDERER=cairo walker --gapplication-service & -fi - -exec walker --width 644 --maxheight 300 --minheight 300 "$@" diff --git a/bin/omarchy-launch-wifi b/bin/omarchy-launch-wifi deleted file mode 100755 index e22b7083..00000000 --- a/bin/omarchy-launch-wifi +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Launch the Omarchy wifi controls (provided by the Impala TUI). - -rfkill unblock wifi -omarchy-launch-or-focus-tui impala diff --git a/bin/omarchy-menu b/bin/omarchy-menu index 9f098c92..59e4af8e 100755 --- a/bin/omarchy-menu +++ b/bin/omarchy-menu @@ -1,891 +1,52 @@ #!/bin/bash -# omarchy:summary=Launch the Omarchy Menu or takes a parameter to jump straight to a submenu. +# omarchy:summary=Control the Omarchy menu (toggle / summon / close / refresh) +# omarchy:args=[toggle|summon|close|refresh|ping] [route] +# omarchy:examples=omarchy menu | omarchy menu toggle system | omarchy menu summon style.theme | omarchy menu refresh -# Set to true when going directly to a submenu, so we can exit directly -BACK_TO_EXIT=false +# Thin wrapper around the standard plugin IPC surface. The menu is the +# first-party `omarchy.menu` plugin; routes are passed as JSON payload. -back_to() { - local parent_menu="$1" +set -euo pipefail - if [[ $BACK_TO_EXIT == "true" ]]; then - exit 0 - elif [[ -n $parent_menu ]]; then - "$parent_menu" - else - show_main_menu - fi +verb="${1-toggle}" +route="${2-root}" + +menu_payload() { + perl -MEncode=decode -MJSON::PP=encode_json -e 'print encode_json({ menu => decode("UTF-8", $ARGV[0]) })' "$1" } -toggle_existing_menu() { - if pgrep -f "walker.*--dmenu" >/dev/null; then - walker --close >/dev/null 2>&1 - exit 0 - fi -} - -menu() { - local prompt="$1" - local options="$2" - local extra="$3" - local preselect="$4" - - read -r -a args <<<"$extra" - - if [[ -n $preselect ]]; then - local index - index=$(echo -e "$options" | grep -nxF "$preselect" | cut -d: -f1) - if [[ -n $index ]]; then - args+=("-c" "$index") - fi - fi - - echo -e "$options" | omarchy-launch-walker --dmenu --width 295 --minheight 1 --maxheight 630 -p "$prompt…" "${args[@]}" 2>/dev/null -} - -terminal() { - xdg-terminal-exec --app-id=org.omarchy.terminal "$@" -} - -present_terminal() { - omarchy-launch-floating-terminal-with-presentation $1 -} - -open_in_editor() { - notify-send -u low "Editing config file" "$1" - omarchy-launch-editor "$1" -} - -install() { - present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2" -} - -install_and_launch() { - present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2 && setsid gtk-launch $3" -} - -install_font() { - present_terminal "echo 'Installing $1...'; omarchy-pkg-add $2 && sleep 2 && omarchy-font-set '$3'" -} - -install_terminal() { - present_terminal "omarchy-install-terminal $1" -} - -aur_install() { - present_terminal "echo 'Installing $1 from AUR...'; omarchy-pkg-aur-add $2" -} - -aur_install_and_launch() { - present_terminal "echo 'Installing $1 from AUR...'; omarchy-pkg-aur-add $2 && setsid gtk-launch $3" -} - -show_learn_menu() { - case $(menu "Learn" " Keybindings\n Omarchy\n Hyprland\n󰣇 Arch\n Neovim\n󱆃 Bash") in - *Keybindings*) omarchy-menu-keybindings ;; - *Omarchy*) omarchy-launch-webapp "https://learn.omacom.io/2/the-omarchy-manual" ;; - *Hyprland*) omarchy-launch-webapp "https://wiki.hypr.land/" ;; - *Arch*) omarchy-launch-webapp "https://wiki.archlinux.org/title/Main_page" ;; - *Bash*) omarchy-launch-webapp "https://devhints.io/bash" ;; - *Neovim*) omarchy-launch-webapp "https://www.lazyvim.org/keymaps" ;; - *) show_main_menu ;; - esac -} - -show_trigger_menu() { - case $(menu "Trigger" "󰔛 Reminder\n Capture\n󰧸 Transcode\n Share\n󰔎 Toggle\n Hardware") in - *Reminder*) show_reminder_menu ;; - *Capture*) show_capture_menu ;; - *Transcode*) omarchy-transcode || back_to show_trigger_menu ;; - *Share*) show_share_menu ;; - *Toggle*) show_toggle_menu ;; - *Hardware*) show_hardware_menu ;; - *) show_main_menu ;; - esac -} - -show_reminder_menu() { - case $(menu "Reminder" "󰔛 Set one\n󰔛 Show all\n󰔛 Clear all") in - *Set*) show_custom_reminder_input ;; - *"Show all"*) omarchy-reminder show ;; - *"Clear all"*) omarchy-reminder clear ;; - *) back_to show_trigger_menu ;; - esac -} - -show_custom_reminder_input() { - local minutes - minutes=$(omarchy-menu-input "Remind in minutes") - - if [[ $minutes =~ ^[0-9]+$ ]] && ((minutes > 0)); then - show_reminder_message_input "$minutes" - elif [[ -n $minutes ]]; then - omarchy-notification-send "󰔛" "Invalid reminder" "Enter the number of minutes" -u critical - show_custom_reminder_input - else - back_to show_reminder_menu - fi -} - -show_reminder_message_input() { - local minutes="$1" - local message - message=$(omarchy-menu-input "Reminder message") - - if [[ -n $message ]]; then - omarchy-reminder "$minutes" "$message" - else - omarchy-reminder "$minutes" - fi -} - -show_capture_menu() { - case $(menu "Capture" " Screenshot\n Screenrecord\n󰴑 Text Extraction\n󰃉 Color") in - *Screenshot*) omarchy-capture-screenshot ;; - *Screenrecord*) show_screenrecord_menu ;; - *Text*) omarchy-capture-text-extraction ;; - *Color*) pkill hyprpicker || hyprpicker -a ;; - *) back_to show_trigger_menu ;; - esac -} - -get_webcam_list() { - v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do - if [[ $line != $'\t'* && -n $line ]]; then - local name="$line" - IFS= read -r device || break - device=$(echo "$device" | tr -d '\t' | head -1) - [[ -n $device ]] && echo "$device $name" - fi - done -} - -show_webcam_select_menu() { - local devices=$(get_webcam_list) - local count=$(echo "$devices" | grep -c . 2>/dev/null || echo 0) - - if [[ -z $devices ]] || ((count == 0)); then - notify-send "No webcam devices found" -u critical -t 3000 - return 1 - fi - - if ((count == 1)); then - echo "$devices" | awk '{print $1}' - else - menu "Select Webcam" "$devices" | awk '{print $1}' - fi -} - -show_screenrecord_menu() { - omarchy-capture-screenrecording --stop-recording && exit 0 - - case $(menu "Screenrecord" " With no audio\n With desktop audio\n With desktop + microphone audio\n With desktop + microphone audio + webcam") in - *"With no audio") omarchy-capture-screenrecording ;; - *"With desktop audio") omarchy-capture-screenrecording --with-desktop-audio ;; - *"With desktop + microphone audio") omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio ;; - *"With desktop + microphone audio + webcam") - local device=$(show_webcam_select_menu) || { - back_to show_capture_menu - return - } - omarchy-capture-screenrecording --with-desktop-audio --with-microphone-audio --with-webcam --webcam-device="$device" +case "$verb" in + toggle) + exec omarchy-shell shell toggle omarchy.menu "$(menu_payload "$route")" ;; - *) back_to show_capture_menu ;; - esac -} - -show_share_menu() { - case $(menu "Share" " Clipboard\n File \n Folder") in - *Clipboard*) omarchy-menu-share clipboard ;; - *File*) terminal bash -c "omarchy-menu-share file" ;; - *Folder*) terminal bash -c "omarchy-menu-share folder" ;; - *) back_to show_trigger_menu ;; - esac -} - -show_toggle_menu() { - local options="󱄄 Screensaver\n󰔎 Nightlight\n󱫖 Idle Lock\n󰂛 Notifications\n󰍜 Top Bar\n󱂬 Workspace Layout\n Window Gaps\n 1-Window Ratio\n󰍹 Monitor Scaling\n Direct Boot\n󰟵 Passwordless Sudo" - - case $(menu "Toggle" "$options") in - *Screensaver*) omarchy-toggle-screensaver ;; - *Nightlight*) omarchy-toggle-nightlight ;; - *Idle*) omarchy-toggle-idle ;; - *Notifications*) omarchy-toggle-notification-silencing ;; - *Bar*) omarchy-toggle-waybar ;; - *Layout*) omarchy-hyprland-workspace-layout-toggle ;; - *Ratio*) omarchy-hyprland-window-single-square-aspect-toggle ;; - *Gaps*) omarchy-hyprland-window-gaps-toggle ;; - *Scaling*) omarchy-hyprland-monitor-scaling-cycle ;; - *"Direct Boot"*) present_terminal omarchy-config-direct-boot ;; - *"Passwordless Sudo"*) present_terminal omarchy-sudo-passwordless ;; - *) back_to show_trigger_menu ;; - esac -} - -show_hardware_menu() { - local options="󰛧 Laptop Display\n 󰍹 Mirror Display" - - if omarchy-hw-hybrid-gpu; then - options="$options\n Hybrid GPU" - fi - - if omarchy-hw-touchpad; then - options="$options\n󰟸 Touchpad" - fi - - if omarchy-hw-dell-xps-haptic-touchpad && omarchy-cmd-present dell-xps-touchpad-haptics; then - options="$options\n󰌌 Touchpad Haptics" - fi - - if omarchy-hw-touchscreen; then - options="$options\n󰆽 Touchscreen" - fi - - case $(menu "Toggle" "$options") in - *Laptop*) omarchy-hyprland-monitor-internal toggle ;; - *Mirror*) omarchy-hyprland-monitor-internal-mirror toggle ;; - *Haptics*) show_hardware_touchpad_haptics_menu ;; - *Touchpad*) omarchy-toggle-touchpad ;; - *Touchscreen*) omarchy-toggle-touchscreen ;; - *"Hybrid GPU"*) present_terminal omarchy-toggle-hybrid-gpu ;; - *) back_to show_trigger_menu ;; - esac -} - -show_hardware_touchpad_haptics_menu() { - local current=$(dell-xps-touchpad-haptics get) - local selected=$(menu "Touchpad Haptics" "low\nmid\nhigh" "" "$current") - - if [[ -n $selected ]]; then - dell-xps-touchpad-haptics set "$selected" - else - back_to show_hardware_menu - fi -} - -show_style_menu() { - case $(menu "Style" "󰸌 Theme\n󰟵 Unlock\n Font\n Background\n Hyprland\n󱄄 Screensaver\n About") in - *Theme*) show_theme_menu ;; - *Unlock*) omarchy-launch-walker -m menus:omarchyunlocks --width 800 --minheight 400 ;; - *Font*) show_font_menu ;; - *Background*) show_background_menu ;; - *Hyprland*) open_in_editor ~/.config/hypr/looknfeel.conf ;; - *Screensaver*) show_screensaver_menu ;; - *About*) show_about_menu ;; - *) show_main_menu ;; - esac -} - -show_about_menu() { - case $(menu "About" " Edit Text\n Set From Image\n Restore Default") in - *Text*) omarchy-branding-about text ;; - *Image*) omarchy-branding-about image ;; - *Default*) omarchy-branding-about reset ;; - *) show_style_menu ;; - esac -} - -show_screensaver_menu() { - case $(menu "Screensaver" " Edit Text\n Set From Image\n Restore Default") in - *Text*) omarchy-branding-screensaver text ;; - *Image*) omarchy-branding-screensaver image ;; - *Default*) omarchy-branding-screensaver reset ;; - *) show_style_menu ;; - esac -} - -show_theme_menu() { - omarchy-launch-walker -m menus:omarchythemes --width 800 --minheight 400 -} - -show_background_menu() { - omarchy-launch-walker -m menus:omarchyBackgroundSelector --width 800 --minheight 400 -} - -show_font_menu() { - theme=$(menu "Font" "$(omarchy-font-list)" "--width 350" "$(omarchy-font-current)") - if [[ $theme == "CNCLD" || -z $theme ]]; then - back_to show_style_menu - else - omarchy-font-set "$theme" - fi -} - -show_setup_menu() { - local options=" Audio\n Wifi\n󰂯 Bluetooth\n󱐋 Power Profile\n System Sleep\n󰍹 Monitors" - [[ -f ~/.config/hypr/bindings.conf ]] && options="$options\n Keybindings" - [[ -f ~/.config/hypr/input.conf ]] && options="$options\n Input" - options="$options\n Defaults\n󰱔 DNS\n Security\n Config" - - case $(menu "Setup" "$options") in - *Audio*) omarchy-launch-audio ;; - *Wifi*) omarchy-launch-wifi ;; - *Bluetooth*) omarchy-launch-bluetooth ;; - *Power*) show_setup_power_menu ;; - *System*) show_setup_system_menu ;; - *Monitors*) open_in_editor ~/.config/hypr/monitors.conf ;; - *Keybindings*) open_in_editor ~/.config/hypr/bindings.conf ;; - *Input*) open_in_editor ~/.config/hypr/input.conf ;; - *Defaults*) show_setup_default_menu ;; - *DNS*) present_terminal omarchy-setup-dns ;; - *Security*) show_setup_security_menu ;; - *Config*) show_setup_config_menu ;; - *) show_main_menu ;; - esac -} - -show_setup_power_menu() { - profile=$(menu "Power Profile" "$(omarchy-powerprofiles-list)" "" "$(powerprofilesctl get)") - - if [[ $profile == "CNCLD" || -z $profile ]]; then - back_to show_setup_menu - else - powerprofilesctl set "$profile" - fi -} - -show_setup_security_menu() { - case $(menu "Setup" "󰈷 Fingerprint\n Fido2") in - *Fingerprint*) present_terminal omarchy-setup-security-fingerprint ;; - *Fido2*) present_terminal omarchy-setup-security-fido2 ;; - *) show_setup_menu ;; - esac -} - -show_setup_default_menu() { - case $(menu "Default" " Browser\n Terminal\n Editor") in - *Browser*) show_setup_default_browser_menu ;; - *Terminal*) show_setup_default_terminal_menu ;; - *Editor*) show_setup_default_editor_menu ;; - *) show_setup_menu ;; - esac -} - -browser_desktop_exists() { - [[ -f ~/.local/share/applications/$1 || -f ~/.nix-profile/share/applications/$1 || -f /usr/share/applications/$1 ]] -} - -show_setup_default_browser_menu() { - local options="" - browser_desktop_exists chromium.desktop && options="$options Chromium" - browser_desktop_exists google-chrome.desktop && options="${options:+$options\n}󰊯 Chrome" - browser_desktop_exists brave-browser.desktop && options="${options:+$options\n}󰖟 Brave" - browser_desktop_exists brave-origin-beta.desktop && options="${options:+$options\n}󰖟 Brave Origin" - browser_desktop_exists microsoft-edge.desktop && options="${options:+$options\n}󰇩 Edge" - browser_desktop_exists firefox.desktop && options="${options:+$options\n}󰈹 Firefox" - browser_desktop_exists zen.desktop && options="${options:+$options\n}󰖟 Zen" - - local current="" - case "$(omarchy-default-browser)" in - chromium) current=" Chromium" ;; - chrome) current="󰊯 Chrome" ;; - brave) current="󰖟 Brave" ;; - brave-origin) current="󰖟 Brave Origin" ;; - edge) current="󰇩 Edge" ;; - firefox) current="󰈹 Firefox" ;; - zen) current="󰖟 Zen" ;; - esac - - case $(menu "Default Browser" "$options" "" "$current") in - *Chromium*) omarchy-default-browser chromium ;; - *Chrome*) omarchy-default-browser chrome ;; - *"Brave Origin"*) omarchy-default-browser brave-origin ;; - *Brave*) omarchy-default-browser brave ;; - *Edge*) omarchy-default-browser edge ;; - *Firefox*) omarchy-default-browser firefox ;; - *Zen*) omarchy-default-browser zen ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_default_terminal_menu() { - local options="" - omarchy-cmd-present alacritty && options="$options Alacritty" - omarchy-cmd-present foot && options="${options:+$options\n} Foot" - omarchy-cmd-present ghostty && options="${options:+$options\n} Ghostty" - omarchy-cmd-present kitty && options="${options:+$options\n} Kitty" - - local current="" - case "$(omarchy-default-terminal)" in - alacritty) current=" Alacritty" ;; - foot) current=" Foot" ;; - ghostty) current=" Ghostty" ;; - kitty) current=" Kitty" ;; - esac - - case $(menu "Default Terminal" "$options" "" "$current") in - *Alacritty*) omarchy-default-terminal alacritty ;; - *Foot*) omarchy-default-terminal foot ;; - *Ghostty*) omarchy-default-terminal ghostty ;; - *Kitty*) omarchy-default-terminal kitty ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_default_editor_menu() { - local options="" - omarchy-cmd-present nvim && options="$options Neovim" - omarchy-cmd-present code && options="${options:+$options\n} VSCode" - omarchy-cmd-present cursor && options="${options:+$options\n} Cursor" - omarchy-cmd-present zeditor && options="${options:+$options\n} Zed" - omarchy-cmd-present sublime_text && options="${options:+$options\n} Sublime Text" - omarchy-cmd-present helix && options="${options:+$options\n} Helix" - omarchy-cmd-present vim && options="${options:+$options\n} Vim" - omarchy-cmd-present emacs && options="${options:+$options\n} Emacs" - - local current="" - case "$(omarchy-default-editor)" in - nvim) current=" Neovim" ;; - code) current=" VSCode" ;; - cursor) current=" Cursor" ;; - zed | zeditor) current=" Zed" ;; - sublime_text) current=" Sublime Text" ;; - helix) current=" Helix" ;; - vim) current=" Vim" ;; - emacs) current=" Emacs" ;; - esac - - case $(menu "Default Editor" "$options" "" "$current") in - *Neovim*) omarchy-default-editor nvim ;; - *VSCode*) omarchy-default-editor code ;; - *Cursor*) omarchy-default-editor cursor ;; - *Zed*) omarchy-default-editor zed ;; - *Sublime*) omarchy-default-editor sublime_text ;; - *Helix*) omarchy-default-editor helix ;; - *Vim*) omarchy-default-editor vim ;; - *Emacs*) omarchy-default-editor emacs ;; - *) show_setup_default_menu ;; - esac -} - -show_setup_config_menu() { - case $(menu "Setup" " Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n Swayosd\n󰌧 Walker\n󰍜 Waybar\n󰞅 XCompose") in - *Hyprland*) open_in_editor ~/.config/hypr/hyprland.conf ;; - *Hypridle*) open_in_editor ~/.config/hypr/hypridle.conf && omarchy-restart-hypridle ;; - *Hyprlock*) open_in_editor ~/.config/hypr/hyprlock.conf ;; - *Hyprsunset*) open_in_editor ~/.config/hypr/hyprsunset.conf && omarchy-restart-hyprsunset ;; - *Swayosd*) open_in_editor ~/.config/swayosd/config.toml && omarchy-restart-swayosd ;; - *Walker*) open_in_editor ~/.config/walker/config.toml && omarchy-restart-walker ;; - *Waybar*) open_in_editor ~/.config/waybar/config.jsonc && omarchy-restart-waybar ;; - *XCompose*) open_in_editor ~/.XCompose && omarchy-restart-xcompose ;; - *) show_setup_menu ;; - esac -} - -show_setup_system_menu() { - local options="" - - if omarchy-toggle-enabled suspend-off; then - options="$options󰒲 Enable Suspend" - else - options="$options󰒲 Disable Suspend" - fi - - if omarchy-hibernation-available; then - options="$options\n󰤁 Disable Hibernate" - else - options="$options\n󰤁 Enable Hibernate" - fi - - case $(menu "System" "$options") in - *Suspend*) omarchy-toggle-suspend ;; - *"Enable Hibernate"*) present_terminal omarchy-hibernation-setup ;; - *"Disable Hibernate"*) present_terminal omarchy-hibernation-remove ;; - *) show_setup_menu ;; - esac -} - -show_install_menu() { - case $(menu "Install" "󰣇 Package\n󰣇 AUR\n Web App\n TUI\n Service\n Style\n󰵮 Development\n Editor\n Terminal\n Browser\n󱚤 AI\n Gaming\n󰍲 Windows") in - *Package*) terminal omarchy-pkg-install ;; - *AUR*) terminal omarchy-pkg-aur-install ;; - *Web*) present_terminal omarchy-webapp-install ;; - *TUI*) present_terminal omarchy-tui-install ;; - *Service*) show_install_service_menu ;; - *Style*) show_install_style_menu ;; - *Development*) show_install_development_menu ;; - *Editor*) show_install_editor_menu ;; - *Terminal*) show_install_terminal_menu ;; - *Browser*) show_install_browser_menu ;; - *Gaming*) show_install_gaming_menu ;; - *AI*) show_install_ai_menu ;; - *Windows*) present_terminal "omarchy-windows-vm install" ;; - *) show_main_menu ;; - esac -} - -show_install_browser_menu() { - case $(menu "Install" " Chrome\n Edge\n Brave\n Brave Origin\n Firefox\n󰖟 Zen") in - *Chrome*) present_terminal "omarchy-install-browser chrome" ;; - *Edge*) present_terminal "omarchy-install-browser edge" ;; - *"Brave Origin"*) present_terminal "omarchy-install-browser brave-origin" ;; - *Brave*) present_terminal "omarchy-install-browser brave" ;; - *Firefox*) present_terminal "omarchy-install-browser firefox" ;; - *Zen*) present_terminal "omarchy-install-browser zen" ;; - *) show_install_menu ;; - esac -} - -show_install_service_menu() { - case $(menu "Install" " Dropbox\n Tailscale\n󱇱 NordVPN [AUR]\n󰏖 ONCE\n󰟵 Bitwarden\n Chromium Account") in - *Dropbox*) present_terminal omarchy-install-dropbox ;; - *Tailscale*) present_terminal omarchy-install-tailscale ;; - *NordVPN*) present_terminal omarchy-install-nordvpn ;; - *ONCE*) present_terminal omarchy-install-once ;; - *Bitwarden*) install_and_launch "Bitwarden" "bitwarden bitwarden-cli" "bitwarden" ;; - *Chromium*) present_terminal omarchy-install-chromium-google-account ;; - *) show_install_menu ;; - esac -} - -show_install_editor_menu() { - case $(menu "Install" " VSCode\n Cursor\n Zed\n Sublime Text\n Helix\n Vim\n Emacs") in - *VSCode*) present_terminal omarchy-install-vscode ;; - *Cursor*) install_and_launch "Cursor" "cursor-bin" "cursor" ;; - *Zed*) present_terminal omarchy-install-zed ;; - *Sublime*) install_and_launch "Sublime Text" "sublime-text-4" "sublime_text" ;; - *Helix*) present_terminal omarchy-install-helix ;; - *Vim*) install "Vim" "vim" ;; - *Emacs*) install "Emacs" "emacs-wayland" && systemctl --user enable --now emacs.service ;; - *) show_install_menu ;; - esac -} - -show_install_terminal_menu() { - case $(menu "Install" " Alacritty\n Foot\n Ghostty\n Kitty") in - *Alacritty*) install_terminal "alacritty" ;; - *Foot*) install_terminal "foot" ;; - *Ghostty*) install_terminal "ghostty" ;; - *Kitty*) install_terminal "kitty" ;; - *) show_install_menu ;; - esac -} - -show_install_ai_menu() { - ollama_pkg=$( - (omarchy-cmd-present nvidia-smi && echo ollama-cuda) || - (omarchy-cmd-present rocminfo && echo ollama-rocm) || - echo ollama - ) - - case $(menu "Install" " Dictation\n󱚤 LM Studio\n󱚤 Ollama\n󱚤 Crush") in - *Dictation*) present_terminal omarchy-voxtype-install ;; - *Studio*) install "LM Studio" "lmstudio-bin" ;; - *Ollama*) install "Ollama" $ollama_pkg ;; - *Crush*) install "Crush" "crush-bin" ;; - *) show_install_menu ;; - esac -} - -show_install_gaming_menu() { - case $(menu "Install" " Steam\n RetroArch\n󰍳 Minecraft\n󰢹 NVIDIA GeForce NOW\n Xbox Cloud Gaming\n󰂯 Xbox Controller\n󰍹 Moonlight (GameStream)\n Lutris (Battle.net)\n󱓟 Heroic (Epic Games)") in - *Steam*) present_terminal omarchy-install-gaming-steam ;; - *RetroArch*) present_terminal omarchy-install-gaming-retroarch ;; - *Minecraft*) install_and_launch "Minecraft" "minecraft-launcher" "minecraft-launcher" ;; - *GeForce*) present_terminal omarchy-install-gaming-geforce-now ;; - *"Xbox Cloud"*) present_terminal omarchy-install-gaming-xbox-cloud ;; - *Xbox*) present_terminal omarchy-install-gaming-xbox-controllers ;; - *Lutris*) present_terminal omarchy-install-gaming-lutris ;; - *Heroic*) present_terminal omarchy-install-gaming-heroic ;; - *Moonlight*) present_terminal omarchy-install-gaming-moonlight ;; - *) show_install_menu ;; - esac -} - -show_install_style_menu() { - case $(menu "Install" "󰸌 Theme\n Background\n Font") in - *Theme*) present_terminal omarchy-theme-install ;; - *Background*) omarchy-theme-bg-install ;; - *Font*) show_install_font_menu ;; - *) show_install_menu ;; - esac -} - -show_install_font_menu() { - case $(menu "Install" " Cascadia Mono\n Meslo LG Mono\n Fira Code\n Victor Code\n Bitstream Vera Mono\n Iosevka" "--width 350") in - *Cascadia*) install_font "Cascadia Mono" "ttf-cascadia-mono-nerd" "CaskaydiaMono Nerd Font" ;; - *Meslo*) install_font "Meslo LG Mono" "ttf-meslo-nerd" "MesloLGL Nerd Font" ;; - *Fira*) install_font "Fira Code" "ttf-firacode-nerd" "FiraCode Nerd Font" ;; - *Victor*) install_font "Victor Code" "ttf-victor-mono-nerd" "VictorMono Nerd Font" ;; - *Bitstream*) install_font "Bitstream Vera Code" "ttf-bitstream-vera-mono-nerd" "BitstromWera Nerd Font" ;; - *Iosevka*) install_font "Iosevka" "ttf-iosevka-nerd" "Iosevka Nerd Font Mono" ;; - *) show_install_menu ;; - esac -} - -show_install_development_menu() { - case $(menu "Install" "󰫏 Ruby on Rails\n Docker DB\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure\n Scala") in - *Rails*) present_terminal "omarchy-install-dev-env ruby" ;; - *Docker*) present_terminal omarchy-install-docker-dbs ;; - *JavaScript*) show_install_javascript_menu ;; - *Go*) present_terminal "omarchy-install-dev-env go" ;; - *PHP*) show_install_php_menu ;; - *Python*) present_terminal "omarchy-install-dev-env python" ;; - *Elixir*) show_install_elixir_menu ;; - *Zig*) present_terminal "omarchy-install-dev-env zig" ;; - *Rust*) present_terminal "omarchy-install-dev-env rust" ;; - *Java*) present_terminal "omarchy-install-dev-env java" ;; - *NET*) present_terminal "omarchy-install-dev-env dotnet" ;; - *OCaml*) present_terminal "omarchy-install-dev-env ocaml" ;; - *Clojure*) present_terminal "omarchy-install-dev-env clojure" ;; - *Scala*) present_terminal "omarchy-install-dev-env scala" ;; - *) show_install_menu ;; - esac -} - -show_install_javascript_menu() { - case $(menu "Install" " Node.js\n Bun\n Deno") in - *Node*) present_terminal "omarchy-install-dev-env node" ;; - *Bun*) present_terminal "omarchy-install-dev-env bun" ;; - *Deno*) present_terminal "omarchy-install-dev-env deno" ;; - *) show_install_development_menu ;; - esac -} - -show_install_php_menu() { - case $(menu "Install" " PHP\n Laravel\n Symfony") in - *PHP*) present_terminal "omarchy-install-dev-env php" ;; - *Laravel*) present_terminal "omarchy-install-dev-env laravel" ;; - *Symfony*) present_terminal "omarchy-install-dev-env symfony" ;; - *) show_install_development_menu ;; - esac -} - -show_install_elixir_menu() { - case $(menu "Install" " Elixir\n Phoenix") in - *Elixir*) present_terminal "omarchy-install-dev-env elixir" ;; - *Phoenix*) present_terminal "omarchy-install-dev-env phoenix" ;; - *) show_install_development_menu ;; - esac -} - -show_remove_menu() { - case $(menu "Remove" "󰣇 Package\n Web App\n TUI\n󰵮 Development\n󰸌 Theme\n Browser\n Dictation\n Gaming\n󰍲 Windows\n󰏓 Preinstalls\n Security") in - *Package*) terminal omarchy-pkg-remove ;; - *Web*) present_terminal omarchy-webapp-remove ;; - *TUI*) present_terminal omarchy-tui-remove ;; - *Development*) show_remove_development_menu ;; - *Theme*) present_terminal omarchy-theme-remove ;; - *Browser*) show_remove_browser_menu ;; - *Dictation*) present_terminal omarchy-voxtype-remove ;; - *Gaming*) show_remove_gaming_menu ;; - *Windows*) present_terminal "omarchy-windows-vm remove" ;; - *Preinstalls*) present_terminal omarchy-remove-preinstalls ;; - *Security*) show_remove_security_menu ;; - *) show_main_menu ;; - esac -} - -show_remove_security_menu() { - case $(menu "Remove" "󰈷 Fingerprint\n Fido2") in - *Fingerprint*) present_terminal omarchy-remove-security-fingerprint ;; - *Fido2*) present_terminal omarchy-remove-security-fido2 ;; - *) show_remove_menu ;; - esac -} - -show_remove_browser_menu() { - case $(menu "Remove" " Chrome\n Edge\n Brave\n Brave Origin\n Firefox\n Zen") in - *Chrome*) present_terminal "omarchy-remove-browser chrome" ;; - *Edge*) present_terminal "omarchy-remove-browser edge" ;; - *"Brave Origin"*) present_terminal "omarchy-remove-browser brave-origin" ;; - *Brave*) present_terminal "omarchy-remove-browser brave" ;; - *Firefox*) present_terminal "omarchy-remove-browser firefox" ;; - *Zen*) present_terminal "omarchy-remove-browser zen" ;; - *) show_remove_menu ;; - esac -} - -show_remove_gaming_menu() { - case $(menu "Remove" " Steam\n RetroArch\n󰍳 Minecraft\n󰢹 NVIDIA GeForce NOW\n Xbox Cloud Gaming\n󰖺 Xbox Controller (󰂯)\n󰍹 Moonlight (GameStream)\n Lutris (Battle.net)\n󱓟 Heroic (Epic Games)") in - *Steam*) present_terminal omarchy-remove-gaming-steam ;; - *RetroArch*) present_terminal omarchy-remove-gaming-retroarch ;; - *Minecraft*) present_terminal omarchy-remove-gaming-minecraft ;; - *GeForce*) present_terminal omarchy-remove-gaming-geforce-now ;; - *"Xbox Cloud"*) present_terminal omarchy-remove-gaming-xbox-cloud ;; - *Xbox*) present_terminal omarchy-remove-gaming-xbox-controllers ;; - *Moonlight*) present_terminal omarchy-remove-gaming-moonlight ;; - *Lutris*) present_terminal omarchy-remove-gaming-lutris ;; - *Heroic*) present_terminal omarchy-remove-gaming-heroic ;; - *) show_remove_menu ;; - esac -} - -show_remove_development_menu() { - case $(menu "Remove" "󰫏 Ruby on Rails\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure\n Scala") in - *Rails*) present_terminal "omarchy-remove-dev-env ruby" ;; - *JavaScript*) show_remove_javascript_menu ;; - *Go*) present_terminal "omarchy-remove-dev-env go" ;; - *PHP*) show_remove_php_menu ;; - *Python*) present_terminal "omarchy-remove-dev-env python" ;; - *Elixir*) show_remove_elixir_menu ;; - *Zig*) present_terminal "omarchy-remove-dev-env zig" ;; - *Rust*) present_terminal "omarchy-remove-dev-env rust" ;; - *Java*) present_terminal "omarchy-remove-dev-env java" ;; - *NET*) present_terminal "omarchy-remove-dev-env dotnet" ;; - *OCaml*) present_terminal "omarchy-remove-dev-env ocaml" ;; - *Clojure*) present_terminal "omarchy-remove-dev-env clojure" ;; - *Scala*) present_terminal "omarchy-remove-dev-env scala" ;; - *) show_remove_menu ;; - esac -} - -show_remove_javascript_menu() { - case $(menu "Remove" " Node.js\n Bun\n Deno") in - *Node*) present_terminal "omarchy-remove-dev-env node" ;; - *Bun*) present_terminal "omarchy-remove-dev-env bun" ;; - *Deno*) present_terminal "omarchy-remove-dev-env deno" ;; - *) show_remove_development_menu ;; - esac -} - -show_remove_php_menu() { - case $(menu "Remove" " PHP\n Laravel\n Symfony") in - *PHP*) present_terminal "omarchy-remove-dev-env php" ;; - *Laravel*) present_terminal "omarchy-remove-dev-env laravel" ;; - *Symfony*) present_terminal "omarchy-remove-dev-env symfony" ;; - *) show_remove_development_menu ;; - esac -} - -show_remove_elixir_menu() { - case $(menu "Remove" " Elixir\n Phoenix") in - *Elixir*) present_terminal "omarchy-remove-dev-env elixir" ;; - *Phoenix*) present_terminal "omarchy-remove-dev-env phoenix" ;; - *) show_remove_development_menu ;; - esac -} - -show_update_menu() { - case $(menu "Update" "  Omarchy\n󰔫 Channel\n Config\n󰸌 Extra Themes\n Process\n󰇅 Hardware\n Firmware\n Password\n Timezone\n Time") in - *Omarchy*) present_terminal omarchy-update ;; - *Channel*) show_update_channel_menu ;; - *Config*) show_update_config_menu ;; - *Themes*) present_terminal omarchy-theme-update ;; - *Process*) show_update_process_menu ;; - *Hardware*) show_update_hardware_menu ;; - *Firmware*) present_terminal omarchy-update-firmware ;; - *Timezone*) present_terminal omarchy-tz-select ;; - *Time*) present_terminal omarchy-update-time ;; - *Password*) show_update_password_menu ;; - *) show_main_menu ;; - esac -} - -show_update_channel_menu() { - case $(menu "Update channel" "🟢 Stable\n🟡 RC\n🟠 Edge\n🔴 Dev") in - *Stable*) present_terminal "omarchy-channel-set stable" ;; - *RC*) present_terminal "omarchy-channel-set rc" ;; - *Edge*) present_terminal "omarchy-channel-set edge" ;; - *Dev*) present_terminal "omarchy-channel-set dev" ;; - *) show_update_menu ;; - esac -} -show_update_process_menu() { - case $(menu "Restart" " Hypridle\n Hyprsunset\n󰎟 Mako\n Swayosd\n󰌧 Walker\n󰍜 Waybar") in - *Hypridle*) omarchy-restart-hypridle ;; - *Hyprsunset*) omarchy-restart-hyprsunset ;; - *Mako*) omarchy-restart-mako ;; - *Swayosd*) omarchy-restart-swayosd ;; - *Walker*) omarchy-restart-walker ;; - *Waybar*) omarchy-restart-waybar ;; - *) show_update_menu ;; - esac -} - -show_update_config_menu() { - case $(menu "Use default config" " Hyprland\n Hypridle\n Hyprlock\n Hyprsunset\n󱣴 Plymouth\n Swayosd\n Tmux\n󰌧 Walker\n󰍜 Waybar") in - *Hyprland*) present_terminal omarchy-refresh-hyprland ;; - *Hypridle*) present_terminal omarchy-refresh-hypridle ;; - *Hyprlock*) present_terminal omarchy-refresh-hyprlock ;; - *Hyprsunset*) present_terminal omarchy-refresh-hyprsunset ;; - *Plymouth*) present_terminal omarchy-refresh-plymouth ;; - *Swayosd*) present_terminal omarchy-refresh-swayosd ;; - *Tmux*) present_terminal omarchy-refresh-tmux ;; - *Walker*) present_terminal omarchy-refresh-walker ;; - *Waybar*) present_terminal omarchy-refresh-waybar ;; - *) show_update_menu ;; - esac -} - -show_update_hardware_menu() { - case $(menu "Restart" " Audio\n󱚾 Wi-Fi\n󰂯 Bluetooth\n󰟸 Trackpad") in - *Audio*) present_terminal omarchy-restart-pipewire ;; - *Wi-Fi*) present_terminal omarchy-restart-wifi ;; - *Bluetooth*) present_terminal omarchy-restart-bluetooth ;; - *Trackpad*) present_terminal omarchy-restart-trackpad ;; - *) show_update_menu ;; - esac -} - -show_update_password_menu() { - case $(menu "Update Password" " Drive Encryption\n User") in - *Drive*) present_terminal omarchy-drive-password ;; - *User*) present_terminal passwd ;; - *) show_update_menu ;; - esac -} - -show_about() { - omarchy-launch-about -} - -show_system_menu() { - local options="󱄄 Screensaver\n Lock" - ! omarchy-toggle-enabled suspend-off && options="$options\n󰒲 Suspend" - omarchy-hibernation-available && options="$options\n󰤁 Hibernate" - options="$options\n󰍃 Logout\n󰜉 Restart\n󰐥 Shutdown" - - case $(menu "System" "$options") in - *Screensaver*) omarchy-launch-screensaver force ;; - *Lock*) omarchy-system-lock ;; - *Suspend*) systemctl suspend ;; - *Hibernate*) systemctl hibernate ;; - *Logout*) omarchy-system-logout ;; - *Restart*) omarchy-system-reboot ;; - *Shutdown*) omarchy-system-shutdown ;; - *) back_to show_main_menu ;; - esac -} - -show_main_menu() { - go_to_menu "$(menu "Go" "󰀻 Apps\n󰧑 Learn\n󱓞 Trigger\n Style\n Setup\n󰉉 Install\n󰭌 Remove\n Update\n About\n System")" -} - -go_to_menu() { - case "${1,,}" in - *apps*) walker -p "Launch…" ;; - *learn*) show_learn_menu ;; - *trigger*) show_trigger_menu ;; - *toggle*) show_toggle_menu ;; - *hardware*) show_hardware_menu ;; - *share*) show_share_menu ;; - *reminder-set*) show_custom_reminder_input ;; - *reminder*) show_reminder_menu ;; - *background*) show_background_menu ;; - *capture*) show_capture_menu ;; - *style*) show_style_menu ;; - *theme*) show_theme_menu ;; - *screenrecord*) show_screenrecord_menu ;; - *setup*) show_setup_menu ;; - *power*) show_setup_power_menu ;; - *install*) show_install_menu ;; - *remove*) show_remove_menu ;; - *update*) show_update_menu ;; - *about*) show_about ;; - *system*) show_system_menu ;; - esac -} - -# Allow user extensions and overrides -USER_EXTENSIONS="$HOME/.config/omarchy/extensions/menu.sh" -[[ -f $USER_EXTENSIONS ]] && source "$USER_EXTENSIONS" - -toggle_existing_menu - -if [[ -n $1 ]]; then - BACK_TO_EXIT=true - go_to_menu "$1" -else - show_main_menu -fi + summon) + exec omarchy-shell shell summon omarchy.menu "$(menu_payload "$route")" + ;; + close) + exec omarchy-shell shell hide omarchy.menu + ;; + refresh | ping) + exec omarchy-shell shell call omarchy.menu "$verb" "{}" + ;; + -h | --help | help) + cat <, or close it if already open. Default verb. + summon [route] Always open the menu (no close-if-visible toggle). + close Close the menu if it is visible. + refresh Re-parse the menu JSONC files. + ping Health check. + +Route is an item id (e.g. setup.power) or alias (e.g. power). Defaults to +"root", which opens the top-level menu. +USAGE + exit 0 + ;; + *) + echo "omarchy-menu: unknown verb '$verb'. Try 'omarchy menu --help'." >&2 + exit 2 + ;; +esac diff --git a/bin/omarchy-menu-clipboard b/bin/omarchy-menu-clipboard new file mode 100755 index 00000000..83a6b557 --- /dev/null +++ b/bin/omarchy-menu-clipboard @@ -0,0 +1,6 @@ +#!/bin/bash +# omarchy:summary=Launch the clipboard manager +# omarchy:group=menu +# omarchy:examples=omarchy menu clipboard + +omarchy-shell shell toggle omarchy.clipboard diff --git a/bin/omarchy-menu-emoji b/bin/omarchy-menu-emoji new file mode 100755 index 00000000..edc1427b --- /dev/null +++ b/bin/omarchy-menu-emoji @@ -0,0 +1,6 @@ +#!/bin/bash +# omarchy:summary=Launch emojis +# omarchy:group=menu +# omarchy:examples=omarchy menu emoji + +omarchy-shell shell toggle omarchy.emojis diff --git a/bin/omarchy-menu-emoji-insert b/bin/omarchy-menu-emoji-insert new file mode 100755 index 00000000..583702e0 --- /dev/null +++ b/bin/omarchy-menu-emoji-insert @@ -0,0 +1,20 @@ +#!/bin/bash + +# omarchy:summary=Insert an emoji into the focused application +# omarchy:group=menu +# omarchy:args= +# omarchy:hidden=true + +emoji="${1:-}" +copy_pid="" + +[[ -n $emoji ]] || exit + +printf '%s' "$emoji" | wl-copy --type text/plain --sensitive --foreground & +copy_pid=$! + +sleep 0.15 +wtype -M shift -k Insert -m shift 2>/dev/null || true +sleep 0.2 + +kill "$copy_pid" 2>/dev/null || true diff --git a/bin/omarchy-menu-file b/bin/omarchy-menu-file index 8cdf4bb0..c2662060 100755 --- a/bin/omarchy-menu-file +++ b/bin/omarchy-menu-file @@ -1,15 +1,15 @@ #!/bin/bash -# omarchy:summary=Pick a file with Walker +# omarchy:summary=Pick a file from a menu # omarchy:group=menu # omarchy:name=file -# omarchy:args=label paths formats [walker args...] +# omarchy:args=label paths formats [menu args...] # omarchy:examples=omarchy menu file "Select image" "$HOME/Pictures" "jpg png webp"|omarchy-menu-file "Select media" "$HOME/Pictures:$HOME/Videos" "jpg png mp4 mov" --width 800 set -euo pipefail if (( $# < 3 )); then - echo "Usage: omarchy-menu-file