Compare commits

..
Author SHA1 Message Date
ZacharyZhang-NY 466c2ca14a Point package managers at China mirrors on cn installs
npm, PyPI and uv, Go, rustup and crates.io, RubyGems and Bundler,
and Maven reach their registries slowly or not at all from China.
On a cn install, link the profile's environment, cargo and gem
drop-ins into place. A regular file the admin wrote is never
replaced; the install fails instead.
2026-10-12 00:29:13 -04:00
406 changed files with 1277 additions and 9292 deletions

No files matched your search

+2 -7
View File
@@ -98,13 +98,8 @@ Exceptions are allowed for migration and package-helper scripts where the helper
# Region # Region
- `/etc/omarchy/region` holds `cn`; no file means Global. `omarchy-region` prints the region and switches it (`omarchy region cn|global`); the profile lives in `default/regions/cn/`, its contract in `docs/regions.md`, user-facing docs in `manual/52-china.md`. - `/etc/omarchy/region` holds the region the installer derived from the timezone (`cn`); no file means Global. The profile lives in `default/regions/cn/`, its contract in `docs/regions.md`.
- `install/config/region.sh` is the one system leaf for both the ISO install (the `/etc/omarchy/region` marker the installer derives from the timezone) and the runtime switch (`OMARCHY_REGION`): the region file and the profile drop-ins it symlinks. Input methods are not part of a region: the installer's keyboard choice and `omarchy-setup-input` own them (`docs/input-methods.md`). - `install/config/region.sh` links the `cn` developer mirrors into `/etc` and `/.cargo` and refuses to replace a regular file it did not create.
- `omarchy-apply-pacman` renders pacman for every caller: the channel templates, the profile's `pacman/mirrorlist-<channel>.prepend` ahead of the mirrorlist, and its `pacman.conf.append`. Nothing in `omarchy update` or migrations may change the region or `/etc/locale.conf`.
- Language is separate from region: `omarchy-language en|zh` (Setup > Language) owns `LANG` in `/etc/locale.conf`, the installer sets it from its first screen, and nothing keyed by region touches it.
- China-only menu rows carry `"when":"[[ \"$(omarchy-region)\" == \"China\" ]]"` on every row, not only the submenu, because search checks each row's own guard.
- Chinese menu labels live in `default/omarchy/omarchy-menu.zh-cn.jsonc`, merged when `LANG` starts with `zh_CN`; give a new menu row with translatable words a label there.
- Shell text goes through `I18n.tr("English %1", arg)` from `qs.Commons`, with its Chinese in `shell/Commons/I18nZh.js` keyed by the English. Notifications and OSD messages are worded by their sender with `omarchy-text "English %1" "$arg"` (Python: `text()`, QML: `I18n.tr`), which reads the same table; the shell shows them as given, so user text is never translated. `test/shell.d/shell-i18n-test.sh` fails on a missing entry.
# Config Structure # Config Structure
-1
View File
@@ -71,7 +71,6 @@ The manual lives in [`manual/`](manual/), which is its authoritative source.
- [Omarchy on...](manual/49-omarchy-on.md) - [Omarchy on...](manual/49-omarchy-on.md)
- [Dual Boot Install](manual/50-dual-boot-install.md) - [Dual Boot Install](manual/50-dual-boot-install.md)
- [Unattended Installs](manual/51-unattended-installs.md) - [Unattended Installs](manual/51-unattended-installs.md)
- [China](manual/52-china.md)
## License ## License
-8
View File
@@ -24,10 +24,6 @@ Run `omarchy-restart-shell` after making changes to QML files.
- Panel / overlay / menu plugins must expose `open(payloadJson)` and - Panel / overlay / menu plugins must expose `open(payloadJson)` and
`close()` lifecycle methods for `shell summon` and `shell hide`. `close()` lifecycle methods for `shell summon` and `shell hide`.
## Text
Wrap every user-visible string in `I18n.tr()` (`import qs.Commons`) and add its Chinese to `shell/Commons/I18nZh.js`; plain JS models that Node tests load stay English, and their text is wrapped where QML shows it.
## IPC ## IPC
- `bin/omarchy-shell` is the canonical IPC entry point. It forwards to - `bin/omarchy-shell` is the canonical IPC entry point. It forwards to
@@ -49,7 +45,3 @@ unicode characters. Agent file-editing tools can strip multi-byte codepoints
in some positions — do **not** rewrite widget files wholesale through those in some positions — do **not** rewrite widget files wholesale through those
tools. For glyph fixes, make a targeted edit with the surrounding context, or tools. For glyph fixes, make a targeted edit with the surrounding context, or
use a Python script that inserts codepoints via `chr(0xXXXXX)`. use a Python script that inserts codepoints via `chr(0xXXXXX)`.
## Palette references
Import `qs.Commons as Commons` and use `Commons.Color` for the palette singleton, including `Connections.target`. Qt 6.12 introduces a `Color` type in `QtQuick` that shadows unqualified palette references. This applies to first-party components and third-party plugin QML alike. Keep the unqualified `qs.Commons` import when the file also uses `Style`, `Util`, or `Border`.
+8
View File
@@ -0,0 +1,8 @@
[Desktop Entry]
Version=1.0
Name=Disk Usage
Exec=xdg-terminal-exec --app-id=TUI.float -e bash -c "dua i /"
Terminal=false
Type=Application
Icon=disk-usage
StartupNotify=true
-3
View File
@@ -46,7 +46,6 @@ GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
GROUP_DESCRIPTIONS[default]="Default application selection" GROUP_DESCRIPTIONS[default]="Default application selection"
GROUP_DESCRIPTIONS[dev]="Omarchy development tools" GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
GROUP_DESCRIPTIONS[disk]="Disk performance helpers" GROUP_DESCRIPTIONS[disk]="Disk performance helpers"
GROUP_DESCRIPTIONS[dictation]="Dictation recording"
GROUP_DESCRIPTIONS[display]="Display and text scaling" GROUP_DESCRIPTIONS[display]="Display and text scaling"
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration" GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption" GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
@@ -59,7 +58,6 @@ GROUP_DESCRIPTIONS[hw]="Hardware detection and controls"
GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls" GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls"
GROUP_DESCRIPTIONS[install]="Optional software installers" GROUP_DESCRIPTIONS[install]="Optional software installers"
GROUP_DESCRIPTIONS[installed]="Installed optional service checks" GROUP_DESCRIPTIONS[installed]="Installed optional service checks"
GROUP_DESCRIPTIONS[language]="System language"
GROUP_DESCRIPTIONS[launch]="Application launchers" GROUP_DESCRIPTIONS[launch]="Application launchers"
GROUP_DESCRIPTIONS[menu]="Omarchy menu commands" GROUP_DESCRIPTIONS[menu]="Omarchy menu commands"
GROUP_DESCRIPTIONS[migrate]="Migration runner" GROUP_DESCRIPTIONS[migrate]="Migration runner"
@@ -75,7 +73,6 @@ GROUP_DESCRIPTIONS[plymouth]="Plymouth boot theme management"
GROUP_DESCRIPTIONS[power]="Power supply detection" GROUP_DESCRIPTIONS[power]="Power supply detection"
GROUP_DESCRIPTIONS[powerprofiles]="Power profile management" GROUP_DESCRIPTIONS[powerprofiles]="Power profile management"
GROUP_DESCRIPTIONS[refresh]="Reset config to defaults" GROUP_DESCRIPTIONS[refresh]="Reset config to defaults"
GROUP_DESCRIPTIONS[region]="Region-specific package sources and setup"
GROUP_DESCRIPTIONS[reinstall]="Reinstall and reset workflows" GROUP_DESCRIPTIONS[reinstall]="Reinstall and reset workflows"
GROUP_DESCRIPTIONS[reminder]="Desktop notification reminders" GROUP_DESCRIPTIONS[reminder]="Desktop notification reminders"
GROUP_DESCRIPTIONS[remove]="Removal workflows" GROUP_DESCRIPTIONS[remove]="Removal workflows"
+16 -16
View File
@@ -85,7 +85,7 @@ finish() {
fail() { fail() {
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
printf '@@omarchy error %s\n' "$1" printf '@@omarchy error %s\n' "$1"
omarchy-notification-send -g 󰚩 -u critical "$(omarchy-text "Couldn't add the %1 account" "$name")" "$1" omarchy-notification-send -g 󰚩 -u critical "Couldn't add the $name account" "$1"
else else
echo "$1" >&2 echo "$1" >&2
fi fi
@@ -113,9 +113,9 @@ interruptible() {
} }
if omarchy-cmd-missing "$provider"; then if omarchy-cmd-missing "$provider"; then
say "$(omarchy-text "Installing the %1 CLI…" "$name")" say "Installing the $name CLI…"
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
mise use -g "$package" >/dev/null 2>&1 || fail "$(omarchy-text "Installing the %1 CLI didn't finish." "$name")" mise use -g "$package" >/dev/null 2>&1 || fail "Installing the $name CLI didn't finish."
else else
mise use -g "$package" || exit 1 mise use -g "$package" || exit 1
fi fi
@@ -150,26 +150,26 @@ if [[ -n $reauth ]]; then
else else
account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }') account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }')
fi fi
[[ -n $account_home ]] || fail "$(omarchy-text "No %1 account named %2." "$name" "$reauth")" [[ -n $account_home ]] || fail "No $name account named $reauth."
if [[ $account_home == "$HOME/.$provider" ]]; then if [[ $account_home == "$HOME/.$provider" ]]; then
say "$(omarchy-text "Sign in to %1 again in the browser window that opens." "$name")" say "Sign in to $name again in the browser window that opens."
login || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")" login || fail "The $name sign-in didn't finish."
else else
say "$(omarchy-text "Sign in again as this account in the private window that opens.")" say "Sign in again as this account in the private window that opens."
private_browser private_browser
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")" interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "The $name sign-in didn't finish."
fi fi
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
finish "$(omarchy-text "Signed in to %1 again." "$name")" finish "Signed in to $name again."
exit 0 exit 0
fi fi
# The first subscription signs in where the CLI always looks, through your # The first subscription signs in where the CLI always looks, through your
# normal browser: nothing is signed in there yet that it could be mistaken for. # normal browser: nothing is signed in there yet that it could be mistaken for.
if ! primary_signed_in "$provider"; then if ! primary_signed_in "$provider"; then
say "$(omarchy-text "Sign in to %1 in the browser window that opens." "$name")" say "Sign in to $name in the browser window that opens."
if login && primary_signed_in "$provider"; then if login && primary_signed_in "$provider"; then
# The first agent signed in on a machine with none chosen becomes the # The first agent signed in on a machine with none chosen becomes the
# default, so `omarchy agent` and the panel's starter prompts have one to # default, so `omarchy agent` and the panel's starter prompts have one to
@@ -179,9 +179,9 @@ if ! primary_signed_in "$provider"; then
printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent" printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent"
fi fi
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
finish "$(omarchy-text "Signed in to %1." "$name")" finish "Signed in to $name."
else else
fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")" fail "The $name sign-in didn't finish."
fi fi
else else
if [[ -z $label && $events == "false" && -t 0 ]]; then if [[ -z $label && $events == "false" && -t 0 ]]; then
@@ -189,7 +189,7 @@ else
fi fi
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
say "$(omarchy-text "Sign in as the account you're adding in the private window that opens.")" say "Sign in as the account you're adding in the private window that opens."
else else
cat <<EOF cat <<EOF
@@ -201,7 +201,7 @@ Don't log out of the $name CLI to do this: logging out revokes the saved sign-in
EOF EOF
fi fi
home=$(omarchy-agent-account-state begin "$provider") || fail "$(omarchy-text "Couldn't set up a home for the new account.")" home=$(omarchy-agent-account-state begin "$provider") || fail "Couldn't set up a home for the new account."
private_browser private_browser
interruptible env BROWSER="$browser" "$home_env=$home" "${login_command[@]}" interruptible env BROWSER="$browser" "$home_env=$home" "${login_command[@]}"
@@ -211,9 +211,9 @@ EOF
added=$(jq -r '.label + (if .email != "" then " (" + .email + ")" else "" end)' <<<"$account") added=$(jq -r '.label + (if .email != "" then " (" + .email + ")" else "" end)' <<<"$account")
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 & omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
if [[ $events == "true" ]]; then if [[ $events == "true" ]]; then
finish "$(omarchy-text "Added %1." "$added")" finish "Added $added."
else else
finish "$(omarchy-text "Added %1." "$added")" finish "Added $added."
echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")" echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")"
fi fi
else else
+22 -29
View File
@@ -73,11 +73,6 @@ class AccountError(Exception):
pass pass
def text(english: str, *args: object) -> str:
"""English with %1.. filled, in the session's language, from the shell's table."""
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
# ------------------------------------------------------------------- paths # ------------------------------------------------------------------- paths
@@ -340,12 +335,12 @@ def begin(provider: str) -> Path:
def register(provider: str, label: str, pending: Path) -> dict[str, Any]: def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
pending = pending.resolve() pending = pending.resolve()
if pending.parent != (accounts_root() / provider / ".pending").resolve(): if pending.parent != (accounts_root() / provider / ".pending").resolve():
raise AccountError(text("%1 is not a pending %2 login", pending, PROVIDERS[provider]['name'])) raise AccountError(f"{pending} is not a pending {PROVIDERS[provider]['name']} login")
found = identity(provider, pending) found = identity(provider, pending)
if not found["accountId"]: if not found["accountId"]:
shutil.rmtree(pending, ignore_errors=True) shutil.rmtree(pending, ignore_errors=True)
raise AccountError(text("The login didn't finish, so no account was added.")) raise AccountError("The login didn't finish, so no account was added.")
with Locked(provider) as registry: with Locked(provider) as registry:
# Each home is asked who it is now, not trusted from when it was added: a # Each home is asked who it is now, not trusted from when it was added: a
@@ -355,10 +350,10 @@ def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
for account in registry["accounts"]: for account in registry["accounts"]:
if account.get("accountId") == found["accountId"]: if account.get("accountId") == found["accountId"]:
shutil.rmtree(pending, ignore_errors=True) shutil.rmtree(pending, ignore_errors=True)
raise AccountError(text( raise AccountError(
"That's %1 (%2). Try again, and sign in as the other account in the private window.", f"That's {account['label']} ({found['email'] or 'already added'}). "
account["label"], found["email"] or text("already added"), "Try again, and sign in as the other account in the private window."
)) )
label = label.strip() or found["email"].split("@")[0] or "Account" label = label.strip() or found["email"].split("@")[0] or "Account"
account_id = unique_id(provider, registry, label) account_id = unique_id(provider, registry, label)
@@ -400,11 +395,11 @@ def unique_id(provider: str, registry: dict[str, Any], label: str, keep: dict[st
def rename(provider: str, target: str, label: str) -> dict[str, Any]: def rename(provider: str, target: str, label: str) -> dict[str, Any]:
label = label.strip() label = label.strip()
if not label: if not label:
raise AccountError(text("Give the account a name")) raise AccountError("Give the account a name")
with Locked(provider) as registry: with Locked(provider) as registry:
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target)) raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
new_id = unique_id(provider, registry, label, keep=account) new_id = unique_id(provider, registry, label, keep=account)
if not account.get("primary") and not account.get("home"): if not account.get("primary") and not account.get("home"):
account["home"] = str(account_home(provider, account)) account["home"] = str(account_home(provider, account))
@@ -433,7 +428,7 @@ def use(provider: str, target: str) -> tuple[dict[str, Any], dict[str, Any]]:
target = ids[(ids.index(previous["id"]) + 1) % len(ids)] target = ids[(ids.index(previous["id"]) + 1) % len(ids)]
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target)) raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
registry["active"] = account["id"] registry["active"] = account["id"]
registry["alert"] = "" registry["alert"] = ""
if not account.get("primary"): if not account.get("primary"):
@@ -461,12 +456,12 @@ def remove(provider: str, target: str) -> dict[str, Any]:
with Locked(provider) as registry: with Locked(provider) as registry:
account = find(registry, target) account = find(registry, target)
if not account: if not account:
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target)) raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
if account.get("primary"): if account.get("primary"):
raise AccountError(text("%1 is the login in %2; it can't be removed here.", account["label"], primary_home(provider))) raise AccountError(f"{account['label']} is the login in {primary_home(provider)}; it can't be removed here.")
home = account_home(provider, account) home = account_home(provider, account)
if home_in_use(provider, home): if home_in_use(provider, home):
raise AccountError(text("A running %1 session uses %2. Quit it first, then remove the account.", PROVIDERS[provider]['name'], account["label"])) raise AccountError(f"A running {PROVIDERS[provider]['name']} session uses {account['label']}. Quit it first, then remove the account.")
# Only the account's own directory goes. Its links point into the primary # Only the account's own directory goes. Its links point into the primary
# home, and rmtree removes a link without following it. # home, and rmtree removes a link without following it.
if home.resolve().parent == (accounts_root() / provider).resolve(): if home.resolve().parent == (accounts_root() / provider).resolve():
@@ -479,14 +474,14 @@ def remove(provider: str, target: str) -> dict[str, Any]:
def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]: def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]:
if mode not in SWITCH_MODES: if mode not in SWITCH_MODES:
raise AccountError(text("Switch mode is manual or auto")) raise AccountError("Switch mode is manual or auto")
with Locked(provider) as registry: with Locked(provider) as registry:
registry["switch"] = mode registry["switch"] = mode
if threshold is not None: if threshold is not None:
try: try:
registry["threshold"] = max(50, min(100, int(threshold))) registry["threshold"] = max(50, min(100, int(threshold)))
except ValueError: except ValueError:
raise AccountError(text("The threshold is a percentage between 50 and 100")) raise AccountError("The threshold is a percentage between 50 and 100")
registry["alert"] = "" registry["alert"] = ""
return dict(registry) return dict(registry)
@@ -561,10 +556,10 @@ def duration(delta: dt.timedelta) -> str:
hours, minutes = divmod(minutes, 60) hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24) days, hours = divmod(hours, 24)
if days: if days:
return text("%1d %2h", days, hours) return f"{days}d {hours}h"
if hours: if hours:
return text("%1h %2m", hours, minutes) return f"{hours}h {minutes}m"
return text("%1m", minutes) return f"{minutes}m"
# The whole switching policy, with no I/O: given the registry and the per # The whole switching policy, with no I/O: given the registry and the per
@@ -664,23 +659,21 @@ def autoswitch(provider: str, now: dt.datetime | None = None) -> bool:
threshold = registry["threshold"] threshold = registry["threshold"]
if decision["action"] == "exhausted": if decision["action"] == "exhausted":
soonest = decision["soonest"] soonest = decision["soonest"]
staying = text("Staying on %1.", source["label"]) when = f" {soonest[1]['label']} resets in {duration(soonest[0] - now)}." if soonest else ""
if soonest: notify(f"All {name} accounts are over {threshold}%", f"Staying on {source['label']}.{when}")
staying = text("Staying on %1. %2 resets in %3.", source["label"], soonest[1]["label"], duration(soonest[0] - now))
notify(text("All %1 accounts are over %2%", name, threshold), staying)
return False return False
target = decision["to"] target = decision["to"]
headline = text("%1 is at %2% of a %3 limit", source["label"], round(decision["fromPeak"]), name) headline = f"{source['label']} is at {round(decision['fromPeak'])}% of a {name} limit"
if decision["action"] == "switch": if decision["action"] == "switch":
registry["active"] = target["id"] registry["active"] = target["id"]
if not target.get("primary"): if not target.get("primary"):
link_shared(provider, account_home(provider, target)) link_shared(provider, account_home(provider, target))
notify(headline, text("New %1 sessions now use %2 (%3%). Running sessions stay on %4.", name, target["label"], round(decision["toPeak"]), source["label"])) notify(headline, f"New {name} sessions now use {target['label']} ({round(decision['toPeak'])}%). Running sessions stay on {source['label']}.")
return True return True
notify( notify(
headline, headline,
text("%1 is at %2%. Click to switch new sessions to it.", target["label"], round(decision["toPeak"])), f"{target['label']} is at {round(decision['toPeak'])}%. Click to switch new sessions to it.",
["omarchy-agent-account-use", provider, target["id"]], ["omarchy-agent-account-use", provider, target["id"]],
) )
return False return False
+2 -2
View File
@@ -31,9 +31,9 @@ active=$(jq -r '.active.label + (if (.active.plan // "") != "" then " (" + .acti
if [[ $(jq -r .previous.id <<<"$result") == "$(jq -r .active.id <<<"$result")" ]]; then if [[ $(jq -r .previous.id <<<"$result") == "$(jq -r .active.id <<<"$result")" ]]; then
echo "New $name sessions already use $active." echo "New $name sessions already use $active."
else else
message=$(omarchy-text "Running sessions stay on %1." "$previous") message="Running sessions stay on $previous."
echo "New $name sessions now use $active. $message" echo "New $name sessions now use $active. $message"
omarchy-notification-send -g 󰚩 "$(omarchy-text "New %1 sessions now use %2" "$name" "$active")" "$message" omarchy-notification-send -g 󰚩 "New $name sessions now use $active" "$message"
# The usage record's own limits describe the active account. # The usage record's own limits describe the active account.
omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 & omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 &
fi fi
+4 -11
View File
@@ -22,7 +22,6 @@ import json
import os import os
import re import re
import sqlite3 import sqlite3
import subprocess
import sys import sys
import tempfile import tempfile
import time import time
@@ -33,11 +32,6 @@ from typing import Any
AGENT_ID = "claude" AGENT_ID = "claude"
AGENT_NAME = "Claude Code" AGENT_NAME = "Claude Code"
def text(english, *args):
"""English with %1.. filled, in the session's language, from the shell's table."""
return subprocess.run(["omarchy-text", english, *map(str, args)], capture_output=True, text=True, check=True).stdout.rstrip("\n")
AUTH_HELP = "Run `claude auth login` to restore authoritative usage." AUTH_HELP = "Run `claude auth login` to restore authoritative usage."
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage" USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
PROBE_MIN_INTERVAL_SECONDS = 15 PROBE_MIN_INTERVAL_SECONDS = 15
@@ -995,12 +989,11 @@ def probe_limits(access_token: str) -> dict[str, Any]:
except urllib.error.HTTPError as error: except urllib.error.HTTPError as error:
retry_after = error.headers.get("retry-after", "") if error.headers else "" retry_after = error.headers.get("retry-after", "") if error.headers else ""
if error.code == 429: if error.code == 429:
if retry_after: help_text = "Anthropic's usage endpoint is rate limiting checks right now" + (
help_text = text("Anthropic's usage endpoint is rate limiting checks right now (retry after %1s). Local Claude Code stats are still shown.", retry_after) f" (retry after {retry_after}s)" if retry_after else ""
else: ) + ". Local Claude Code stats are still shown."
help_text = text("Anthropic's usage endpoint is rate limiting checks right now. Local Claude Code stats are still shown.")
else: else:
help_text = text("Anthropic's usage endpoint returned status %1. Local Claude Code stats are still shown.", error.code) help_text = f"Anthropic's usage endpoint returned status {error.code}. Local Claude Code stats are still shown."
return {"ok": False, "helpText": help_text} return {"ok": False, "helpText": help_text}
except Exception: except Exception:
# A transport failure reached no server at all — no route, no DNS. Any # A transport failure reached no server at all — no route, no DNS. Any
+2 -2
View File
@@ -6,8 +6,8 @@ wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle >/dev/null
if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then
omarchy-brightness-keyboard-mute on omarchy-brightness-keyboard-mute on
omarchy-osd -i microphone-muted -m "$(omarchy-text "Microphone muted")" omarchy-osd -i microphone-muted -m "Microphone muted"
else else
omarchy-brightness-keyboard-mute off omarchy-brightness-keyboard-mute off
omarchy-osd -i microphone -m "$(omarchy-text "Microphone on")" omarchy-osd -i microphone -m "Microphone on"
fi fi
+1 -1
View File
@@ -13,7 +13,7 @@ sinks=$(timeout 2 pactl -f json list sinks |
sinks_count=$(jq 'length' <<<"$sinks") sinks_count=$(jq 'length' <<<"$sinks")
if (( sinks_count == 0 )); then if (( sinks_count == 0 )); then
omarchy-osd -m "$(omarchy-text "No audio devices found")" omarchy-osd -m "No audio devices found"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -13,5 +13,5 @@ fi
level=$1 level=$1
omarchy-notification-send -g 󱐋 -u critical "$(omarchy-text "Time to recharge!")" "$(omarchy-text "Battery is down to %1%" "$level")" -i battery-caution -t 30000 omarchy-notification-send -g 󱐋 -u critical "Time to recharge!" "Battery is down to ${level}%" -i battery-caution -t 30000
omarchy-hook battery-low "$level" omarchy-hook battery-low "$level"
+3 -3
View File
@@ -129,9 +129,9 @@ if [[ $charge_holding == "true" ]]; then
threshold_label="${threshold_end}%" threshold_label="${threshold_end}%"
fi fi
omarchy-text "Battery %1% · Holding at %2 · %3W / %4Wh" "$percentage" "$threshold_label" "$power_rate" "$capacity" echo "Battery ${percentage}% · Holding at ${threshold_label} · ${power_rate}W / ${capacity}Wh"
elif [[ $state == "charging" ]]; then elif [[ $state == "charging" ]]; then
omarchy-text "Battery %1% · %2 to full ·  %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity" echo "Battery ${percentage}% · ${time_remaining} to full ·  ${power_rate}W / ${capacity}Wh"
else else
omarchy-text "Battery %1% · %2 left ·  %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity" echo "Battery ${percentage}% · ${time_remaining} left ·  ${power_rate}W / ${capacity}Wh"
fi fi
+2 -2
View File
@@ -23,7 +23,7 @@ SELECTION=$(slurp 2>/dev/null)
RESULT=$(grim -g "$SELECTION" - | zbarimg -q --raw -Sdisable -Sqrcode.enable - 2>/dev/null) RESULT=$(grim -g "$SELECTION" - | zbarimg -q --raw -Sdisable -Sqrcode.enable - 2>/dev/null)
if [[ -z $RESULT ]]; then if [[ -z $RESULT ]]; then
omarchy-notification-send -g 󰐲 -u critical "$(omarchy-text "No QR code found")" "$(omarchy-text "Select a region containing a QR code")" omarchy-notification-send -g 󰐲 -u critical "No QR code found" "Select a region containing a QR code"
exit 1 exit 1
fi fi
@@ -33,4 +33,4 @@ fi
# the notification history, and an unmarked copy would be retained by clipboard # the notification history, and an unmarked copy would be retained by clipboard
# history. Pasting still works; only the recorded copy is given up. # history. Pasting still works; only the recorded copy is given up.
printf '%s' "$RESULT" | wl-copy --sensitive printf '%s' "$RESULT" | wl-copy --sensitive
omarchy-notification-send -g 󰐲 "$(omarchy-text "QR code copied to clipboard")" omarchy-notification-send -g 󰐲 "QR code copied to clipboard"
+4 -4
View File
@@ -23,7 +23,7 @@
OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}" OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}"
if [[ ! -d $OUTPUT_DIR ]]; then if [[ ! -d $OUTPUT_DIR ]]; then
omarchy-notification-send -u critical -t 3000 "$(omarchy-text "Screen recording directory does not exist: %1" "$OUTPUT_DIR")" omarchy-notification-send -u critical -t 3000 "Screen recording directory does not exist: $OUTPUT_DIR"
exit 1 exit 1
fi fi
@@ -76,7 +76,7 @@ start_webcam_overlay() {
if [[ -z $WEBCAM_DEVICE ]]; then if [[ -z $WEBCAM_DEVICE ]]; then
WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p') WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p')
if [[ -z $WEBCAM_DEVICE ]]; then if [[ -z $WEBCAM_DEVICE ]]; then
omarchy-notification-send -u critical -t 3000 "$(omarchy-text "No webcam devices found")" omarchy-notification-send -u critical -t 3000 "No webcam devices found"
return 1 return 1
fi fi
fi fi
@@ -231,7 +231,7 @@ stop_screenrecording() {
if pgrep -f "^gpu-screen-recorder" >/dev/null; then if pgrep -f "^gpu-screen-recorder" >/dev/null; then
pkill -9 -f "^gpu-screen-recorder" pkill -9 -f "^gpu-screen-recorder"
omarchy-notification-send -u critical -t 5000 "$(omarchy-text "Screen recording error")" "$(omarchy-text "Recording process had to be force-killed. Video may be corrupted.")" omarchy-notification-send -u critical -t 5000 "Screen recording error" "Recording process had to be force-killed. Video may be corrupted."
else else
finalize_recording finalize_recording
local filename=$(cat "$RECORDING_FILE" 2>/dev/null) local filename=$(cat "$RECORDING_FILE" 2>/dev/null)
@@ -241,7 +241,7 @@ stop_screenrecording() {
# Generate a preview thumbnail from the first frame # Generate a preview thumbnail from the first frame
ffmpeg -y -i "$filename" -ss 00:00:00.1 -vframes 1 -q:v 2 "$preview" -loglevel quiet 2>/dev/null ffmpeg -y -i "$filename" -ss 00:00:00.1 -vframes 1 -q:v 2 "$preview" -loglevel quiet 2>/dev/null
omarchy-notification-send "$(omarchy-text "Screen recording saved")" "$(omarchy-text "Open with Super + Alt + , (or click this)")" \ omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \
-t 10000 --image "${preview:-$filename}" \ -t 10000 --image "${preview:-$filename}" \
--exec mpv -- "$filename" --exec mpv -- "$filename"
@@ -5,7 +5,7 @@
mapfile -t devices < <(omarchy-capture-webcam-list) mapfile -t devices < <(omarchy-capture-webcam-list)
if (( ${#devices[@]} == 0 )); then if (( ${#devices[@]} == 0 )); then
omarchy-notification-send "$(omarchy-text "No webcam devices found")" -u critical -t 3000 omarchy-notification-send "No webcam devices found" -u critical -t 3000
exit 1 exit 1
fi fi
+1 -1
View File
@@ -23,4 +23,4 @@ TEXT=$(grim -g "$SELECTION" - | tesseract stdin stdout --oem 1 --psm 6 -l "${OMA
[[ -z $TEXT ]] && exit 1 [[ -z $TEXT ]] && exit 1
printf "%s" "$TEXT" | wl-copy printf "%s" "$TEXT" | wl-copy
omarchy-notification-send -g 󰴑 "$(omarchy-text "Copied text from selection to clipboard")" omarchy-notification-send -g 󰴑 "Copied text from selection to clipboard"
+1 -1
View File
@@ -18,7 +18,7 @@ copy_url() {
[[ -n $url ]] || return 1 [[ -n $url ]] || return 1
printf '%s' "$url" | wl-copy --type text/plain printf '%s' "$url" | wl-copy --type text/plain
omarchy-notification-send -g 󰅍 "$(omarchy-text "URL copied to clipboard")" omarchy-notification-send -g 󰅍 "URL copied to clipboard"
} }
reply_copied() { reply_copied() {
+3 -3
View File
@@ -87,7 +87,7 @@ download_url() {
# Don't show anything until yt-dlp confirms there's actually a video to grab. # Don't show anything until yt-dlp confirms there's actually a video to grab.
if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then if ! yt-dlp --no-playlist --simulate --quiet --no-warnings --no-exec --no-exec-before-download -- "$url" >/dev/null 2>&1; then
omarchy-notification-send -u critical -g 󰅖 "$(omarchy-text "No video found for download")" "$url" omarchy-notification-send -u critical -g 󰅖 "No video found for download" "$url"
exit 0 exit 0
fi fi
@@ -148,7 +148,7 @@ download_url() {
# toast would exit before the thumbnail cleanup below is ever scheduled. # toast would exit before the thumbnail cleanup below is ever scheduled.
# `--` keeps mpv from parsing a leading-dash filename as an option; the path # `--` keeps mpv from parsing a leading-dash filename as an option; the path
# is one discrete argument, so it never reaches a shell. # is one discrete argument, so it never reaches a shell.
omarchy-notification-send -g 󰄬 "$(omarchy-text "Download complete")" "$title" \ omarchy-notification-send -g 󰄬 "Download complete" "$title" \
-t 10000 --image "${preview:-$filepath}" \ -t 10000 --image "${preview:-$filepath}" \
--exec mpv -- "$filepath" || true --exec mpv -- "$filepath" || true
@@ -160,7 +160,7 @@ download_url() {
rm -f "$preview" rm -f "$preview"
) & ) &
else else
omarchy-notification-send -u critical -g 󰅖 "$(omarchy-text "Download failed")" "$url" omarchy-notification-send -u critical -g 󰅖 "Download failed" "$url"
fi fi
exit 0 exit 0
+2 -2
View File
@@ -40,8 +40,8 @@ announce() {
omarchy-notification-send \ omarchy-notification-send \
--urgency critical \ --urgency critical \
--glyph "$CRASH_GLYPH" \ --glyph "$CRASH_GLYPH" \
"$(omarchy-text "Process crashed: %1" "$comm")" \ "Process crashed: $comm" \
"$(omarchy-text "Click to diagnose with AI")" \ "Click to diagnose with AI" \
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal" --exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
} }
+1 -1
View File
@@ -48,4 +48,4 @@ fi
env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1 env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default browser" "$name")" omarchy-notification-send -g $glyph "$name is now the default browser"
-37
View File
@@ -1,37 +0,0 @@
#!/bin/bash
# omarchy:summary=Get or set the default dictation backend
# omarchy:args=[backend]
# omarchy:examples=omarchy default dictation | omarchy default dictation superwhisper
if (($# > 1)); then
echo "Usage: omarchy default dictation [backend]" >&2
exit 1
fi
if (($# == 0)); then
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ ! -s $config ]]; then
echo "No dictation backend configured. Select one in Setup > Defaults > Dictation." >&2
exit 1
fi
read -r backend < "$config"
else
backend=$1
fi
if [[ ! $backend =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Invalid dictation backend: $backend" >&2
exit 1
fi
if (($# == 0)); then
echo "$backend"
else
installer="omarchy-install-dictation-$backend"
if omarchy-cmd-missing "$installer"; then
echo "No installer available for dictation backend: $backend" >&2
exit 1
fi
omarchy-launch-floating-terminal-with-presentation "$installer"
fi
+1 -1
View File
@@ -59,4 +59,4 @@ fi
mkdir -p "$(dirname "$editor_file")" mkdir -p "$(dirname "$editor_file")"
printf '%s\n' "$editor" >"$editor_file" printf '%s\n' "$editor" >"$editor_file"
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default editor" "$name")" omarchy-notification-send -g $glyph "$name is now the default editor"
+1 -1
View File
@@ -48,4 +48,4 @@ cat >~/.config/xdg-terminals.list <<EOF
$desktop_id $desktop_id
EOF EOF
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default terminal" "$name")" omarchy-notification-send -g $glyph "$name is now the default terminal"
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
# omarchy:summary=Record dictation with the configured backend
# omarchy:args=<start|stop|toggle>
# omarchy:examples=omarchy dictation start | omarchy dictation toggle
if (($# != 1)) || [[ $1 != "start" && $1 != "stop" && $1 != "toggle" ]]; then
echo "Usage: omarchy dictation <start|stop|toggle>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
backend=$(omarchy-default-dictation) || exit 1
adapter="omarchy-dictation-$backend"
if omarchy-cmd-missing "$adapter"; then
echo "The selected dictation backend ($backend) is not installed." >&2
exit 1
fi
"$adapter" "$1" 9>&-
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
# omarchy:summary=Record dictation using Superwhisper
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start) superwhisper start ;;
stop)
if omarchy-cmd-missing superwhisper || ! systemctl --user is-active --quiet superwhisper.service; then
exit 0
else
superwhisper stop
fi
;;
toggle) superwhisper record ;;
*) echo "Usage: omarchy dictation superwhisper <start|stop|toggle>" >&2; exit 1 ;;
esac
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Save a configured dictation backend after stopping the previous backend
# omarchy:args=<backend>
set -e
if (($# != 1)) || [[ ! $1 =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "Usage: omarchy-dictation-use <backend>" >&2
exit 1
fi
config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
mkdir -p "${config%/*}"
exec 9> "$config.lock"
if ! flock -w 5 9; then
echo "Dictation is busy; try again." >&2
exit 1
fi
previous=$(omarchy-default-dictation 2>/dev/null) || previous=""
if [[ -n $previous && $previous != "$1" ]] && omarchy-cmd-present "omarchy-dictation-$previous"; then
if ! "omarchy-dictation-$previous" stop 9>&-; then
echo "Could not stop $previous; keeping the existing dictation backend." >&2
exit 1
fi
fi
temporary=$(mktemp "$config.XXXXXX")
trap 'rm -f "$temporary"' EXIT
printf '%s\n' "$1" > "$temporary"
mv -f "$temporary" "$config"
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
# omarchy:summary=Record dictation using Voxtype
# omarchy:args=<start|stop|toggle>
case "${1:-}" in
start|toggle) voxtype record "$1" ;;
stop)
if omarchy-cmd-missing voxtype || [[ $(voxtype status) == "stopped" ]]; then
exit 0
else
voxtype record stop
fi
;;
*) echo "Usage: omarchy dictation voxtype <start|stop|toggle>" >&2; exit 1 ;;
esac
+1 -1
View File
@@ -169,7 +169,7 @@ set_terminal_size() {
[[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id") [[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id")
local new_id local new_id
new_id="$(omarchy-notification-send \ new_id="$(omarchy-notification-send \
"$(omarchy-text "Restart Foot to apply the new terminal font size")" \ "Restart Foot to apply the new terminal font size" \
"${replace[@]}" -p 2>/dev/null)" || true "${replace[@]}" -p 2>/dev/null)" || true
[[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file" [[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file"
fi fi
+2 -2
View File
@@ -74,11 +74,11 @@ XML
omarchy-restart-shell omarchy-restart-shell
if pgrep -x ghostty; then if pgrep -x ghostty; then
omarchy-notification-send -g "$(omarchy-text "You must restart Ghostty to see font change")" omarchy-notification-send -g "You must restart Ghostty to see font change"
fi fi
if pgrep -x foot; then if pgrep -x foot; then
omarchy-notification-send -g "$(omarchy-text "You must restart Foot to see font change")" omarchy-notification-send -g "You must restart Foot to see font change"
fi fi
omarchy-hook font-set "$font_name" omarchy-hook font-set "$font_name"
+2 -2
View File
@@ -10,7 +10,7 @@ if (( $# == 0 )); then
mapfile -t cores < <(omarchy-games-retro-cores) mapfile -t cores < <(omarchy-games-retro-cores)
if (( ${#cores[@]} == 0 )); then if (( ${#cores[@]} == 0 )); then
omarchy-notification-send -g 󰯉 "$(omarchy-text "No RetroArch cores found")" "/usr/lib/libretro" omarchy-notification-send -g 󰯉 "No RetroArch cores found" "/usr/lib/libretro"
exit 1 exit 1
fi fi
@@ -69,4 +69,4 @@ EOF
chmod +x "$desktop_file" chmod +x "$desktop_file"
update-desktop-database "$desktop_dir" &>/dev/null || true update-desktop-database "$desktop_dir" &>/dev/null || true
omarchy-notification-send -g 󰯉 "$(omarchy-text "%1 installed" "$game_name")" "$(omarchy-text "Start it with Super + Space")" omarchy-notification-send -g 󰯉 "$game_name installed" "Start it with Super + Space"
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# omarchy:summary=Match the Dell XPS 13 DX13260 with an Intel Panther Lake GPU.
omarchy-hw-match "DX13260" && omarchy-hw-intel-ptl
+5 -5
View File
@@ -16,7 +16,7 @@ wake() {
on() { on() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "$(omarchy-text "Laptop display enabled")" omarchy-notification-send -g 󰍹 "Laptop display enabled"
fi fi
wake wake
@@ -24,25 +24,25 @@ on() {
off() { off() {
if [[ -z $INTERNAL ]]; then if [[ -z $INTERNAL ]]; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "No laptop display found")" omarchy-notification-send -g 󰍹 "No laptop display found"
exit 1 exit 1
fi fi
# The name is written into generated Lua below, so only a plain connector # The name is written into generated Lua below, so only a plain connector
# name may pass; anything else could execute on the next reload. # name may pass; anything else could execute on the next reload.
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "Refusing unsafe monitor name")" omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name"
exit 1 exit 1
fi fi
if ! omarchy-hyprland-monitor-external-active; then if ! omarchy-hyprland-monitor-external-active; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "Can't disable the only active display")" omarchy-notification-send -g 󰍹 "Can't disable the only active display"
exit 1 exit 1
fi fi
if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then
printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG" printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG"
omarchy-notification-send -g 󰍹 "$(omarchy-text "Laptop display disabled")" omarchy-notification-send -g 󰍹 "Laptop display disabled"
hyprctl reload hyprctl reload
fi fi
} }
+5 -5
View File
@@ -13,12 +13,12 @@ EXTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | test("^(eDP|LVDS|DS
on() { on() {
if [[ -z $EXTERNAL ]]; then if [[ -z $EXTERNAL ]]; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "No external monitors found for mirror")" omarchy-notification-send -g 󰍹 "No external monitors found for mirror"
exit 1 exit 1
fi fi
if [[ -z $INTERNAL ]]; then if [[ -z $INTERNAL ]]; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "No laptop monitor found to mirror")" omarchy-notification-send -g 󰍹 "No laptop monitor found to mirror"
exit 1 exit 1
fi fi
@@ -26,7 +26,7 @@ on() {
# names may pass; a user-created headless output can carry any name. # names may pass; a user-created headless output can carry any name.
for output in "$INTERNAL" "$EXTERNAL"; do for output in "$INTERNAL" "$EXTERNAL"; do
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
omarchy-notification-send -g 󰍹 "$(omarchy-text "Refusing unsafe monitor name")" omarchy-notification-send -g 󰍹 "Refusing unsafe monitor name"
exit 1 exit 1
fi fi
done done
@@ -35,7 +35,7 @@ on() {
if omarchy-hyprland-toggle-disabled $TOGGLE; then 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" printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG"
omarchy-notification-send -g 󰍹 "$(omarchy-text "Mirroring enabled (%1)" "$EXTERNAL")" omarchy-notification-send -g 󰍹 "Mirroring enabled ($EXTERNAL)"
hyprctl reload hyprctl reload
fi fi
} }
@@ -43,7 +43,7 @@ on() {
off() { off() {
if omarchy-hyprland-toggle-enabled $TOGGLE; then if omarchy-hyprland-toggle-enabled $TOGGLE; then
omarchy-hyprland-toggle $TOGGLE off omarchy-hyprland-toggle $TOGGLE off
omarchy-notification-send -g 󰍹 "$(omarchy-text "Extended mode restored")" omarchy-notification-send -g 󰍹 "Extended mode restored"
fi fi
} }
@@ -3,6 +3,6 @@
# omarchy:summary=Toggle single-window square aspect ratio. # omarchy:summary=Toggle single-window square aspect ratio.
case $(omarchy-hyprland-toggle single-window-aspect-ratio) in case $(omarchy-hyprland-toggle single-window-aspect-ratio) in
on) omarchy-notification-send -g  "$(omarchy-text "Enable single-window square aspect ratio")" ;; on) omarchy-notification-send -g  "Enable single-window square aspect ratio" ;;
off) omarchy-notification-send -g  "$(omarchy-text "Disable single-window square aspect ratio")" ;; off) omarchy-notification-send -g  "Disable single-window square aspect ratio" ;;
esac esac
+2 -2
View File
@@ -40,14 +40,14 @@ notify_missing_width() {
local key="$1" local key="$1"
local workspace="$2" local workspace="$2"
omarchy-notification-send -g  "$(omarchy-text "No saved width found for %1 on workspace %2" "$key" "$workspace")" "Use Super + Alt + Home to save one for this workspace." 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() { notify_saved_width() {
local key="$1" local key="$1"
local workspace="$2" local workspace="$2"
omarchy-notification-send -g  "$(omarchy-text "Saved width for %1 on workspace %2" "$key" "$workspace")" "Restore using Super + Home on this workspace." omarchy-notification-send -g  "Saved width for $key on workspace $workspace" "Restore using Super + Home on this workspace."
} }
hypr_dispatch() { hypr_dispatch() {
+1 -1
View File
@@ -18,4 +18,4 @@ printf 'hl.workspace_rule({ workspace = "%s", layout = "%s" })\n' "$ACTIVE_WORKS
hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \ hyprctl eval "hl.workspace_rule({ workspace = \"$ACTIVE_WORKSPACE\", layout = \"$NEW_LAYOUT\" })" >/dev/null 2>&1 || \
hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT" hyprctl keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
omarchy-notification-send -g 󱂬 "$(omarchy-text "Workspace layout set to %1" "$NEW_LAYOUT")" omarchy-notification-send -g 󱂬 "Workspace layout set to $NEW_LAYOUT"
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
# omarchy:summary=Configure or inspect the input method selected at installation
# omarchy:args=<status|configure [--seed|--defaults] [none|mozc|hangul|pinyin|chewing]>
# omarchy:hidden=true
set -euo pipefail
python "$OMARCHY_PATH/default/input-methods/configure.py" "$@"
@@ -1,27 +0,0 @@
#!/bin/bash
# omarchy:summary=Install and configure Superwhisper dictation
# omarchy:requires-sudo=true
set -e
omarchy-pkg-add superwhisper-bin
bash /usr/share/superwhisper/setup-user
# Existing profiles may still use Right Alt, which conflicts with Omarchy.
# Keep a recording shortcut active while moving away from that binding.
shortcuts=$(superwhisper shortcuts show)
if [[ $(jq -r '.shortcuts.hold' <<< "$shortcuts") == "RightAlt" ]]; then
if [[ -z $(jq -r '.shortcuts.toggle' <<< "$shortcuts") ]]; then
superwhisper shortcuts set hold "RightSuper" >/dev/null
else
superwhisper shortcuts set hold none >/dev/null
fi
fi
# Superwhisper requires a native recording shortcut; keep it separate from
# Omarchy's push-to-talk keys.
superwhisper shortcuts set toggle "Alt+Space" >/dev/null
superwhisper shortcuts set hold none >/dev/null
omarchy-dictation-use superwhisper
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Superwhisper Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
superwhisper settings
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
if omarchy-cmd-missing voxtype; then
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
else
exit 0
fi
fi
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
mkdir -p "$config_home/voxtype"
if [[ ! -e $config_home/voxtype/config.toml ]]; then
cp "$OMARCHY_PATH/default/voxtype/config.toml" "$config_home/voxtype/"
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
fi
# Setup reuses the configured model and downloads it only when missing.
# Retry these steps even when an earlier attempt already installed the binary.
voxtype setup --download --no-post-install
voxtype setup systemd
systemctl --user enable --now voxtype.service
omarchy-dictation-use voxtype
hyprctl reload >/dev/null
omarchy-notification-send -g  "$(omarchy-text "Voxtype Dictation Ready")" "$(omarchy-text "Hold Right Alt or F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
-46
View File
@@ -1,46 +0,0 @@
#!/bin/bash
# omarchy:summary=Show or switch the system language between English and Simplified Chinese
# omarchy:args=[en|zh]
# omarchy:examples=omarchy language | omarchy language zh
# omarchy:requires-sudo=true
set -euo pipefail
locale_conf=/etc/locale.conf
if (($# == 0)); then
lang=$(sed -n 's/^LANG=//p' "$locale_conf")
# locale.conf may quote the value.
lang=${lang#[\"\']}
printf '%s\n' "${lang%[\"\']}"
exit 0
fi
case ${1:-} in
en) lang=en_US.UTF-8 ;;
zh) lang=zh_CN.UTF-8 ;;
*)
echo "Usage: omarchy-language [en|zh]" >&2
exit 1
;;
esac
sudo sed -i "s/^#\(${lang/./\\.} UTF-8\)/\1/" /etc/locale.gen
sudo locale-gen
if grep -q '^LANG=' "$locale_conf"; then
sudo sed -i "s/^LANG=.*/LANG=$lang/" "$locale_conf"
else
sudo sed -i '$a\' "$locale_conf"
echo "LANG=$lang" | sudo tee -a "$locale_conf" >/dev/null
fi
# The language reaches only a new session, so offer one in the new language.
prompt=$(LANG=$lang omarchy-text "Log out now to switch the language?")
log_out=$(LANG=$lang omarchy-text "Log out now")
later=$(LANG=$lang omarchy-text "Later")
if [[ -t 0 && -t 1 ]] && gum confirm --affirmative "$log_out" --negative "$later" "$prompt" 2>&1; then
omarchy-system-logout
else
LANG=$lang omarchy-text "Log back in to switch the language."
fi
+1 -1
View File
@@ -11,5 +11,5 @@ if [[ -z $path ]]; then
exit 1 exit 1
fi fi
omarchy-notification-send -u low "$(omarchy-text "Editing config file")" "$path" omarchy-notification-send -u low "Editing config file" "$path"
exec omarchy-launch-editor "$path" exec omarchy-launch-editor "$path"
+1 -1
View File
@@ -16,7 +16,7 @@ terminal=$(xdg-terminal-exec --print-id)
case $terminal in case $terminal in
*Alacritty* | *ghostty* | *foot* | *kitty*) ;; *Alacritty* | *ghostty* | *foot* | *kitty*) ;;
*) *)
omarchy-notification-send -g ✋ "$(omarchy-text "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty")" omarchy-notification-send -g ✋ "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty"
exit 1 exit 1
;; ;;
esac esac
+1 -3
View File
@@ -15,9 +15,7 @@
# against a half-written tree, and that failed reload leaves a second engine # against a half-written tree, and that failed reload leaves a second engine
# generation behind that turns the next restart's IPC kill into a crash. # generation behind that turns the next restart's IPC kill into a crash.
run_shell() { run_shell() {
local startup_background QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
startup_background=$(readlink -f "$HOME/.local/state/omarchy/current/background" 2>/dev/null)
OMARCHY_STARTUP_BACKGROUND="$startup_background" QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" & systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" &
shell_pid=$! shell_pid=$!
+1 -2
View File
@@ -332,7 +332,6 @@ dynamic_bindings() {
static_bindings() { static_bindings() {
echo "SHIFT ALT,L,Copy URL from Web App,sendshortcut,SHIFT ALT,L," echo "SHIFT ALT,L,Copy URL from Web App,sendshortcut,SHIFT ALT,L,"
echo "SHIFT ALT,D,Download Video from Web App,sendshortcut,SHIFT ALT,D," echo "SHIFT ALT,D,Download Video from Web App,sendshortcut,SHIFT ALT,D,"
echo "CTRL SHIFT,SPACE,Switch input language,exec,fcitx5-remote -t"
} }
# Actions Omarchy binds to a second chord meant as an alternative, named one at # Actions Omarchy binds to a second chord meant as an alternative, named one at
@@ -534,7 +533,7 @@ output_binding_records_uncached() {
keybindings_cache_key() { keybindings_cache_key() {
{ {
printf 'v15\n' printf 'v14\n'
hyprctl devices 2>/dev/null | grep -F 'active keymap:' hyprctl devices 2>/dev/null | grep -F 'active keymap:'
hyprctl binds 2>/dev/null hyprctl binds 2>/dev/null
} | sha256sum | awk '{ print $1 }' } | sha256sum | awk '{ print $1 }'
+1 -1
View File
@@ -28,7 +28,7 @@ rows=$(jq -r --arg icon "$PLUGIN_ICON" \
". as \$plugins ". as \$plugins
| .[] | select($filter) | .[] | select($filter)
| \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins") | \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins")
[[ -n $rows ]] || { omarchy-notification-send "$(omarchy-text "No plugin to $1")"; exit 0; } [[ -n $rows ]] || { omarchy-notification-send "No plugin to ${1}"; exit 0; }
selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0 selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0
[[ -n $selection ]] || exit 0 [[ -n $selection ]] || exit 0
+1 -1
View File
@@ -33,7 +33,7 @@ else
picked=$(omarchy-file-select "${select_args[@]}") || status=$? picked=$(omarchy-file-select "${select_args[@]}") || status=$?
if ((${status:-0} > 1)); then if ((${status:-0} > 1)); then
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not share")" "$(omarchy-text "The file chooser did not open")" omarchy-notification-send -g "" -u critical "Could not share" "The file chooser did not open"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -9,4 +9,4 @@ set -e
timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1 timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1
sudo timedatectl set-timezone "$timezone" sudo timedatectl set-timezone "$timezone"
omarchy-shell -q omarchy.clock refresh omarchy-shell -q omarchy.clock refresh
omarchy-notification-send "$(omarchy-text "Timezone is now set to %1" "$timezone")" omarchy-notification-send "Timezone is now set to $timezone"
+3 -3
View File
@@ -21,9 +21,9 @@ pending_migrations=$(omarchy-migrate --pending 2>/dev/null) || exit 0
pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l) pending_count=$(printf '%s\n' "$pending_migrations" | sed '/^[[:space:]]*$/d' | wc -l)
if (( pending_count == 1 )); then if (( pending_count == 1 )); then
message=$(omarchy-text "Click to run 1 pending migration.") message="Click to run 1 pending migration."
else else
message=$(omarchy-text "Click to run %1 pending migrations." "$pending_count") message="Click to run $pending_count pending migrations."
fi fi
# This runs from omarchy-migrate-notify.service after graphical-session.target, # This runs from omarchy-migrate-notify.service after graphical-session.target,
@@ -40,7 +40,7 @@ fi
# The shell keeps the click command with the toast, so this oneshot can hand the # The shell keeps the click command with the toast, so this oneshot can hand the
# invitation over and exit instead of staying activated until it is answered. # invitation over and exit instead of staying activated until it is answered.
omarchy-notification-send -u critical -g  "$(omarchy-text "Pending Omarchy Migrations")" "$message" \ omarchy-notification-send -u critical -g  "Pending Omarchy Migrations" "$message" \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0 --exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0
# Reached when the notification could not be handed off, so fall back to telling # Reached when the notification could not be handed off, so fall back to telling
+1 -1
View File
@@ -2,4 +2,4 @@
# omarchy:summary=Show the current time and date notification # omarchy:summary=Show the current time and date notification
omarchy-notification-send -g  -u low "$(date +"%A %H:%M · %d %B %Y") · $(omarchy-text "Week %1" "$(date +%V)")" omarchy-notification-send -g  -u low "$(date +"%A %H:%M · %d %B %Y · Week %V")"
+2 -2
View File
@@ -159,8 +159,8 @@ done
omarchy-plugin-enable "$new_id" >/dev/null omarchy-plugin-enable "$new_id" >/dev/null
clone_complete=1 clone_complete=1
omarchy-notification-send -g 󰐱 \ omarchy-notification-send -g 󰐱 \
"$(omarchy-text "Editing Cloned Plugin")" \ "Editing Cloned Plugin" \
"$(omarchy-text "Original plugin has been replace by clone.")" "Original plugin has been replace by clone."
echo "Cloned $source_id to $target_dir and switched to $new_id" echo "Cloned $source_id to $target_dir and switched to $new_id"
if (( edit_clone )); then if (( edit_clone )); then
# Word-splitting is deliberate: EDITOR may carry flags. # Word-splitting is deliberate: EDITOR may carry flags.
+2
View File
@@ -68,6 +68,8 @@ run_first_run_step() {
fi fi
} }
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 "install fingerprint setup post-update hook" \ run_first_run_step "install fingerprint setup post-update hook" \
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook" omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook"
run_first_run_step "install agent setup post-update hook" \ run_first_run_step "install agent setup post-update hook" \
-1
View File
@@ -599,7 +599,6 @@ keyboard_form() {
done done
apply_keyboard "$keyboard" apply_keyboard "$keyboard"
omarchy_persist_input_selection
} }
confirm_reboot() { confirm_reboot() {
-57
View File
@@ -1,57 +0,0 @@
#!/bin/bash
# omarchy:summary=Show or switch the region this machine is set up for
# omarchy:args=[global|cn]
# omarchy:examples=omarchy region | omarchy region cn
# omarchy:requires-sudo=true
set -euo pipefail
region_file=/etc/omarchy/region
if (($# == 0)); then
if [[ ! -e $region_file ]]; then
echo "Global"
elif [[ $(<"$region_file") == "cn" ]]; then
echo "China"
else
echo "Unknown region in $region_file: $(<"$region_file")" >&2
exit 1
fi
exit 0
fi
if [[ $1 != "global" && $1 != "cn" ]]; then
echo "Usage: omarchy-region [global|cn]" >&2
exit 1
fi
channel=$(omarchy-version-channel)
if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then
echo "Cannot tell this machine's package channel ($channel); run omarchy channel set first" >&2
exit 1
fi
if [[ $1 == "cn" ]]; then
# Upgrade first: the keyring install below syncs new databases.
omarchy-update-pacman -Syu --noconfirm
sudo env OMARCHY_REGION=cn OMARCHY_PATH="$OMARCHY_PATH" bash -e "$OMARCHY_PATH/install/config/region.sh"
# Trust ArchLinuxCN before the refresh upgrades through it.
rendered=$(mktemp -d)
trap 'rm -rf "$rendered"' EXIT
omarchy-apply-pacman --render "$channel" "$rendered"
mapfile -t profile_packages < <(grep -Ev '^[[:space:]]*(#|$)' "$OMARCHY_PATH/default/regions/cn/packages")
sudo pacman --config "$rendered/pacman.conf" -Sy --noconfirm --needed "${profile_packages[@]}"
sudo pacman-key --populate archlinuxcn
omarchy-refresh-pacman "$channel"
else
sudo env OMARCHY_REGION=global OMARCHY_PATH="$OMARCHY_PATH" bash -e "$OMARCHY_PATH/install/config/region.sh"
omarchy-refresh-pacman "$channel"
fi
# The developer mirrors reach only a new session, so offer one.
if [[ -t 0 && -t 1 ]] && gum confirm --affirmative "$(omarchy-text "Log out now")" --negative "$(omarchy-text "Later")" "$(omarchy-text "Log out now to switch the developer mirrors?")" 2>&1; then
omarchy-system-logout
else
omarchy-text "Log back in to switch the developer mirrors."
fi
+14 -15
View File
@@ -57,16 +57,16 @@ show_reminders() {
[[ -f $reminder_dir/${timer%.timer}.message ]] && reminder_message=$(<"$reminder_dir/${timer%.timer}.message") [[ -f $reminder_dir/${timer%.timer}.message ]] && reminder_message=$(<"$reminder_dir/${timer%.timer}.message")
if [[ -n $reminder_message ]]; then if [[ -n $reminder_message ]]; then
body+="$(omarchy-text "%1 in %2 (%3)" "$reminder_message" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n' body+="$reminder_message in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
else else
body+="$(omarchy-text "%1-min reminder in %2 (%3)" "$reminder_minutes" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n' body+="${reminder_minutes}-min reminder in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
fi fi
done < <(active_reminder_timers "$now") done < <(active_reminder_timers "$now")
if [[ -z $body ]]; then if [[ -z $body ]]; then
omarchy-notification-send -g 󰢌 "$(omarchy-text "Upcoming reminders")" "$(omarchy-text "No outstanding reminders")" omarchy-notification-send -g 󰢌 "Upcoming reminders" "No outstanding reminders"
else else
omarchy-notification-send -g 󰢌 "$(omarchy-text "Upcoming reminders")" "${body%$'\n'}" omarchy-notification-send -g 󰢌 "Upcoming reminders" "${body%$'\n'}"
fi fi
} }
@@ -75,8 +75,7 @@ show_json() {
local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders" local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
local now=$(date +%s) local now=$(date +%s)
local count=0 local count=0
local tooltip local tooltip="Set Reminder"
tooltip=$(omarchy-text "Set Reminder")
while IFS=$'\t' read -r timer next; do while IFS=$'\t' read -r timer next; do
count=$((count + 1)) count=$((count + 1))
@@ -92,7 +91,7 @@ show_json() {
if [[ -n $reminder_message ]]; then if [[ -n $reminder_message ]]; then
label=$reminder_message label=$reminder_message
else else
label=$(omarchy-text "%1-min reminder" "$reminder_minutes") label="${reminder_minutes}-min reminder"
fi fi
item_json=$(jq -cn \ item_json=$(jq -cn \
@@ -110,9 +109,9 @@ show_json() {
done < <(active_reminder_timers "$now") done < <(active_reminder_timers "$now")
if ((count == 1)); then if ((count == 1)); then
tooltip=$(omarchy-text "1 reminder") tooltip="1 reminder"
elif ((count > 1)); then elif ((count > 1)); then
tooltip=$(omarchy-text "%1 reminders" "$count") tooltip="$count reminders"
fi fi
jq -cn --argjson count "$count" --arg tooltip "$tooltip" --argjson reminders "$reminders_json" '{count:$count,active:($count > 0),tooltip:$tooltip,reminders:$reminders}' jq -cn --argjson count "$count" --arg tooltip "$tooltip" --argjson reminders "$reminders_json" '{count:$count,active:($count > 0),tooltip:$tooltip,reminders:$reminders}'
@@ -130,7 +129,7 @@ clear_reminders() {
rm -f "$reminder_dir"/omarchy-reminder-*.message 2>/dev/null || true rm -f "$reminder_dir"/omarchy-reminder-*.message 2>/dev/null || true
refresh_indicator refresh_indicator
omarchy-notification-send -g 󰢌 "$(omarchy-text "All reminders have been cleared")" omarchy-notification-send -g 󰢌 "All reminders have been cleared"
} }
usage() { usage() {
@@ -177,7 +176,7 @@ if [[ -z $minutes ]] || [[ ! $minutes =~ ^[0-9]+$ ]] || ((minutes == 0)); then
fi fi
if [[ -z $message ]]; then if [[ -z $message ]]; then
message=$(omarchy-text "Your %1 minutes are up" "$minutes") message="Your ${minutes} minutes are up"
fi fi
set_at=$(date +%s) set_at=$(date +%s)
@@ -185,18 +184,18 @@ remind_at=$(date -d "+${minutes} minutes" +%H:%M)
unit="omarchy-reminder-${minutes}m-$set_at" unit="omarchy-reminder-${minutes}m-$set_at"
reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders" reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
message_file="$reminder_dir/$unit.message" message_file="$reminder_dir/$unit.message"
confirmation=$(omarchy-text "You'll be reminded at %1" "$remind_at") confirmation="You'll be reminded at $remind_at"
confirmation_title=$(omarchy-text "Reminder set for %1 minutes" "$minutes") confirmation_title="Reminder set for ${minutes} minutes"
mkdir -p "$reminder_dir" mkdir -p "$reminder_dir"
if [[ -n $custom_message ]]; then if [[ -n $custom_message ]]; then
printf "%s" "$custom_message" >"$message_file" printf "%s" "$custom_message" >"$message_file"
confirmation_title=$(omarchy-text "%1 in %2 minutes" "$custom_message" "$minutes") confirmation_title="$custom_message in ${minutes} minutes"
fi fi
systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \ systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \
bash -c 'omarchy-notification-send -g 󰢌 "$(omarchy-text "Reminder")" "$1"; rm -f "$2"; omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true' bash "$message" "$message_file" bash -c 'omarchy-notification-send -g 󰢌 "Reminder" "$1"; rm -f "$2"; omarchy-shell -q omarchy.indicators refresh >/dev/null 2>&1 || true' bash "$message" "$message_file"
refresh_indicator refresh_indicator
omarchy-notification-send -g 󰢌 "$confirmation_title" "$confirmation" omarchy-notification-send -g 󰢌 "$confirmation_title" "$confirmation"
-40
View File
@@ -1,40 +0,0 @@
#!/bin/bash
# omarchy:summary=Remove Superwhisper dictation and its desktop integration
# omarchy:requires-sudo=true
set -e
config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
data_home="${XDG_DATA_HOME:-$HOME/.local/share}"
systemctl --user disable --now superwhisper.service 2>/dev/null || true
omarchy-shell -q shell setPluginEnabled superwhisper.panel false >/dev/null
omarchy-pkg-drop superwhisper-bin
systemctl --user daemon-reload
# Remove the package setup's links, preserving independently installed copies
# and the user's preferences, history, and models.
unlink_package_link() {
if [[ -L $1 && $(readlink "$1") == "$2" ]]; then
rm "$1"
fi
}
unlink_package_link "$HOME/.local/bin/superwhisper" /usr/bin/superwhisper
unlink_package_link "$data_home/superwhisper/app/current" /opt/superwhisper
unlink_package_link "$config_home/omarchy/plugins/superwhisper-panel" /opt/superwhisper/assets/omarchy-plugin/superwhisper-panel
for agent in .agents .claude .codex .pi/agent; do
unlink_package_link "$HOME/$agent/skills/superwhisper" /opt/superwhisper/assets/agent-skill/superwhisper
done
backend_config="$config_home/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "superwhisper" ]]; then
rm "$backend_config"
fi
omarchy-shell -q shell rescanPlugins
hyprctl reload >/dev/null
if systemctl --user -q is-active omarchy-fcitx5.service; then
systemctl --user restart omarchy-fcitx5.service
fi
+1 -22
View File
@@ -2,30 +2,9 @@
# omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings. # omarchy:summary=Restart the XCompose input method service (fcitx5) to apply new compose key settings.
set -euo pipefail
# A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's # A fcitx5 running outside the unit owns org.fcitx.Fcitx5, so the unit's
# instance would exit on arrival and the stale process would keep serving the # instance would exit on arrival and the stale process would keep serving the
# old ~/.XCompose -- a restart that reports success and changes nothing. # old ~/.XCompose -- a restart that reports success and changes nothing.
systemctl --user stop omarchy-fcitx5.service systemctl --user stop omarchy-fcitx5.service
pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true pkill -x fcitx5 >/dev/null 2>&1 || true
for attempt in {1..50}; do
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then
break
fi
sleep 0.1
done
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
# An activation concurrent with stop may have restarted the unit.
systemctl --user stop omarchy-fcitx5.service
pkill -u "$UID" -x fcitx5 >/dev/null 2>&1 || true
for attempt in {1..50}; do
if ! pgrep -u "$UID" -x fcitx5 >/dev/null; then break; fi
sleep 0.1
done
fi
if pgrep -u "$UID" -x fcitx5 >/dev/null; then
echo "Fcitx did not stop; input service was not restarted." >&2
exit 1
fi
systemctl --user start omarchy-fcitx5.service systemctl --user start omarchy-fcitx5.service
-30
View File
@@ -1,30 +0,0 @@
#!/bin/bash
# omarchy:summary=Set up Japanese, Korean, or Chinese typing
# omarchy:args=[mozc|hangul|pinyin|chewing]
# omarchy:examples=omarchy setup input | omarchy setup input mozc
# omarchy:requires-sudo=true
set -euo pipefail
if (( $# > 1 )); then
echo "Usage: omarchy setup input [mozc|hangul|pinyin|chewing]" >&2
exit 1
fi
method=${1:-}
if [[ -z $method ]]; then
choice=$(jq -r 'to_entries[] | select(.key != "none") | .value.label' "$OMARCHY_PATH/default/input-methods/presets.json" |
gum choose --header "Select input language")
method=$(jq -r --arg label "$choice" 'to_entries[] | select(.value.label == $label) | .key' "$OMARCHY_PATH/default/input-methods/presets.json")
fi
case "$method" in
mozc | hangul | pinyin | chewing) ;;
*) echo "Unknown input method: $method" >&2; exit 1 ;;
esac
mapfile -t packages < <(jq -r --arg method "$method" '.[$method].packages[]' "$OMARCHY_PATH/default/input-methods/presets.json")
omarchy-pkg-add "${packages[@]}"
omarchy-input-method configure "$method"
echo "Input method added. The default switch is Ctrl+Shift+Space or the Japanese/Korean input key; existing custom keys are preserved."
+47 -194
View File
@@ -1,7 +1,7 @@
#!/bin/bash -p #!/bin/bash -p
# omarchy:summary=Toggle passwordless sudo for the current user. # omarchy:summary=Toggle passwordless sudo for the current user.
# omarchy:args=[MINUTES|permanent|--active|--disable] # omarchy:args=[MINUTES]
# omarchy:requires-sudo=true # omarchy:requires-sudo=true
if [[ $- != *p* && ${BASH_SOURCE[0]} == "$0" ]]; then if [[ $- != *p* && ${BASH_SOURCE[0]} == "$0" ]]; then
@@ -22,6 +22,7 @@ fi
set -euo pipefail set -euo pipefail
readonly DEFAULT_MINUTES=15
readonly MAX_MINUTES=1440 readonly MAX_MINUTES=1440
readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock
readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
@@ -33,7 +34,7 @@ readonly INSTALLED_SELF=/usr/bin/omarchy-sudo-passwordless
readonly STATUS_INACTIVE=3 readonly STATUS_INACTIVE=3
usage() { usage() {
echo "Usage: omarchy-sudo-passwordless [MINUTES|permanent|--active|--disable]" >&2 echo "Usage: omarchy-sudo-passwordless [MINUTES]" >&2
echo "MINUTES must be between 1 and $MAX_MINUTES." >&2 echo "MINUTES must be between 1 and $MAX_MINUTES." >&2
exit 1 exit 1
} }
@@ -42,10 +43,6 @@ valid_minutes() {
[[ $1 =~ ^0*[1-9][0-9]{0,3}$ ]] && ((10#$1 <= MAX_MINUTES)) [[ $1 =~ ^0*[1-9][0-9]{0,3}$ ]] && ((10#$1 <= MAX_MINUTES))
} }
valid_duration() {
[[ $1 == "permanent" ]] || valid_minutes "$1"
}
valid_uid() { valid_uid() {
[[ $1 =~ ^0*[1-9][0-9]{0,9}$ ]] && ((10#$1 <= 4294967294)) [[ $1 =~ ^0*[1-9][0-9]{0,9}$ ]] && ((10#$1 <= 4294967294))
} }
@@ -99,39 +96,14 @@ with_root_lock() {
} }
rule_file() { rule_file() {
local uid=$((10#$1)) printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$1"
if [[ ${2:-} == "permanent" ]]; then
printf '/etc/sudoers.d/99-omarchy-permanent-nopasswd-%s' "$uid"
else
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$uid"
fi
} }
# The sudoers rule is the only grant record. A missing file is distinct from # The sudoers rule is the only grant record. A missing file is distinct from
# an unreadable, unsafe, or administrator-modified file. # an unreadable, unsafe, or administrator-modified file.
read_grant() { read_grant() {
local file permanent contents local file contents
file=$(rule_file "$1") file=$(rule_file "$1")
permanent=$(rule_file "$1" permanent)
if [[ -e $permanent || -L $permanent ]]; then
verify_root_path "$permanent" && [[ -f $permanent ]] || return 2
contents=$(/usr/bin/cat -- "$permanent") || return 2
[[ $contents =~ ^([a-z_][a-z0-9_-]*\$?)\ ALL=\(ALL\)\ NOPASSWD:\ ALL$ ]] || return 2
GRANT_NAME=${BASH_REMATCH[1]}
GRANT_DEADLINE=""
valid_account_name "$GRANT_NAME" || return 2
if [[ -e $file || -L $file ]]; then
# A SIGKILL between publication and removal can leave both policies.
# Only accept a matching generated timed rule. Permanent access already
# exists, so old expiry callbacks must not revoke it; disable removes
# both files, and the next duration change finishes their replacement.
verify_root_path "$file" && [[ -f $file ]] || return 2
contents=$(/usr/bin/cat -- "$file") || return 2
[[ $contents =~ ^([a-z_][a-z0-9_-]*\$?)\ ALL=\(ALL\)\ NOTAFTER=([0-9]{14}Z)\ NOPASSWD:\ ALL$ ]] || return 2
[[ ${BASH_REMATCH[1]} == "$GRANT_NAME" ]] || return 2
fi
return 0
fi
[[ -e $file || -L $file ]] || return "$STATUS_INACTIVE" [[ -e $file || -L $file ]] || return "$STATUS_INACTIVE"
verify_root_path "$file" && [[ -f $file ]] || return 2 verify_root_path "$file" && [[ -f $file ]] || return 2
contents=$(/usr/bin/cat -- "$file") || return 2 contents=$(/usr/bin/cat -- "$file") || return 2
@@ -149,9 +121,6 @@ classify_generated_rule() {
[[ -f $file && ! -L $file ]] || return 1 [[ -f $file && ! -L $file ]] || return 1
contents=$(/usr/bin/cat -- "$file") || return 2 contents=$(/usr/bin/cat -- "$file") || return 2
suffix=${file##*/99-omarchy-nopasswd-} suffix=${file##*/99-omarchy-nopasswd-}
if [[ $file == */99-omarchy-permanent-nopasswd-* ]]; then
suffix=${file##*/99-omarchy-permanent-nopasswd-}
fi
# The legacy command wrote the caller's unvalidated name into both the # The legacy command wrote the caller's unvalidated name into both the
# filename and the rule. That exact relationship is its fingerprint, so an # filename and the rule. That exact relationship is its fingerprint, so an
@@ -169,21 +138,14 @@ classify_generated_rule() {
valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]] valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]]
} }
cleanup_rule_file() { cleanup_uid_locked() {
local file=$1 local file
file=$(rule_file "$1")
[[ -e $file || -L $file ]] || return 0 [[ -e $file || -L $file ]] || return 0
verify_root_path "$file" && classify_generated_rule "$file" || return 1 verify_root_path "$file" && classify_generated_rule "$file" || return 1
/usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]] /usr/bin/rm -f -- "$file" && [[ ! -e $file && ! -L $file ]]
} }
cleanup_uid_locked() {
local file duration
for duration in temporary permanent; do
file=$(rule_file "$1" "$duration")
cleanup_rule_file "$file" || return 1
done
}
# The generated prefix is reserved: boot cleanup and the package hook already # The generated prefix is reserved: boot cleanup and the package hook already
# remove everything in it, and the legacy writer could produce a rule whose # remove everything in it, and the legacy writer could produce a rule whose
# body differs from its filename. Nothing unrecognized may stay live there, but # body differs from its filename. Nothing unrecognized may stay live there, but
@@ -301,7 +263,6 @@ migrate_locked() {
expire_locked() { expire_locked() {
local status now local status now
if read_grant "$1"; then if read_grant "$1"; then
[[ -n $GRANT_DEADLINE ]] || return 0
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2 now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
[[ $now < $GRANT_DEADLINE ]] && return 0 [[ $now < $GRANT_DEADLINE ]] && return 0
cleanup_uid_locked "$1" cleanup_uid_locked "$1"
@@ -320,7 +281,6 @@ status_locked() {
resolve_account "$1" || return 2 resolve_account "$1" || return 2
if read_grant "$1"; then if read_grant "$1"; then
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2 [[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2
[[ -n $GRANT_DEADLINE ]] || return 0
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2 now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
if [[ $now < $GRANT_DEADLINE ]]; then if [[ $now < $GRANT_DEADLINE ]]; then
return 0 return 0
@@ -340,11 +300,7 @@ finish_enable() {
if cleanup_uid_locked "$uid"; then if cleanup_uid_locked "$uid"; then
[[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true [[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true
else else
if [[ -n $timer ]]; then echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2
echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2
else
echo "Could not revoke passwordless sudo. Administrator cleanup is required." >&2
fi
fi fi
fi fi
[[ -z $pending ]] || /usr/bin/rm -f -- "$pending" [[ -z $pending ]] || /usr/bin/rm -f -- "$pending"
@@ -353,15 +309,9 @@ finish_enable() {
enable_locked() ( enable_locked() (
local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status
resolve_account "$uid" && valid_duration "$minutes" || return 1 resolve_account "$uid" && valid_minutes "$minutes" || return 1
verify_root_path /etc/sudoers.d || return 1 verify_boot_cleanup && verify_root_path /etc/sudoers.d || return 1
if [[ $minutes != "permanent" ]]; then file=$(rule_file "$uid")
verify_boot_cleanup || {
echo "Temporary sudo cleanup is unavailable or a settings package transaction is incomplete. Check the omarchy-settings installation." >&2
return 1
}
fi
file=$(rule_file "$uid" "$minutes")
if read_grant "$uid"; then if read_grant "$uid"; then
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1 [[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1
else else
@@ -370,69 +320,39 @@ enable_locked() (
fi fi
trap finish_enable EXIT trap finish_enable EXIT
omarchy_security_install_signal_exit_traps omarchy_security_install_signal_exit_traps
now=$(/usr/bin/date +%s) || return 1
expires=$((now + 10#$minutes * 60))
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
pending=$(/usr/bin/mktemp /etc/sudoers.d/.omarchy-nopasswd.XXXXXX) || return 1 pending=$(/usr/bin/mktemp /etc/sudoers.d/.omarchy-nopasswd.XXXXXX) || return 1
if [[ $minutes == "permanent" ]]; then /usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1
/usr/bin/printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$ACCOUNT_NAME" >"$pending" || return 1
else
now=$(/usr/bin/date +%s) || return 1
expires=$((now + 10#$minutes * 60))
deadline=$(/usr/bin/date -u -d "@$expires" +%Y%m%d%H%M%SZ) || return 1
/usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1
fi
/usr/bin/chown root:root "$pending" && /usr/bin/chmod 0440 "$pending" || return 1 /usr/bin/chown root:root "$pending" && /usr/bin/chmod 0440 "$pending" || return 1
/usr/sbin/visudo -cf "$pending" >/dev/null || return 1 /usr/sbin/visudo -cf "$pending" >/dev/null || return 1
if [[ $minutes != "permanent" ]]; then token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1 [[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1 timer="omarchy-nopasswd-expire-$uid-$token"
timer="omarchy-nopasswd-expire-$uid-$token" /usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \
/usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \ --timer-property=AccuracySec=1s --unit="$timer" \
--timer-property=AccuracySec=1s --unit="$timer" \ -- "$INSTALLED_SELF" __expire "$uid" || return 1
-- "$INSTALLED_SELF" __expire "$uid" || return 1 /usr/bin/systemctl is-active --quiet "$timer.timer" || return 1
/usr/bin/systemctl is-active --quiet "$timer.timer" || return 1
fi
# The temporary filename contains a dot, so sudo ignores it. Rename within # The temporary filename contains a dot, so sudo ignores it. Rename within
# sudoers.d publishes the complete validated policy in one operation. # sudoers.d publishes the complete validated policy in one operation.
/usr/bin/mv -fT -- "$pending" "$file" || return 1 /usr/bin/mv -fT -- "$pending" "$file" || return 1
pending="" pending=""
# Keep access uninterrupted during renewal. When changing duration type, now=$(/usr/bin/date +%s) || return 1
# remove the other rule only after its replacement is published. The lock (( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer"
# keeps status and expiry callbacks from observing both rules together.
if [[ $minutes == "permanent" ]]; then
cleanup_rule_file "$(rule_file "$uid")" || return 1
else
cleanup_rule_file "$(rule_file "$uid" permanent)" || return 1
fi
if [[ $minutes != "permanent" ]]; then
now=$(/usr/bin/date +%s) || return 1
(( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer"
fi
) )
root_dispatch() { root_dispatch() {
local action="$1" status local action="$1"
shift shift
case "$action" in case "$action" in
__status) __status)
(($# == 1)) && verify_sudo_caller "$1" || return 2 (($# == 1)) && verify_sudo_caller "$1" || return 2
if with_root_lock status_locked "$1"; then with_root_lock status_locked "$1"
return 0
else
status=$?
# Reserve status 1 for sudo refusing noninteractive authentication.
# Every internal inspection failure, including lock failures, is 2.
(( status == STATUS_INACTIVE )) || status=2
return "$status"
fi
;; ;;
__enable) __enable)
(($# == 2)) && verify_sudo_caller "$1" && valid_duration "$2" || return 1 (($# == 2)) && verify_sudo_caller "$1" && valid_minutes "$2" || return 1
if with_root_lock enable_locked "$1" "$2"; then with_root_lock enable_locked "$1" "$2"
return 0
else
status=$?
echo "Could not enable passwordless sudo. Check existing sudoers policy and cleanup support." >&2
return "$status"
fi
;; ;;
__disable) __disable)
(($# == 1)) && verify_sudo_caller "$1" || return 1 (($# == 1)) && verify_sudo_caller "$1" || return 1
@@ -462,14 +382,6 @@ root_dispatch() {
esac esac
} }
show_enabled() {
if [[ $minutes == "permanent" ]]; then
echo "Passwordless sudo has been ENABLED permanently. Run this command again to disable it."
else
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes."
fi
}
case "${1:-}" in case "${1:-}" in
__status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete) __status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete)
action=$1 action=$1
@@ -479,30 +391,9 @@ case "${1:-}" in
;; ;;
esac esac
# Prefer matched policy tags to executing a privileged command, avoiding
# authentication/session logs under the default listing policy. Restrictive
# listpw settings require the root-status fallback. Neither path prompts or
# trusts cached credentials as evidence of passwordless access.
if [[ ${1:-} == "--active" ]]; then
(($# == 1)) || usage
uid=$(/usr/bin/id -u)
if policy=$(/usr/bin/sudo -n -N -l -l -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null); then
/usr/bin/grep -q '!authenticate' <<<"$policy"
else
# listpw=always can require authentication to list a passwordless grant.
# With a command, -k ignores cached credentials without invalidating them.
status=0
/usr/bin/sudo -kn -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null || status=$?
# An internal inspection error still proves that sudo ran the helper
# without authentication. Keep the warning visible for unsafe policy.
(( status == 0 || status == 2 )) || exit 1
fi
exit
fi
(($# <= 1)) || usage (($# <= 1)) || usage
minutes=${1:-} minutes=${1:-$DEFAULT_MINUTES}
(($# == 0)) || [[ $minutes == "--disable" ]] || valid_duration "$minutes" || usage valid_minutes "$minutes" || usage
uid=$(/usr/bin/id -u) uid=$(/usr/bin/id -u)
valid_uid "$uid" || { valid_uid "$uid" || {
echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2 echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2
@@ -520,76 +411,38 @@ omarchy_security_install_sudo_cleanup_traps
exit 1 exit 1
} }
# Inspect policy without authenticating. Only inspect the root-owned grant echo "Toggle passwordless sudo..."
# when policy already permits it without a password; otherwise the eventual if /usr/bin/sudo -N -- "$INSTALLED_SELF" __status "$uid"; then
# enable action is the sole authentication boundary. enable_locked validates if (($# == 0)); then
# any existing policy before publishing, including expired or modified rules. /usr/bin/sudo -N -- "$INSTALLED_SELF" __disable "$uid"
status=$STATUS_INACTIVE echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
if policy=$(/usr/bin/sudo -n -N -l -l -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null); then else
if /usr/bin/grep -q '!authenticate' <<<"$policy"; then /usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
status=0 echo "Passwordless sudo expiry updated. It will automatically disable in ${minutes} minutes."
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __status "$uid" || status=$?
fi fi
else else
# A listing password is independent of command authorization. An active status=$?
# grant can still be inspected/revoked without prompting under listpw=always. if (( status != STATUS_INACTIVE )); then
status=0
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null || status=$?
if (( status == 1 )) && [[ $minutes != "--disable" ]]; then
# No noninteractive inspection is available. Offer the confirmed enable
# flow; its single authenticated action validates existing policy itself.
status=$STATUS_INACTIVE
fi
fi
if (( status == 0 )) && { (($# == 0)) || [[ $minutes == "--disable" ]]; }; then
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __disable "$uid"
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
elif (( status == STATUS_INACTIVE )) && [[ $minutes == "--disable" ]]; then
echo "Passwordless sudo is already DISABLED."
elif (( status == 0 )) && [[ $minutes != "permanent" ]]; then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
show_enabled
else
if (( status != 0 && status != STATUS_INACTIVE )); then
echo "Could not safely inspect passwordless sudo; no grant will be enabled. Resolve the reported authorization or cleanup error first." >&2 echo "Could not safely inspect passwordless sudo; no grant will be enabled. Resolve the reported authorization or cleanup error first." >&2
exit 1 exit 1
fi fi
if [[ -z $minutes ]]; then
choice=$(/usr/bin/gum choose --header "How long should passwordless sudo stay enabled?" "15 minutes" "1 Hour" "1 Day" "Permanently") || exit 130
case "$choice" in
"15 minutes") minutes=15 ;;
"1 Hour") minutes=60 ;;
"1 Day") minutes=1440 ;;
"Permanently") minutes=permanent ;;
*) exit 130 ;;
esac
fi
if [[ $minutes == "permanent" ]]; then
duration="permanently"
else
duration="for ${minutes} minutes"
fi
echo "" echo ""
echo "⚠️ WARNING: This will allow ANY process running as your user to" echo "⚠️ WARNING: This will allow ANY process running as your user to"
echo "execute ANY command as root WITHOUT a password $duration." echo "execute ANY command as root WITHOUT a password for ${minutes} minutes."
echo "" echo ""
echo "This is useful for AI agents that need to run sudo commands," echo "This is useful for AI agents that need to run sudo commands,"
echo "but it significantly weakens the security of your system." echo "but it significantly weakens the security of your system."
echo "Anyone or anything with access to your user account gets full root." echo "Anyone or anything with access to your user account gets full root."
echo "" echo ""
if [[ $minutes == "permanent" ]]; then echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
echo "Passwordless sudo will remain enabled across reboots until you disable it." echo "including if the machine reboots before the deadline."
else echo "Run this command again to disable it early."
echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
echo "including if the machine reboots before the deadline."
fi
echo "Run this command again to disable it."
echo "" echo ""
if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then if /usr/bin/gum confirm "Enable passwordless sudo for ${minutes} minutes? This is a significant security risk!"; then
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes" /usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
echo "" echo ""
show_enabled echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes."
else else
echo "Aborted. No changes made." echo "Aborted. No changes made."
fi fi
+1 -1
View File
@@ -6,7 +6,7 @@
nohup bash -c "sleep 2 && uwsm stop" >/dev/null 2>&1 & nohup bash -c "sleep 2 && uwsm stop" >/dev/null 2>&1 &
omarchy-osd -i logout -m "$(omarchy-text "Logging out")" -d 5000 omarchy-osd -i logout -m "Logging out" -d 5000
# Now close all windows # Now close all windows
omarchy-hyprland-window-close-all omarchy-hyprland-window-close-all
+1 -1
View File
@@ -8,7 +8,7 @@
# scope cannot terminate it before it runs. # scope cannot terminate it before it runs.
systemd-run --user --collect --quiet --on-active="2s" --timer-property=AccuracySec=100ms systemctl reboot --no-wall || exit 1 systemd-run --user --collect --quiet --on-active="2s" --timer-property=AccuracySec=100ms systemctl reboot --no-wall || exit 1
omarchy-osd -i reboot -m "$(omarchy-text "Rebooting")" -d 5000 omarchy-osd -i reboot -m "Rebooting" -d 5000
omarchy-state clear re*-required omarchy-state clear re*-required
+2 -2
View File
@@ -7,7 +7,7 @@
if [[ ${1:-} == "--inhibited" ]]; then if [[ ${1:-} == "--inhibited" ]]; then
# Close windows in the background so they cannot delay power-off. # Close windows in the background so they cannot delay power-off.
( (
omarchy-osd -i shutdown -m "$(omarchy-text "Shutting down")" -d 5000 omarchy-osd -i shutdown -m "Shutting down" -d 5000
omarchy-state clear re*-required omarchy-state clear re*-required
omarchy-hyprland-window-close-all omarchy-hyprland-window-close-all
) & ) &
@@ -21,7 +21,7 @@ elif [[ ${1:-} == "--inhibit" ]]; then
--who=Omarchy --why="Shutdown in progress" --mode=block \ --who=Omarchy --why="Shutdown in progress" --mode=block \
"$0" --inhibited || { "$0" --inhibited || {
status=$? status=$?
omarchy-notification-send -u critical "$(omarchy-text "Shutdown failed")" "$(omarchy-text "Could not complete shutdown. Please try again.")" omarchy-notification-send -u critical "Shutdown failed" "Could not complete shutdown. Please try again."
exit "$status" exit "$status"
} }
else else
+2 -2
View File
@@ -98,8 +98,8 @@ report_unsecured() {
"$1" >&2 "$1" >&2
omarchy-notification-send -u critical -g 󰌾 \ omarchy-notification-send -u critical -g 󰌾 \
"$(omarchy-text "Screen did not lock before suspend")" \ "Screen did not lock before suspend" \
"$(omarchy-text "The session was left unlocked (%1)." "$1")" >/dev/null 2>&1 || true "The session was left unlocked ($1)." >/dev/null 2>&1 || true
exit 1 exit 1
} }
+1 -1
View File
@@ -59,7 +59,7 @@ claim_path() {
announce() { announce() {
local path="$1" local path="$1"
local name="${path##*/}" local name="${path##*/}"
local args=("$(omarchy-text "Received %1" "$name")" "$(omarchy-text "Saved to %1" "${dir/#$HOME/~}")" -u critical) local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical)
case "${name,,}" in case "${name,,}" in
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff) *.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
+4 -4
View File
@@ -27,8 +27,8 @@ if ((${#files[@]} == 0)); then
picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$? picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$?
if ((${status:-0} > 1)); then if ((${status:-0} > 1)); then
omarchy-notification-send -g "󰒊" -u critical "$(omarchy-text "Could not send to %1" "$name")" \ omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" \
"$(omarchy-text "The file chooser did not open")" "The file chooser did not open"
exit 1 exit 1
fi fi
@@ -43,8 +43,8 @@ else
fi fi
if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then
omarchy-notification-send -g "󰒊" "$(omarchy-text "Sent to %1" "$name")" "$what" omarchy-notification-send -g "󰒊" "Sent to $name" "$what"
else else
omarchy-notification-send -g "󰒊" -u critical "$(omarchy-text "Could not send to %1" "$name")" "${error:-$(omarchy-text "Taildrop transfer failed")}" omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" "${error:-Taildrop transfer failed}"
exit 1 exit 1
fi fi
-38
View File
@@ -1,38 +0,0 @@
#!/bin/bash
# omarchy:summary=Print an Omarchy message in the session's language, with %1.. filled from the arguments
# omarchy:args=<english> [args...]
# omarchy:examples=omarchy-text "Sent to %1" phone
# omarchy:hidden=true
set -euo pipefail
if (($# < 1)); then
echo "Usage: omarchy-text <english> [args...]" >&2
exit 1
fi
text=$1
shift
# The shell's table is the one source: one JSON-encoded "english": "chinese" pair per line.
if [[ ${LANG:-} == zh_CN* ]]; then
translated=$(grep -E '^ ".*": ".*",?$' "$OMARCHY_PATH/shell/Commons/I18nZh.js" | sed 's/,$//' | paste -sd, |
sed 's/^/{/; s/$/}/' | jq -r --arg key "$text" '.[$key] // empty')
[[ -z $translated ]] || text=$translated
fi
# One pass over the template, so an argument that holds %2 is printed as given.
out=""
rest=$text
while [[ $rest =~ ^([^%]*)%(.)(.*)$ ]]; do
out+=${BASH_REMATCH[1]}
mark=${BASH_REMATCH[2]}
rest=${BASH_REMATCH[3]}
if [[ $mark == [1-9] ]] && ((mark <= $#)); then
out+=${!mark}
else
out+="%$mark"
fi
done
printf '%s\n' "$out$rest"
-201
View File
@@ -1,201 +0,0 @@
#!/bin/bash
# omarchy:summary=Play the current background's matching theme intro
# omarchy:hidden=true
set -euo pipefail
state_home="$HOME/.local/state"
current_dir="$state_home/omarchy/current"
current_background_link="$current_dir/background"
marker="$state_home/omarchy/background-intro.session-id"
toggle="$state_home/omarchy/toggles/background-intros-off"
video_extensions=(mp4 m4v mov webm mkv avi)
resolve_intro() {
local background
local stem
local extension
local intro
background=$(readlink -f "${1:-$current_background_link}" 2>/dev/null) || return 1
[[ -f $background ]] || return 1
case ${background,,} in
*.jpg | *.jpeg | *.png | *.bmp | *.webp) ;;
*) return 1 ;;
esac
stem=${background##*/}
stem=${stem%.*}
for extension in "${video_extensions[@]}"; do
intro="${background%/*}/intros/$stem.$extension"
if [[ -f $intro ]]; then
printf '%s\n' "$intro"
return 0
fi
done
return 1
}
# Preparation is speculative: the selected staged clip is compared again by OWE.
prepare_theme_intro() {
local theme="$1" background remembered name source intro
local -a names=()
[[ -n $theme && $theme != .* && $theme != */* ]] || return 0
[[ ! -f $toggle ]] && ! animations_off || return 0
owe warm >/dev/null 2>&1 || return 0
[[ $(cat "$current_dir/theme.name" 2>/dev/null) != "$theme" ]] || return 0
remembered=$(cat "$state_home/omarchy/theme-backgrounds/$theme" 2>/dev/null || true)
if [[ ${remembered%/*} == "$current_dir/theme/backgrounds" ]]; then
name=${remembered##*/}
elif [[ -f $remembered ]]; then
background="$remembered"
else
mapfile -d '' -t names < <(
find -L "$HOME/.config/omarchy/backgrounds/$theme" -maxdepth 1 -type f -print0 2>/dev/null | sort -z
)
if (( ${#names[@]} > 0 )); then
background=${names[0]}
else
mapfile -d '' -t names < <(
find -L "$OMARCHY_PATH/themes/$theme/backgrounds" "$HOME/.config/omarchy/themes/$theme/backgrounds" -maxdepth 1 -type f \
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.bmp' -o -iname '*.webp' \) -printf '%f\0' 2>/dev/null | sort -zu
)
(( ${#names[@]} > 0 )) || return 0
name=${names[0]}
fi
fi
if [[ -n ${name:-} ]]; then
for source in "$HOME/.config/omarchy/themes/$theme" "$OMARCHY_PATH/themes/$theme"; do
if [[ -f $source/backgrounds/$name ]]; then
background="$source/backgrounds/$name"
break
fi
done
fi
[[ -n ${background:-} ]] || return 0
intro=$(resolve_intro "$background" || true)
[[ -z $intro ]] || owe intro-prepare "$intro" >/dev/null 2>&1 || true
}
animations_off() {
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool' 2>/dev/null) == "false" ]]
}
theme_switch=false
intro_prepared=false
expected_background=""
expected_theme=""
case ${1:-} in
"") ;;
--prepare-theme)
prepare_theme_intro "${2:-}"
exit 0
;;
--resolve-only)
resolve_intro "${2:-}" || true
exit 0
;;
--resolve-startup)
if [[ ! -f $toggle ]] && ! animations_off; then
resolve_intro || true
fi
exit 0
;;
--theme-switch)
theme_switch=true
expected_background=$(readlink -f "${2:-}" 2>/dev/null) || exit 1
expected_theme=${3:-}
intro_prepared=${4:-false}
[[ -n $expected_theme ]] || exit 1
;;
*)
echo "Usage: omarchy-theme-bg-boot-intro [--resolve-only [background]|--resolve-startup|--prepare-theme theme|--theme-switch background theme-id [prepared]]" >&2
exit 1
;;
esac
if [[ $theme_switch == false ]]; then
mkdir -p "$(dirname "$marker")"
exec 9>"$state_home/omarchy/background-intro.lock"
flock 9
session_id=${OMARCHY_SESSION_ID:-${HYPRLAND_INSTANCE_SIGNATURE:-}}
[[ -n $session_id ]] || exit 0
if [[ -f $marker && $(<"$marker") == "$session_id" ]]; then
exit 0
fi
marker_staged=$(mktemp "$state_home/omarchy/.background-intro.session-id.XXXXXX")
trap 'rm -f "$marker_staged"' EXIT
printf '%s\n' "$session_id" >"$marker_staged"
mv -f "$marker_staged" "$marker"
trap - EXIT
exec 9>&-
fi
selection_current() {
[[ $(readlink -f "$current_background_link") == "$expected_background" &&
$(stat -Lc '%d:%i' "$current_dir/theme" 2>/dev/null) == "$expected_theme" ]]
}
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
if [[ -f $toggle ]] || animations_off; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
intro=$(resolve_intro || true)
if [[ -z $intro ]]; then
[[ $theme_switch == false ]] || exit 1
exit 0
fi
if [[ $theme_switch == true && $intro_prepared != "true" ]]; then
owe refresh >/dev/null || exit 1
elif [[ $theme_switch == false ]]; then
deadline=$((SECONDS + 5))
while ! timeout 1 owe status >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
exit 0
fi
sleep 0.2
done
(( SECONDS < deadline )) || exit 0
fi
# The asynchronous outgoing cover must reach the compositor before playback
# can hide the shell background. Older shells have no readiness method.
if [[ $theme_switch == true ]]; then
deadline=$((SECONDS + 3))
while true; do
cover_status=$(omarchy-shell shell themeIntroCoverStatus "$expected_theme" 2>/dev/null || true)
case $cover_status in
loading)
(( SECONDS < deadline )) || exit 1
sleep 0.01
;;
error) exit 1 ;;
superseded) exit 0 ;;
*) break ;;
esac
done
fi
if [[ $theme_switch == true ]] && ! selection_current; then
exit 0
fi
# A selection or setting changed while OWE was starting no longer permits this intro.
if [[ ! -f $toggle && $intro == "$(resolve_intro || true)" ]] && ! animations_off; then
if [[ $theme_switch == true && $intro_prepared == "true" ]]; then
owe intro --start first-frame --refresh "$intro"
else
owe intro --start first-frame "$intro"
fi
elif [[ $theme_switch == true ]]; then
exit 1
fi
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash
# omarchy:summary=Enable or disable background intros
# omarchy:args=<enable|disable|toggle|status>
# omarchy:examples=omarchy theme bg intro toggle | omarchy theme bg intro disable
set -euo pipefail
toggle="$HOME/.local/state/omarchy/toggles/background-intros-off"
case ${1:-} in
enable)
rm -f "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Enabled")"
;;
disable)
mkdir -p "$(dirname "$toggle")"
touch "$toggle"
omarchy-notification-send "$(omarchy-text "Background Intros")" "$(omarchy-text "Disabled")"
;;
toggle)
if [[ -f $toggle ]]; then
omarchy-theme-bg-intro enable
else
omarchy-theme-bg-intro disable
fi
;;
status)
[[ ! -f $toggle ]]
;;
*)
echo "Usage: omarchy-theme-bg-intro <enable|disable|toggle|status>" >&2
exit 1
;;
esac
+1 -1
View File
@@ -17,7 +17,7 @@ mapfile -d '' -t BACKGROUNDS < <(
TOTAL=${#BACKGROUNDS[@]} TOTAL=${#BACKGROUNDS[@]}
if (( TOTAL == 0 )); then if (( TOTAL == 0 )); then
omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000 omarchy-notification-send "No background was found for theme" -t 2000
else else
# Get current background from symlink # Get current background from symlink
if [[ -L $CURRENT_BACKGROUND_LINK ]]; then if [[ -L $CURRENT_BACKGROUND_LINK ]]; then
+1 -1
View File
@@ -35,5 +35,5 @@ fi
# Now remove the theme directory for THEME_NAME # Now remove the theme directory for THEME_NAME
rm -rf "$THEME_PATH" rm -rf "$THEME_PATH"
omarchy-notification-send "$(omarchy-text "Theme removed")" "$THEME_NAME" || true omarchy-notification-send "Theme removed" "$THEME_NAME" || true
echo "Removed $THEME_NAME" echo "Removed $THEME_NAME"
+7 -85
View File
@@ -50,13 +50,6 @@ shell_ipc() {
timeout 2 omarchy-shell "$@" >/dev/null 2>&1 timeout 2 omarchy-shell "$@" >/dev/null 2>&1
} }
wait_for_theme_intro() {
local method="$1" token="$2" pending="$3" deadline=$((SECONDS + 3))
while (( SECONDS < deadline )) && [[ $(omarchy-shell shell "$method" "$token" 2>/dev/null) == "$pending" ]]; do
sleep 0.02
done
}
is_video_path() { is_video_path() {
[[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]] [[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]]
} }
@@ -77,18 +70,10 @@ snapshot_background_path() {
} }
snapshot_current_background() { snapshot_current_background() {
local current_background snapshot local current_background
# An interrupted intro is still moving; its final still would flash as a current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
# cover. Capture the frame already playing instead, when OWE supports it. snapshot_background_path "$current_background" "previous"
mkdir -p "$BACKGROUND_TRANSITION_CACHE"
snapshot="$BACKGROUND_TRANSITION_CACHE/previous-$$.ppm"
if owe render "$(jq -nc --arg path "$snapshot" '{cmd: "snapshot", path: $path}')" >/dev/null 2>&1; then
echo "$snapshot"
else
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
snapshot_background_path "$current_background" "previous"
fi
} }
theme_background_state_file() { theme_background_state_file() {
@@ -186,48 +171,17 @@ set_theme_background_link() {
} }
set_theme_background() { set_theme_background() {
local new_background new_background_snapshot theme_id intro_prepared=false local new_background new_background_snapshot
if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then
if ! choose_theme_background; then if ! choose_theme_background; then
omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000 omarchy-notification-send "No background was found for theme" -t 2000
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
return return
fi fi
fi fi
new_background="$CHOSEN_THEME_BACKGROUND" new_background="$CHOSEN_THEME_BACKGROUND"
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
if [[ -n $INTRO_PREPARE_PID ]] && wait "$INTRO_PREPARE_PID"; then
intro_prepared=true
fi
theme_id=$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH")
THEME_INTRO_TOKEN="$theme_id"
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$theme_id" "$colors_payload" "$shell_payload" || \
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
ln -nsf "$new_background" "$CURRENT_BACKGROUND_LINK"
(
if ! omarchy-theme-bg-boot-intro --theme-switch "$new_background" "$theme_id" "$intro_prepared"; then
if [[ $(readlink -f "$CURRENT_BACKGROUND_LINK") == "$(readlink -f "$new_background")" &&
$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
shell_ipc background setInstant "$new_background" || true
fi
fi
shell_ipc shell finishThemeIntro "$theme_id" || true
wait_for_theme_intro themeIntroStatus "$theme_id" "pending"
# Reloading Hyprland interrupts video presentation. Wait for playback,
# then serialize with theme selection so an old intro cannot reload
# the compositor during the next theme's video.
(
flock 9
if [[ $(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null) == "$theme_id" ]]; then
omarchy-restart-hyprctl
fi
) 9>"$THEME_SET_LOCK"
[[ -z $OLD_BACKGROUND_SNAPSHOT ]] || rm -f "$OLD_BACKGROUND_SNAPSHOT"
) 9>&- &
return
fi
new_background_snapshot="" new_background_snapshot=""
if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then
new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT" new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT"
@@ -383,12 +337,6 @@ fi
exec 9>"$THEME_SET_LOCK" exec 9>"$THEME_SET_LOCK"
flock 9 flock 9
# A current video frame only represents the screen after its opening fade.
# Finish that short reveal before capturing the next outgoing cover.
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
wait_for_theme_intro themeIntroStatus "$(stat -Lc '%d:%i' "$CURRENT_THEME_PATH" 2>/dev/null)" "pending"
fi
PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true) PREVIOUS_THEME_NAME=$(cat "$CURRENT_THEME_NAME_PATH" 2>/dev/null || true)
remember_current_theme_background remember_current_theme_background
@@ -417,24 +365,11 @@ OLD_BACKGROUND_SNAPSHOT=""
PREPARED_BACKGROUND="" PREPARED_BACKGROUND=""
PREPARED_BACKGROUND_SNAPSHOT="" PREPARED_BACKGROUND_SNAPSHOT=""
BACKGROUND_TRANSITION_SNAPSHOTS=true BACKGROUND_TRANSITION_SNAPSHOTS=true
THEME_BACKGROUND_INTRO=""
INTRO_PREPARE_PID=""
THEME_INTRO_TOKEN=""
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
# Resolve the staged choice while the old theme still exists. Video changes # Resolve the staged choice while the old theme still exists. Video changes
# switch directly to the durable path in QML, so neither side needs a copy. # switch directly to the durable path in QML, so neither side needs a copy.
if choose_staged_theme_background; then if choose_staged_theme_background; then
if [[ $PREVIOUS_THEME_NAME != "$THEME_NAME" && ! -f $HOME/.local/state/omarchy/toggles/background-intros-off ]] && if background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
[[ $(hyprctl -j getoption animations:enabled 2>/dev/null | jq -r '.bool') == "true" ]]; then
THEME_BACKGROUND_INTRO=$(omarchy-theme-bg-boot-intro --resolve-only "$STAGED_THEME_BACKGROUND")
fi
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# OWE retains the opened clip across the staged directory's rename.
owe intro-prepare "$THEME_BACKGROUND_INTRO" >/dev/null 2>&1 9>&- &
INTRO_PREPARE_PID=$!
shell_ipc shell prepareThemeIntro "$OLD_BACKGROUND_SNAPSHOT" "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "" "" || true
elif background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background) OLD_BACKGROUND_SNAPSHOT=$(snapshot_current_background)
# Hand the shell the next background now, so it decodes while the # Hand the shell the next background now, so it decodes while the
@@ -454,12 +389,6 @@ fi
# Generate dynamic configs # Generate dynamic configs
omarchy-theme-set-templates omarchy-theme-set-templates
# Replacing the theme's still can cancel an active OWE intro even before the
# background link changes. Present its cover before replacing either path.
if [[ -n $THEME_BACKGROUND_INTRO ]]; then
wait_for_theme_intro themeIntroCoverStatus "$(stat -Lc '%d:%i' "$NEXT_THEME_PATH")" "loading"
fi
# Swap next theme in as current # Swap next theme in as current
rm -rf "$CURRENT_THEME_PATH" rm -rf "$CURRENT_THEME_PATH"
mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH" mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
@@ -489,6 +418,7 @@ flock -u 9
post_theme_commands=( post_theme_commands=(
omarchy-restart-terminal omarchy-restart-terminal
omarchy-restart-hyprctl
omarchy-restart-btop omarchy-restart-btop
omarchy-restart-opencode omarchy-restart-opencode
omarchy-restart-helix omarchy-restart-helix
@@ -507,14 +437,6 @@ post_theme_commands=(
) )
if [[ $THEME_HEADLESS != "1" ]]; then if [[ $THEME_HEADLESS != "1" ]]; then
# Let the wallpaper and bar finish their reveal before app retints compete
# with decoding and the shell's frame rendering. An intro reloads Hyprland
# after playback; ordinary theme changes reload it with the app retints.
if [[ -n $THEME_INTRO_TOKEN ]]; then
wait_for_theme_intro themeIntroStatus "$THEME_INTRO_TOKEN" "pending"
else
post_theme_commands+=(omarchy-restart-hyprctl)
fi
run_parallel "${post_theme_commands[@]}" run_parallel "${post_theme_commands[@]}"
# Call hook on theme set # Call hook on theme set
+2 -7
View File
@@ -29,8 +29,6 @@ if [[ -n $OMARCHY_THEME_SYNC_FROM ]] || ! omarchy-toggle-enabled herdr-theme-syn
fi fi
theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null) theme=$(cat "$HOME/.local/state/omarchy/current/theme.name" 2>/dev/null)
control_dir="$XDG_RUNTIME_DIR/omarchy-theme-sync"
mkdir -m 700 -p "$control_dir"
# Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there. # Runs on the remote machine under a login shell, so OMARCHY_PATH and PATH are set there.
# SSH carries no desktop session, so the environment comes from the live Hyprland instance. # SSH carries no desktop session, so the environment comes from the live Hyprland instance.
@@ -68,13 +66,10 @@ read -r -d '' REMOTE_SCRIPT <<'EOF'
} </dev/null } </dev/null
EOF EOF
# Reuse each authenticated connection for ten idle minutes, so an SSH agent like # One machine at a time, so an SSH agent like 1Password asks for approval at most once.
# 1Password does not need to approve every theme change. Connect one machine at a time.
# SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment. # SSH joins its arguments into a shell command, so the theme travels on stdin as a quoted assignment.
for target in $targets; do for target in $targets; do
result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" | result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" |
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 \ timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 "$target" bash -l -s 2>&1 | tail -n 1)
-o ControlMaster=auto -o ControlPersist=600 -o "ControlPath=$control_dir/%C" \
"$target" bash -l -s 2>&1 | tail -n 1)
echo "$(date '+%F %T') $target: ${result:-unreachable}" echo "$(date '+%F %T') $target: ${result:-unreachable}"
done done
+2 -2
View File
@@ -16,7 +16,7 @@ off) omarchy-hyprland-toggle no-animations on ;;
esac esac
if omarchy-hyprland-toggle-enabled no-animations; then if omarchy-hyprland-toggle-enabled no-animations; then
omarchy-notification-send -g 󰕟 "$(omarchy-text "Animations disabled")" omarchy-notification-send -g 󰕟 "Animations disabled"
else else
omarchy-notification-send -g 󰕟 "$(omarchy-text "Animations enabled")" omarchy-notification-send -g 󰕟 "Animations enabled"
fi fi
+2 -2
View File
@@ -11,8 +11,8 @@ omarchy-toggle crash-capture-off
# starting only settles this session; the next login reads the flag itself. # starting only settles this session; the next login reads the flag itself.
if omarchy-toggle-enabled crash-capture-off; then if omarchy-toggle-enabled crash-capture-off; then
systemctl --user stop omarchy-crash-watch.service systemctl --user stop omarchy-crash-watch.service
omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture disabled")" omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture disabled"
else else
systemctl --user start omarchy-crash-watch.service systemctl --user start omarchy-crash-watch.service
omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture enabled")" omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture enabled"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle screensaver-off omarchy-toggle screensaver-off
if omarchy-toggle-enabled screensaver-off; then if omarchy-toggle-enabled screensaver-off; then
omarchy-notification-send -g 󱄄 "$(omarchy-text "Screensaver disabled")" omarchy-notification-send -g 󱄄 "Screensaver disabled"
else else
omarchy-notification-send -g 󱄄 "$(omarchy-text "Screensaver enabled")" omarchy-notification-send -g 󱄄 "Screensaver enabled"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle suspend-off omarchy-toggle suspend-off
if omarchy-toggle-enabled suspend-off; then if omarchy-toggle-enabled suspend-off; then
omarchy-notification-send -g 󰒲 "$(omarchy-text "Suspend removed from system menu")" omarchy-notification-send -g 󰒲 "Suspend removed from system menu"
else else
omarchy-notification-send -g 󰒲 "$(omarchy-text "Suspend now available in system menu")" omarchy-notification-send -g 󰒲 "Suspend now available in system menu"
fi fi
+2 -2
View File
@@ -5,7 +5,7 @@
omarchy-toggle herdr-theme-sync omarchy-toggle herdr-theme-sync
if omarchy-toggle-enabled herdr-theme-sync; then if omarchy-toggle-enabled herdr-theme-sync; then
omarchy-notification-send -g 󰸌 "$(omarchy-text "Herdr theme sync enabled")" omarchy-notification-send -g 󰸌 "Herdr theme sync enabled"
else else
omarchy-notification-send -g 󰸌 "$(omarchy-text "Herdr theme sync disabled")" omarchy-notification-send -g 󰸌 "Herdr theme sync disabled"
fi fi
+3 -3
View File
@@ -193,14 +193,14 @@ main() {
output=$(output_path "$input" "$format" "$resolution") output=$(output_path "$input" "$format" "$resolution")
if [[ $type == "video" ]]; then if [[ $type == "video" ]]; then
omarchy-notification-send -g  "$(omarchy-text "Transcoding video…")" "$(omarchy-text "%1 to %2 (%3)" "$(basename -- "$input")" "$format" "$resolution")" omarchy-notification-send -g  "Transcoding video…" "$(basename -- "$input") to $format ($resolution)"
transcode_video "$input" "$format" "$resolution" "$output" transcode_video "$input" "$format" "$resolution" "$output"
copy_to_clipboard "$output" copy_to_clipboard "$output"
omarchy-notification-send -g  "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard." omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard."
else else
transcode_picture "$input" "$format" "$resolution" "$output" transcode_picture "$input" "$format" "$resolution" "$output"
copy_to_clipboard "$output" copy_to_clipboard "$output"
omarchy-notification-send -g  "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard." omarchy-notification-send -g  "Transcoded to $resolution $format" "Saved and copied to clipboard."
fi fi
} }
+1 -1
View File
@@ -37,5 +37,5 @@ icon_name=$(printf '%s\n' "$APP_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^[:
rm -f "$DESKTOP_DIR/$APP_NAME.desktop" rm -f "$DESKTOP_DIR/$APP_NAME.desktop"
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png" rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
omarchy-notification-send -g  "$(omarchy-text "TUI removed")" "$APP_NAME" omarchy-notification-send -g  "TUI removed" "$APP_NAME"
fi fi
+1 -1
View File
@@ -127,7 +127,7 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
omarchy-update-system-pkgs omarchy-update-system-pkgs
omarchy-migrate omarchy-migrate
omarchy-update-orphan-pkgs -y omarchy-update-orphan-pkgs
omarchy-update-analyze-logs omarchy-update-analyze-logs
omarchy-update-status omarchy-update-status
+3 -5
View File
@@ -1,8 +1,6 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Review and optionally remove orphaned system packages after updates # omarchy:summary=Review and optionally remove orphaned system packages after updates
# omarchy:args=[-y]
# omarchy:examples=omarchy update orphan-pkgs | omarchy update orphan-pkgs -y
# omarchy:requires-sudo=true # omarchy:requires-sudo=true
set -e set -e
@@ -14,18 +12,18 @@ echo -e "\e[32m\nOrphan system packages\e[0m"
printf ' %s\n' "${orphans[@]}" printf ' %s\n' "${orphans[@]}"
echo echo
if [[ ${1:-} != "-y" && ( ! -t 0 || ! -t 1 ) ]]; then if [[ ! -t 0 || ! -t 1 ]]; then
echo "${#orphans[@]} orphaned package(s) found. Re-run omarchy-update-orphan-pkgs in a terminal to review/remove them." echo "${#orphans[@]} orphaned package(s) found. Re-run omarchy-update-orphan-pkgs in a terminal to review/remove them."
echo echo
exit 0 exit 0
fi fi
if [[ ${1:-} != "-y" ]] && ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then if ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then
echo "Keeping orphaned packages." echo "Keeping orphaned packages."
echo echo
exit 0 exit 0
fi fi
echo -e "\e[32m\nRemoving orphan system packages\e[0m" echo -e "\e[32m\nRemoving orphan system packages\e[0m"
omarchy-pkg-drop "${orphans[@]}" sudo pacman -Rns "${orphans[@]}"
echo echo
-2
View File
@@ -1802,8 +1802,6 @@ copy_missing_config_defaults() {
while IFS= read -r -d '' source_path; do while IFS= read -r -d '' source_path; do
rel="${source_path#$source_dir/}" rel="${source_path#$source_dir/}"
is_retired_config_file "$rel" && continue is_retired_config_file "$rel" && continue
# The input migration preserves working shortcuts in existing IME profiles.
[[ $rel == "fcitx5/config" ]] && continue
target_path="$target_dir/$rel" target_path="$target_dir/$rel"
[[ -e $target_path || -L $target_path ]] && continue [[ -e $target_path || -L $target_path ]] && continue
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# omarchy:summary=Install and configure Voxtype dictation
# omarchy:requires-sudo=true
set -e
# Install voxtype and configure it for use.
if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
omarchy-pkg-add wtype voxtype-bin
# Setup voxtype
mkdir -p ~/.config/voxtype
cp "$OMARCHY_PATH/default/voxtype/config.toml" ~/.config/voxtype/
voxtype setup --download --no-post-install
if omarchy-hw-vulkan; then
voxtype setup gpu --enable || true
fi
voxtype setup systemd
hyprctl reload >/dev/null
omarchy-restart-shell
omarchy-notification-send -g  "Voxtype Dictation Ready" "Hold F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000
fi
@@ -18,10 +18,6 @@ if omarchy-cmd-present voxtype; then
omarchy-pkg-drop voxtype-bin omarchy-pkg-drop voxtype-bin
rm -rf ~/.config/voxtype rm -rf ~/.config/voxtype
rm -rf ~/.local/share/voxtype rm -rf ~/.local/share/voxtype
backend_config="${XDG_CONFIG_HOME:-$HOME/.config}/omarchy/defaults/dictation"
if [[ -f $backend_config && $(< "$backend_config") == "voxtype" ]]; then
rm "$backend_config"
fi
hyprctl reload >/dev/null hyprctl reload >/dev/null
else else
echo "Voxtype was not installed." echo "Voxtype was not installed."
+13
View File
@@ -0,0 +1,13 @@
#!/bin/bash
# omarchy:summary=Stream voxtype --follow status as bar-friendly JSON
if omarchy-cmd-missing voxtype; then
echo '{"alt": "", "class": "idle", "tooltip": ""}'
exit 0
fi
# Keep the status follower as the direct child of the bar. Quickshell
# clean up their direct child when they restart; a shell pipeline/coproc would
# leave `voxtype status --follow` orphaned under the user systemd instance.
exec setpriv --pdeathsig TERM voxtype status --follow --extended --format json
+1 -1
View File
@@ -56,6 +56,6 @@ rm -f "${desktop_file:-$DESKTOP_DIR/$APP_NAME.desktop}"
rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png" rm -f "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
omarchy-notification-send -g  "$(omarchy-text "Web app removed")" "$APP_NAME" omarchy-notification-send -g  "Web app removed" "$APP_NAME"
fi fi
update-desktop-database "$DESKTOP_DIR" &>/dev/null update-desktop-database "$DESKTOP_DIR" &>/dev/null
+1 -1
View File
@@ -1414,7 +1414,7 @@ launch_windows() {
if ! priv up_wait; then if ! priv up_wait; then
echo "❌ Failed to start Windows VM!" echo "❌ Failed to start Windows VM!"
echo " Try checking: omarchy-windows-vm status" echo " Try checking: omarchy-windows-vm status"
omarchy-notification-send -u critical "$(omarchy-text "Windows VM")" "$(omarchy-text "Failed to start Windows VM")" omarchy-notification-send -u critical "Windows VM" "Failed to start Windows VM"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -1,5 +1,5 @@
--ozone-platform=wayland --ozone-platform=wayland
--ozone-platform-hint=wayland --ozone-platform-hint=wayland
--password-store=gnome-libsecret --password-store=gnome-libsecret
--enable-features=TouchpadOverscrollHistoryNavigation,OverlayScrollbar --enable-features=TouchpadOverscrollHistoryNavigation
--load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim --load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim
-3
View File
@@ -1,3 +0,0 @@
# Keep Pinyin offline without prompting to enable cloud prediction.
FirstRun=False
CloudPinyinEnabled=False
-2
View File
@@ -1,2 +0,0 @@
[TriggerKey]
0=Super+semicolon
-1
View File
@@ -1 +0,0 @@
Allow Overriding System XKB Settings=False
-20
View File
@@ -1,20 +0,0 @@
# Leave Ctrl+Space to tmux/Herdr and the Super shortcuts to the desktop.
[Hotkey/TriggerKeys]
0=Control+Shift+space
1=Zenkaku_Hankaku
2=Hangul
[Hotkey/ActivateKeys]
0=Henkan
[Hotkey/DeactivateKeys]
0=Muhenkan
[Hotkey/AltTriggerKeys]
[Hotkey/EnumerateGroupForwardKeys]
[Hotkey/EnumerateGroupBackwardKeys]
[Behavior]
ActiveByDefault=False
-1
View File
@@ -1 +0,0 @@
superwhisper
@@ -11,7 +11,7 @@
# #
# To put it into use, remove .sample from this file name. # To put it into use, remove .sample from this file name.
# Example: add an Include line above the first repository, [omarchy]. # Example: add an Include line above [core].
# Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself. # Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself.
CONF=/etc/pacman.conf CONF=/etc/pacman.conf
@@ -21,4 +21,4 @@ MARKER="Include = $SNIPPET"
[[ -r $SNIPPET ]] || exit 0 [[ -r $SNIPPET ]] || exit 0
grep -qxF "$MARKER" "$CONF" && exit 0 grep -qxF "$MARKER" "$CONF" && exit 0
sudo sed -i "0,/^\[omarchy\]/s||$MARKER\n\n[omarchy]|" "$CONF" sudo sed -i "0,/^\[core\]/s||$MARKER\n\n[core]|" "$CONF"
@@ -1,298 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd"> <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig> <fontconfig>
<!-- CJK: fold the language tag onto one tag per Noto CJK variant -->
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-hk</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hant</string>
</test>
<edit name="lang" mode="assign">
<string>zh-tw</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-sg</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-mo</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="eq">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-hans</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue-cn</string>
</test>
<edit name="lang" mode="assign">
<string>zh-cn</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>yue</string>
</test>
<edit name="lang" mode="assign">
<string>zh-hk</string>
</edit>
</match>
<!-- Then append that variant after the generic family it falls back from -->
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-cn</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK SC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-hk</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK HK</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<string>zh-tw</string>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK TC</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ja</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK JP</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>sans-serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>serif</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Serif CJK KR</string>
</edit>
</match>
<match target="pattern">
<test name="lang" qual="first" compare="contains">
<langset><string>ko</string></langset>
</test>
<test name="family">
<string>monospace</string>
</test>
<edit name="family" mode="append" binding="strong">
<string>Noto Sans Mono CJK KR</string>
</edit>
</match>
<match target="pattern"> <match target="pattern">
<test name="family" qual="any"> <test name="family" qual="any">
<string>sans-serif</string> <string>sans-serif</string>
+4 -8
View File
@@ -6,16 +6,12 @@ hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation
-- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the -- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the
-- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer -- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer
-- animations. Overlays open as soon as their scale is ready, without a -- animations. The overlays stay mapped between opens and grow from a parked
-- compositor fade or slide. Panels keep their own QML opacity transition -- 1x1 when shown, which Hyprland would otherwise animate as a slide in from
-- for normal open/close, and skip it for panel handoff. -- the corner. Panels keep their own QML opacity transition for normal
-- open/close, and skip it for panel handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel|omarchy-osd|omarchy-reminders|omarchy-network-qr)$" }, no_anim = true, animation = "none" }) hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel|omarchy-osd|omarchy-reminders|omarchy-network-qr)$" }, no_anim = true, animation = "none" })
-- A boot intro hands the desktop between OWE's video layer and the shell's
-- background layer. The media fades itself, so a compositor fade on either
-- layer only dips the screen toward the empty desktop during the handoff.
hl.layer_rule({ match = { namespace = "^(omarchy-background|owe-background)$" }, no_anim = true, animation = "none" })
-- Dev gallery is the main shell workbench; open it maximized like -- Dev gallery is the main shell workbench; open it maximized like
-- SUPER+ALT+F so component previews have the whole workspace. -- SUPER+ALT+F so component previews have the whole workspace.
o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true }) o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true })
-8
View File
@@ -1,12 +1,4 @@
require("default.hypr.startup-cursor")
hl.on("hyprland.start", function() hl.on("hyprland.start", function()
-- Applications inherit the user's cursor; only the compositor starts blank.
if omarchy_startup_cursor_pending then
hl.env("XCURSOR_PATH", omarchy_startup_cursor.path)
hl.env("XCURSOR_THEME", omarchy_startup_cursor.xcursor)
end
-- Slow app launch fix -- set systemd vars before starting session services. -- Slow app launch fix -- set systemd vars before starting session services.
hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)") hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)")
hl.exec_cmd("dbus-update-activation-environment --systemd --all") hl.exec_cmd("dbus-update-activation-environment --systemd --all")
-9
View File
@@ -1,9 +0,0 @@
if o.shell_succeeds("omarchy-default-dictation") then
o.bind("SUPER + CTRL + X", "Toggle dictation", "omarchy-dictation toggle")
o.bind("F9", "Start dictation (push-to-talk)", "omarchy-dictation start")
o.bind("F9", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true })
-- A modifier's mask changes between its press and release. Match the keysym
-- independently of that mask; AltGr layouts use a different keysym.
o.bind("ALT + Alt_R", "Start dictation (push-to-talk)", "omarchy-dictation start", { ignore_mods = true })
o.bind("ALT + Alt_R", "Stop dictation (push-to-talk)", "omarchy-dictation stop", { release = true, ignore_mods = true })
end
-66
View File
@@ -1,66 +0,0 @@
local paths = require("default.hypr.paths")
-- Same terminal tag test as clipboard.lua.
local function active_window_is_terminal()
local window = hl.get_active_window()
if not window then
return false
end
for _, tag in ipairs(window.tags or {}) do
if tag:gsub("%*$", "") == "terminal" then
return true
end
end
return false
end
-- Read on demand so adding an engine in a live desktop needs no reload.
local function has_input_method()
local file = io.open(paths.config_home .. "/fcitx5/profile", "r")
if not file then return false end
local in_item, found = false, false
for line in file:lines() do
if line:match("^%[") then
in_item = line:match("^%[Groups/%d+/Items/%d+%]$") ~= nil
elseif in_item then
local name = line:match("^Name=(.+)$")
if name and not name:match("^keyboard%-") then found = true end
end
end
file:close()
return found
end
-- Preserve custom Ctrl + Space triggers. Without a
-- [Hotkey/TriggerKeys] list in the config, fcitx5 uses its default, which has
-- Ctrl + Space.
local function fcitx5_toggles_on_ctrl_space()
local file = io.open(paths.config_home .. "/fcitx5/config", "r")
if not file then
return true
end
local in_triggers, has_triggers, toggles = false, false, false
for line in file:lines() do
if line:match("^%[") then
in_triggers = line == "[Hotkey/TriggerKeys]"
has_triggers = has_triggers or in_triggers
elseif in_triggers and line:match("^%d+=Control%+space$") then
toggles = true
end
end
file:close()
return toggles or not has_triggers
end
-- Pass the tmux/Herdr prefix through and return to direct input, so the next
-- key reaches the multiplexer instead of the engine's composition.
o.bind("CTRL + SPACE", "Direct input for the terminal prefix", function()
if active_window_is_terminal() and has_input_method() and not fcitx5_toggles_on_ctrl_space() then
-- Wait for the local controller reply before dispatching the next key.
o.shell_succeeds("timeout 0.3 fcitx5-remote --check -c")
end
end, { non_consuming = true })
-43
View File
@@ -1,43 +0,0 @@
-- Defaults for Japanese (JIS) keyboards, which only apply when the installed
-- layout is jp. See default/hypr/keyboard.lua.
local keyboard = require("default.hypr.keyboard")
if not keyboard.jis() then
return
end
-- Same down/up split as the universal clipboard shortcuts in clipboard.lua.
local function send_shortcut_once(mods, key)
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "down" }))
hl.timer(function()
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "up" }))
end, { timeout = 50, type = "oneshot" })
end
local function active_window_class()
local window = hl.get_active_window()
return ((window and window.class) or ""):lower()
end
-- On JIS, + is Shift + ;, so apps see Ctrl + Shift + semicolon and never zoom
-- in. Hand them Ctrl + keypad plus instead, which Chromium, Electron apps,
-- VS Code, and Firefox read as zoom in. Obsidian only listens for Ctrl + ^,
-- which JIS types without Shift.
local zoom_in_overrides = {
obsidian = { "CTRL", "asciicircum" },
}
o.bind("CTRL + SHIFT + semicolon", "Zoom in (JIS + key)", function()
local class = active_window_class()
for pattern, chord in pairs(zoom_in_overrides) do
if class:find(pattern, 1, true) then
send_shortcut_once(chord[1], chord[2])
return
end
end
send_shortcut_once("CTRL", "KP_Add")
end)
Loaded 100 of 406 files, more files were not shown because too many files have changed in this diff. Show more