Compare commits
136
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4551e2767
|
||
|
|
f2d69265d6
|
||
|
|
375ff9036d
|
||
|
|
6a221de8b3
|
||
|
|
70fc7a9e2d
|
||
|
|
ae065afb66
|
||
|
|
f2af41bbc9
|
||
|
|
dd704a29cd
|
||
|
|
3c6e7cca55 | ||
|
|
02fb6439eb | ||
|
|
c8ec83d046 | ||
|
|
8e1f9266ca | ||
|
|
fcd681c099 | ||
|
|
76b9331d8b | ||
|
|
c08a1bc3c2 | ||
|
|
2d2aa337e4 | ||
|
|
738d59e3a9 | ||
|
|
7198a7b711 | ||
|
|
e5b01ace6b | ||
|
|
848e9610c0 | ||
|
|
95d4e57db9 | ||
|
|
6da63c8942 | ||
|
|
562684f9fa | ||
|
|
b914d694f2 | ||
|
|
199a7f23fc | ||
|
|
1824bc3066 | ||
|
|
371fd05f03 | ||
|
|
557ba1d270 | ||
|
|
fdd457c0fa | ||
|
|
8798461461 | ||
|
|
4521dac023 | ||
|
|
c7402fe653 | ||
|
|
c352b62d67 | ||
|
|
15b3529ef2 | ||
|
|
057681b33c | ||
|
|
50d687a1f2 | ||
|
|
02d28d273a | ||
|
|
5ad44d62f6 | ||
|
|
c24889096e | ||
|
|
1554a522cc | ||
|
|
717a0e9c50 | ||
|
|
34016cfb27 | ||
|
|
b83d3df084 | ||
|
|
902fd8aebd | ||
|
|
a137760c89 | ||
|
|
37207f1911 | ||
|
|
060c57b4aa | ||
|
|
0a55a3c554 | ||
|
|
40642c0d35 | ||
|
|
ee358c251b | ||
|
|
b333308b2d | ||
|
|
8415c8d2a2 | ||
|
|
f273eed840 | ||
|
|
d62dff95b6 | ||
|
|
036e501e58 | ||
|
|
35f8eef1bc | ||
|
|
9a57690e7d | ||
|
|
2841563e6c | ||
|
|
cd73dc6cfd | ||
|
|
d2e4a95aed | ||
|
|
1abe693d73 | ||
|
|
fcda55944a | ||
|
|
22514b59f2 | ||
|
|
0f14a84934 | ||
|
|
cd0025b62f | ||
|
|
d9b970dd62 | ||
|
|
982290fa42 | ||
|
|
b6f2c1cef7 | ||
|
|
7b68fee056 | ||
|
|
9fe6a04715 | ||
|
|
402128b6a2 | ||
|
|
2fa6d0ecc5 | ||
|
|
83957145a7 | ||
|
|
0f8af9be30 | ||
|
|
db21abdef0 | ||
|
|
499f50ffd6 | ||
|
|
5fc227b06c | ||
|
|
051952600d | ||
|
|
0d8232b9db | ||
|
|
619979a716 | ||
|
|
1454e04b7f | ||
|
|
d53b24cfc7 | ||
|
|
f63ecb6697 | ||
|
|
2b2302a279 | ||
|
|
e236713d9f | ||
|
|
e590f22de6 | ||
|
|
df44695ea7 | ||
|
|
7f0c94d480 | ||
|
|
f9c36b2f3b | ||
|
|
a77c246d38 | ||
|
|
a3e40cb8b0 | ||
|
|
09da70c303 | ||
|
|
df338c4003 | ||
|
|
1f5d29a3ac | ||
|
|
41c491ae4c | ||
|
|
b268838479 | ||
|
|
bf08fedf8e | ||
|
|
7f9711ed9c | ||
|
|
8956964ee5 | ||
|
|
ebb0273584 | ||
|
|
3c3f23222b | ||
|
|
4e323342ca | ||
|
|
dff33f16e3 | ||
|
|
6fe83bfa47 | ||
|
|
dae9edef4c | ||
|
|
19e5941075 | ||
|
|
033b5ecd3d | ||
|
|
77c8e3f413 | ||
|
|
ab6804f8cc | ||
|
|
2ad241159e | ||
|
|
4eafef5025 | ||
|
|
e1471efa42 | ||
|
|
b51f36a9fe | ||
|
|
07d5f49963 | ||
|
|
5f6c803b21 | ||
|
|
56c8344fd2 | ||
|
|
465d4da567 | ||
|
|
151a530d15 | ||
|
|
da6045be3f | ||
|
|
23072924c4 | ||
|
|
f4bb97018e | ||
|
|
343501011f | ||
|
|
600734790c | ||
|
|
ab028e80eb | ||
|
|
a62771e2b4 | ||
|
|
32ebf6b85b | ||
|
|
b6bf851af4 | ||
|
|
c86af5cea5 | ||
|
|
83d158ad07 | ||
|
|
0c0f4881cb | ||
|
|
81df05533c | ||
|
|
8f7dd71fda | ||
|
|
96af8d4c02 | ||
|
|
8be14d5ac6 | ||
|
|
89fcd6a1b7 | ||
|
|
45d2e807db |
No files matched your search
@@ -96,6 +96,16 @@ Exceptions are allowed for migration and package-helper scripts where the helper
|
||||
- Do not add `aliases` to new menu entries. Aliases are reserved for
|
||||
established alternate names users already type, kept for compatibility.
|
||||
|
||||
# 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`.
|
||||
- `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`).
|
||||
- `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/` - default configs copied to `~/.config/`
|
||||
|
||||
@@ -71,6 +71,7 @@ The manual lives in [`manual/`](manual/), which is its authoritative source.
|
||||
- [Omarchy on...](manual/49-omarchy-on.md)
|
||||
- [Dual Boot Install](manual/50-dual-boot-install.md)
|
||||
- [Unattended Installs](manual/51-unattended-installs.md)
|
||||
- [China](manual/52-china.md)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ Run `omarchy-restart-shell` after making changes to QML files.
|
||||
- Panel / overlay / menu plugins must expose `open(payloadJson)` and
|
||||
`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
|
||||
|
||||
- `bin/omarchy-shell` is the canonical IPC entry point. It forwards to
|
||||
@@ -45,3 +49,7 @@ unicode characters. Agent file-editing tools can strip multi-byte codepoints
|
||||
in some positions — do **not** rewrite widget files wholesale through those
|
||||
tools. For glyph fixes, make a targeted edit with the surrounding context, or
|
||||
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`.
|
||||
@@ -1,8 +0,0 @@
|
||||
[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
|
||||
@@ -46,6 +46,7 @@ GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
|
||||
GROUP_DESCRIPTIONS[default]="Default application selection"
|
||||
GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
|
||||
GROUP_DESCRIPTIONS[disk]="Disk performance helpers"
|
||||
GROUP_DESCRIPTIONS[dictation]="Dictation recording"
|
||||
GROUP_DESCRIPTIONS[display]="Display and text scaling"
|
||||
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
|
||||
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
|
||||
@@ -58,6 +59,7 @@ GROUP_DESCRIPTIONS[hw]="Hardware detection and controls"
|
||||
GROUP_DESCRIPTIONS[hyprland]="Hyprland window, monitor, and toggle controls"
|
||||
GROUP_DESCRIPTIONS[install]="Optional software installers"
|
||||
GROUP_DESCRIPTIONS[installed]="Installed optional service checks"
|
||||
GROUP_DESCRIPTIONS[language]="System language"
|
||||
GROUP_DESCRIPTIONS[launch]="Application launchers"
|
||||
GROUP_DESCRIPTIONS[menu]="Omarchy menu commands"
|
||||
GROUP_DESCRIPTIONS[migrate]="Migration runner"
|
||||
@@ -73,6 +75,7 @@ GROUP_DESCRIPTIONS[plymouth]="Plymouth boot theme management"
|
||||
GROUP_DESCRIPTIONS[power]="Power supply detection"
|
||||
GROUP_DESCRIPTIONS[powerprofiles]="Power profile management"
|
||||
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[reminder]="Desktop notification reminders"
|
||||
GROUP_DESCRIPTIONS[remove]="Removal workflows"
|
||||
|
||||
@@ -85,7 +85,7 @@ finish() {
|
||||
fail() {
|
||||
if [[ $events == "true" ]]; then
|
||||
printf '@@omarchy error %s\n' "$1"
|
||||
omarchy-notification-send -g -u critical "Couldn't add the $name account" "$1"
|
||||
omarchy-notification-send -g -u critical "$(omarchy-text "Couldn't add the %1 account" "$name")" "$1"
|
||||
else
|
||||
echo "$1" >&2
|
||||
fi
|
||||
@@ -113,9 +113,9 @@ interruptible() {
|
||||
}
|
||||
|
||||
if omarchy-cmd-missing "$provider"; then
|
||||
say "Installing the $name CLI…"
|
||||
say "$(omarchy-text "Installing the %1 CLI…" "$name")"
|
||||
if [[ $events == "true" ]]; then
|
||||
mise use -g "$package" >/dev/null 2>&1 || fail "Installing the $name CLI didn't finish."
|
||||
mise use -g "$package" >/dev/null 2>&1 || fail "$(omarchy-text "Installing the %1 CLI didn't finish." "$name")"
|
||||
else
|
||||
mise use -g "$package" || exit 1
|
||||
fi
|
||||
@@ -150,26 +150,26 @@ if [[ -n $reauth ]]; then
|
||||
else
|
||||
account_home=$(omarchy-agent-account-state homes "$provider" | awk -F'\t' -v id="$reauth" '$1 == id { print $2 }')
|
||||
fi
|
||||
[[ -n $account_home ]] || fail "No $name account named $reauth."
|
||||
[[ -n $account_home ]] || fail "$(omarchy-text "No %1 account named %2." "$name" "$reauth")"
|
||||
|
||||
if [[ $account_home == "$HOME/.$provider" ]]; then
|
||||
say "Sign in to $name again in the browser window that opens."
|
||||
login || fail "The $name sign-in didn't finish."
|
||||
say "$(omarchy-text "Sign in to %1 again in the browser window that opens." "$name")"
|
||||
login || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
else
|
||||
say "Sign in again as this account in the private window that opens."
|
||||
say "$(omarchy-text "Sign in again as this account in the private window that opens.")"
|
||||
private_browser
|
||||
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "The $name sign-in didn't finish."
|
||||
interruptible env BROWSER="$browser" "$home_env=$account_home" "${login_command[@]}" || fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
fi
|
||||
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
finish "Signed in to $name again."
|
||||
finish "$(omarchy-text "Signed in to %1 again." "$name")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 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.
|
||||
if ! primary_signed_in "$provider"; then
|
||||
say "Sign in to $name in the browser window that opens."
|
||||
say "$(omarchy-text "Sign in to %1 in the browser window that opens." "$name")"
|
||||
if login && primary_signed_in "$provider"; then
|
||||
# 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
|
||||
@@ -179,9 +179,9 @@ if ! primary_signed_in "$provider"; then
|
||||
printf '%s\n' "$provider" >"$HOME/.config/omarchy/defaults/agent"
|
||||
fi
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
finish "Signed in to $name."
|
||||
finish "$(omarchy-text "Signed in to %1." "$name")"
|
||||
else
|
||||
fail "The $name sign-in didn't finish."
|
||||
fail "$(omarchy-text "The %1 sign-in didn't finish." "$name")"
|
||||
fi
|
||||
else
|
||||
if [[ -z $label && $events == "false" && -t 0 ]]; then
|
||||
@@ -189,7 +189,7 @@ else
|
||||
fi
|
||||
|
||||
if [[ $events == "true" ]]; then
|
||||
say "Sign in as the account you're adding in the private window that opens."
|
||||
say "$(omarchy-text "Sign in as the account you're adding in the private window that opens.")"
|
||||
else
|
||||
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
|
||||
fi
|
||||
|
||||
home=$(omarchy-agent-account-state begin "$provider") || fail "Couldn't set up a home for the new account."
|
||||
home=$(omarchy-agent-account-state begin "$provider") || fail "$(omarchy-text "Couldn't set up a home for the new account.")"
|
||||
|
||||
private_browser
|
||||
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")
|
||||
omarchy-agent-usage-update --force "$provider" >/dev/null 2>&1 &
|
||||
if [[ $events == "true" ]]; then
|
||||
finish "Added $added."
|
||||
finish "$(omarchy-text "Added %1." "$added")"
|
||||
else
|
||||
finish "Added $added."
|
||||
finish "$(omarchy-text "Added %1." "$added")"
|
||||
echo "Switch to it with: omarchy agent account use $provider $(jq -r .id <<<"$account")"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -73,6 +73,11 @@ class AccountError(Exception):
|
||||
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
|
||||
|
||||
|
||||
@@ -335,12 +340,12 @@ def begin(provider: str) -> Path:
|
||||
def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
|
||||
pending = pending.resolve()
|
||||
if pending.parent != (accounts_root() / provider / ".pending").resolve():
|
||||
raise AccountError(f"{pending} is not a pending {PROVIDERS[provider]['name']} login")
|
||||
raise AccountError(text("%1 is not a pending %2 login", pending, PROVIDERS[provider]['name']))
|
||||
|
||||
found = identity(provider, pending)
|
||||
if not found["accountId"]:
|
||||
shutil.rmtree(pending, ignore_errors=True)
|
||||
raise AccountError("The login didn't finish, so no account was added.")
|
||||
raise AccountError(text("The login didn't finish, so no account was added."))
|
||||
|
||||
with Locked(provider) as registry:
|
||||
# Each home is asked who it is now, not trusted from when it was added: a
|
||||
@@ -350,10 +355,10 @@ def register(provider: str, label: str, pending: Path) -> dict[str, Any]:
|
||||
for account in registry["accounts"]:
|
||||
if account.get("accountId") == found["accountId"]:
|
||||
shutil.rmtree(pending, ignore_errors=True)
|
||||
raise AccountError(
|
||||
f"That's {account['label']} ({found['email'] or 'already added'}). "
|
||||
"Try again, and sign in as the other account in the private window."
|
||||
)
|
||||
raise AccountError(text(
|
||||
"That's %1 (%2). Try again, and sign in as the other account in the private window.",
|
||||
account["label"], found["email"] or text("already added"),
|
||||
))
|
||||
|
||||
label = label.strip() or found["email"].split("@")[0] or "Account"
|
||||
account_id = unique_id(provider, registry, label)
|
||||
@@ -395,11 +400,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]:
|
||||
label = label.strip()
|
||||
if not label:
|
||||
raise AccountError("Give the account a name")
|
||||
raise AccountError(text("Give the account a name"))
|
||||
with Locked(provider) as registry:
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
new_id = unique_id(provider, registry, label, keep=account)
|
||||
if not account.get("primary") and not account.get("home"):
|
||||
account["home"] = str(account_home(provider, account))
|
||||
@@ -428,7 +433,7 @@ def use(provider: str, target: str) -> tuple[dict[str, Any], dict[str, Any]]:
|
||||
target = ids[(ids.index(previous["id"]) + 1) % len(ids)]
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
registry["active"] = account["id"]
|
||||
registry["alert"] = ""
|
||||
if not account.get("primary"):
|
||||
@@ -456,12 +461,12 @@ def remove(provider: str, target: str) -> dict[str, Any]:
|
||||
with Locked(provider) as registry:
|
||||
account = find(registry, target)
|
||||
if not account:
|
||||
raise AccountError(f"No {PROVIDERS[provider]['name']} account named {target}")
|
||||
raise AccountError(text("No %1 account named %2", PROVIDERS[provider]['name'], target))
|
||||
if account.get("primary"):
|
||||
raise AccountError(f"{account['label']} is the login in {primary_home(provider)}; it can't be removed here.")
|
||||
raise AccountError(text("%1 is the login in %2; it can't be removed here.", account["label"], primary_home(provider)))
|
||||
home = account_home(provider, account)
|
||||
if home_in_use(provider, home):
|
||||
raise AccountError(f"A running {PROVIDERS[provider]['name']} session uses {account['label']}. Quit it first, then remove the account.")
|
||||
raise AccountError(text("A running %1 session uses %2. Quit it first, then remove the account.", PROVIDERS[provider]['name'], account["label"]))
|
||||
# Only the account's own directory goes. Its links point into the primary
|
||||
# home, and rmtree removes a link without following it.
|
||||
if home.resolve().parent == (accounts_root() / provider).resolve():
|
||||
@@ -474,14 +479,14 @@ def remove(provider: str, target: str) -> dict[str, Any]:
|
||||
|
||||
def set_mode(provider: str, mode: str, threshold: str | None) -> dict[str, Any]:
|
||||
if mode not in SWITCH_MODES:
|
||||
raise AccountError("Switch mode is manual or auto")
|
||||
raise AccountError(text("Switch mode is manual or auto"))
|
||||
with Locked(provider) as registry:
|
||||
registry["switch"] = mode
|
||||
if threshold is not None:
|
||||
try:
|
||||
registry["threshold"] = max(50, min(100, int(threshold)))
|
||||
except ValueError:
|
||||
raise AccountError("The threshold is a percentage between 50 and 100")
|
||||
raise AccountError(text("The threshold is a percentage between 50 and 100"))
|
||||
registry["alert"] = ""
|
||||
return dict(registry)
|
||||
|
||||
@@ -556,10 +561,10 @@ def duration(delta: dt.timedelta) -> str:
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
days, hours = divmod(hours, 24)
|
||||
if days:
|
||||
return f"{days}d {hours}h"
|
||||
return text("%1d %2h", days, hours)
|
||||
if hours:
|
||||
return f"{hours}h {minutes}m"
|
||||
return f"{minutes}m"
|
||||
return text("%1h %2m", hours, minutes)
|
||||
return text("%1m", minutes)
|
||||
|
||||
|
||||
# The whole switching policy, with no I/O: given the registry and the per
|
||||
@@ -659,21 +664,23 @@ def autoswitch(provider: str, now: dt.datetime | None = None) -> bool:
|
||||
threshold = registry["threshold"]
|
||||
if decision["action"] == "exhausted":
|
||||
soonest = decision["soonest"]
|
||||
when = f" {soonest[1]['label']} resets in {duration(soonest[0] - now)}." if soonest else ""
|
||||
notify(f"All {name} accounts are over {threshold}%", f"Staying on {source['label']}.{when}")
|
||||
staying = text("Staying on %1.", source["label"])
|
||||
if soonest:
|
||||
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
|
||||
|
||||
target = decision["to"]
|
||||
headline = f"{source['label']} is at {round(decision['fromPeak'])}% of a {name} limit"
|
||||
headline = text("%1 is at %2% of a %3 limit", source["label"], round(decision["fromPeak"]), name)
|
||||
if decision["action"] == "switch":
|
||||
registry["active"] = target["id"]
|
||||
if not target.get("primary"):
|
||||
link_shared(provider, account_home(provider, target))
|
||||
notify(headline, f"New {name} sessions now use {target['label']} ({round(decision['toPeak'])}%). Running sessions stay on {source['label']}.")
|
||||
notify(headline, text("New %1 sessions now use %2 (%3%). Running sessions stay on %4.", name, target["label"], round(decision["toPeak"]), source["label"]))
|
||||
return True
|
||||
notify(
|
||||
headline,
|
||||
f"{target['label']} is at {round(decision['toPeak'])}%. Click to switch new sessions to it.",
|
||||
text("%1 is at %2%. Click to switch new sessions to it.", target["label"], round(decision["toPeak"])),
|
||||
["omarchy-agent-account-use", provider, target["id"]],
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -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
|
||||
echo "New $name sessions already use $active."
|
||||
else
|
||||
message="Running sessions stay on $previous."
|
||||
message=$(omarchy-text "Running sessions stay on %1." "$previous")
|
||||
echo "New $name sessions now use $active. $message"
|
||||
omarchy-notification-send -g "New $name sessions now use $active" "$message"
|
||||
omarchy-notification-send -g "$(omarchy-text "New %1 sessions now use %2" "$name" "$active")" "$message"
|
||||
# The usage record's own limits describe the active account.
|
||||
omarchy-agent-usage-update --limits-only "$1" >/dev/null 2>&1 &
|
||||
fi
|
||||
@@ -22,6 +22,7 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
@@ -32,6 +33,11 @@ from typing import Any
|
||||
|
||||
AGENT_ID = "claude"
|
||||
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."
|
||||
USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage"
|
||||
PROBE_MIN_INTERVAL_SECONDS = 15
|
||||
@@ -989,11 +995,12 @@ def probe_limits(access_token: str) -> dict[str, Any]:
|
||||
except urllib.error.HTTPError as error:
|
||||
retry_after = error.headers.get("retry-after", "") if error.headers else ""
|
||||
if error.code == 429:
|
||||
help_text = "Anthropic's usage endpoint is rate limiting checks right now" + (
|
||||
f" (retry after {retry_after}s)" if retry_after else ""
|
||||
) + ". Local Claude Code stats are still shown."
|
||||
if retry_after:
|
||||
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)
|
||||
else:
|
||||
help_text = text("Anthropic's usage endpoint is rate limiting checks right now. Local Claude Code stats are still shown.")
|
||||
else:
|
||||
help_text = f"Anthropic's usage endpoint returned status {error.code}. Local Claude Code stats are still shown."
|
||||
help_text = text("Anthropic's usage endpoint returned status %1. Local Claude Code stats are still shown.", error.code)
|
||||
return {"ok": False, "helpText": help_text}
|
||||
except Exception:
|
||||
# A transport failure reached no server at all — no route, no DNS. Any
|
||||
|
||||
@@ -6,8 +6,8 @@ wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle >/dev/null
|
||||
|
||||
if wpctl get-volume @DEFAULT_AUDIO_SOURCE@ | grep -q MUTED; then
|
||||
omarchy-brightness-keyboard-mute on
|
||||
omarchy-osd -i microphone-muted -m "Microphone muted"
|
||||
omarchy-osd -i microphone-muted -m "$(omarchy-text "Microphone muted")"
|
||||
else
|
||||
omarchy-brightness-keyboard-mute off
|
||||
omarchy-osd -i microphone -m "Microphone on"
|
||||
omarchy-osd -i microphone -m "$(omarchy-text "Microphone on")"
|
||||
fi
|
||||
@@ -13,7 +13,7 @@ sinks=$(timeout 2 pactl -f json list sinks |
|
||||
sinks_count=$(jq 'length' <<<"$sinks")
|
||||
|
||||
if (( sinks_count == 0 )); then
|
||||
omarchy-osd -m "No audio devices found"
|
||||
omarchy-osd -m "$(omarchy-text "No audio devices found")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -13,5 +13,5 @@ fi
|
||||
|
||||
level=$1
|
||||
|
||||
omarchy-notification-send -g -u critical "Time to recharge!" "Battery is down to ${level}%" -i battery-caution -t 30000
|
||||
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-hook battery-low "$level"
|
||||
@@ -129,9 +129,9 @@ if [[ $charge_holding == "true" ]]; then
|
||||
threshold_label="${threshold_end}%"
|
||||
fi
|
||||
|
||||
echo "Battery ${percentage}% · Holding at ${threshold_label} · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · Holding at %2 · %3W / %4Wh" "$percentage" "$threshold_label" "$power_rate" "$capacity"
|
||||
elif [[ $state == "charging" ]]; then
|
||||
echo "Battery ${percentage}% · ${time_remaining} to full · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · %2 to full · %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
|
||||
else
|
||||
echo "Battery ${percentage}% · ${time_remaining} left · ${power_rate}W / ${capacity}Wh"
|
||||
omarchy-text "Battery %1% · %2 left · %3W / %4Wh" "$percentage" "$time_remaining" "$power_rate" "$capacity"
|
||||
fi
|
||||
@@ -23,7 +23,7 @@ SELECTION=$(slurp 2>/dev/null)
|
||||
RESULT=$(grim -g "$SELECTION" - | zbarimg -q --raw -Sdisable -Sqrcode.enable - 2>/dev/null)
|
||||
|
||||
if [[ -z $RESULT ]]; then
|
||||
omarchy-notification-send -g -u critical "No QR code found" "Select a region containing a QR code"
|
||||
omarchy-notification-send -g -u critical "$(omarchy-text "No QR code found")" "$(omarchy-text "Select a region containing a QR code")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -33,4 +33,4 @@ fi
|
||||
# the notification history, and an unmarked copy would be retained by clipboard
|
||||
# history. Pasting still works; only the recorded copy is given up.
|
||||
printf '%s' "$RESULT" | wl-copy --sensitive
|
||||
omarchy-notification-send -g "QR code copied to clipboard"
|
||||
omarchy-notification-send -g "$(omarchy-text "QR code copied to clipboard")"
|
||||
@@ -23,7 +23,7 @@
|
||||
OUTPUT_DIR="${OMARCHY_SCREENRECORD_DIR:-${XDG_VIDEOS_DIR:-$HOME/Videos}}"
|
||||
|
||||
if [[ ! -d $OUTPUT_DIR ]]; then
|
||||
omarchy-notification-send -u critical -t 3000 "Screen recording directory does not exist: $OUTPUT_DIR"
|
||||
omarchy-notification-send -u critical -t 3000 "$(omarchy-text "Screen recording directory does not exist: %1" "$OUTPUT_DIR")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -76,7 +76,7 @@ start_webcam_overlay() {
|
||||
if [[ -z $WEBCAM_DEVICE ]]; then
|
||||
WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p')
|
||||
if [[ -z $WEBCAM_DEVICE ]]; then
|
||||
omarchy-notification-send -u critical -t 3000 "No webcam devices found"
|
||||
omarchy-notification-send -u critical -t 3000 "$(omarchy-text "No webcam devices found")"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
@@ -231,7 +231,7 @@ stop_screenrecording() {
|
||||
|
||||
if pgrep -f "^gpu-screen-recorder" >/dev/null; then
|
||||
pkill -9 -f "^gpu-screen-recorder"
|
||||
omarchy-notification-send -u critical -t 5000 "Screen recording error" "Recording process had to be force-killed. Video may be corrupted."
|
||||
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.")"
|
||||
else
|
||||
finalize_recording
|
||||
local filename=$(cat "$RECORDING_FILE" 2>/dev/null)
|
||||
@@ -241,7 +241,7 @@ stop_screenrecording() {
|
||||
# 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
|
||||
|
||||
omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \
|
||||
omarchy-notification-send "$(omarchy-text "Screen recording saved")" "$(omarchy-text "Open with Super + Alt + , (or click this)")" \
|
||||
-t 10000 --image "${preview:-$filename}" \
|
||||
--exec mpv -- "$filename"
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
mapfile -t devices < <(omarchy-capture-webcam-list)
|
||||
if (( ${#devices[@]} == 0 )); then
|
||||
omarchy-notification-send "No webcam devices found" -u critical -t 3000
|
||||
omarchy-notification-send "$(omarchy-text "No webcam devices found")" -u critical -t 3000
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -23,4 +23,4 @@ TEXT=$(grim -g "$SELECTION" - | tesseract stdin stdout --oem 1 --psm 6 -l "${OMA
|
||||
[[ -z $TEXT ]] && exit 1
|
||||
|
||||
printf "%s" "$TEXT" | wl-copy
|
||||
omarchy-notification-send -g "Copied text from selection to clipboard"
|
||||
omarchy-notification-send -g "$(omarchy-text "Copied text from selection to clipboard")"
|
||||
@@ -18,7 +18,7 @@ copy_url() {
|
||||
|
||||
[[ -n $url ]] || return 1
|
||||
printf '%s' "$url" | wl-copy --type text/plain
|
||||
omarchy-notification-send -g "URL copied to clipboard"
|
||||
omarchy-notification-send -g "$(omarchy-text "URL copied to clipboard")"
|
||||
}
|
||||
|
||||
reply_copied() {
|
||||
|
||||
@@ -87,7 +87,7 @@ download_url() {
|
||||
|
||||
# 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
|
||||
omarchy-notification-send -u critical -g "No video found for download" "$url"
|
||||
omarchy-notification-send -u critical -g "$(omarchy-text "No video found for download")" "$url"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -148,7 +148,7 @@ download_url() {
|
||||
# toast would exit before the thumbnail cleanup below is ever scheduled.
|
||||
# `--` keeps mpv from parsing a leading-dash filename as an option; the path
|
||||
# is one discrete argument, so it never reaches a shell.
|
||||
omarchy-notification-send -g "Download complete" "$title" \
|
||||
omarchy-notification-send -g "$(omarchy-text "Download complete")" "$title" \
|
||||
-t 10000 --image "${preview:-$filepath}" \
|
||||
--exec mpv -- "$filepath" || true
|
||||
|
||||
@@ -160,7 +160,7 @@ download_url() {
|
||||
rm -f "$preview"
|
||||
) &
|
||||
else
|
||||
omarchy-notification-send -u critical -g "Download failed" "$url"
|
||||
omarchy-notification-send -u critical -g "$(omarchy-text "Download failed")" "$url"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -40,8 +40,8 @@ announce() {
|
||||
omarchy-notification-send \
|
||||
--urgency critical \
|
||||
--glyph "$CRASH_GLYPH" \
|
||||
"Process crashed: $comm" \
|
||||
"Click to diagnose with AI" \
|
||||
"$(omarchy-text "Process crashed: %1" "$comm")" \
|
||||
"$(omarchy-text "Click to diagnose with AI")" \
|
||||
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
|
||||
}
|
||||
|
||||
|
||||
@@ -48,4 +48,4 @@ fi
|
||||
|
||||
env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default browser"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default browser" "$name")"
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/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
|
||||
@@ -59,4 +59,4 @@ fi
|
||||
mkdir -p "$(dirname "$editor_file")"
|
||||
printf '%s\n' "$editor" >"$editor_file"
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default editor"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default editor" "$name")"
|
||||
@@ -48,4 +48,4 @@ cat >~/.config/xdg-terminals.list <<EOF
|
||||
$desktop_id
|
||||
EOF
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default terminal"
|
||||
omarchy-notification-send -g $glyph "$(omarchy-text "%1 is now the default terminal" "$name")"
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/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>&-
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/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
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/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"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/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
|
||||
@@ -169,7 +169,7 @@ set_terminal_size() {
|
||||
[[ $prev_id =~ ^[0-9]+$ ]] && replace=(-r "$prev_id")
|
||||
local new_id
|
||||
new_id="$(omarchy-notification-send \
|
||||
"Restart Foot to apply the new terminal font size" \
|
||||
"$(omarchy-text "Restart Foot to apply the new terminal font size")" \
|
||||
"${replace[@]}" -p 2>/dev/null)" || true
|
||||
[[ $new_id =~ ^[0-9]+$ ]] && printf '%s\n' "$new_id" >"$id_file"
|
||||
fi
|
||||
|
||||
@@ -74,11 +74,11 @@ XML
|
||||
omarchy-restart-shell
|
||||
|
||||
if pgrep -x ghostty; then
|
||||
omarchy-notification-send -g "You must restart Ghostty to see font change"
|
||||
omarchy-notification-send -g "$(omarchy-text "You must restart Ghostty to see font change")"
|
||||
fi
|
||||
|
||||
if pgrep -x foot; then
|
||||
omarchy-notification-send -g "You must restart Foot to see font change"
|
||||
omarchy-notification-send -g "$(omarchy-text "You must restart Foot to see font change")"
|
||||
fi
|
||||
|
||||
omarchy-hook font-set "$font_name"
|
||||
@@ -10,7 +10,7 @@ if (( $# == 0 )); then
|
||||
mapfile -t cores < <(omarchy-games-retro-cores)
|
||||
|
||||
if (( ${#cores[@]} == 0 )); then
|
||||
omarchy-notification-send -g "No RetroArch cores found" "/usr/lib/libretro"
|
||||
omarchy-notification-send -g "$(omarchy-text "No RetroArch cores found")" "/usr/lib/libretro"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -69,4 +69,4 @@ EOF
|
||||
chmod +x "$desktop_file"
|
||||
update-desktop-database "$desktop_dir" &>/dev/null || true
|
||||
|
||||
omarchy-notification-send -g "$game_name installed" "Start it with Super + Space"
|
||||
omarchy-notification-send -g "$(omarchy-text "%1 installed" "$game_name")" "$(omarchy-text "Start it with Super + Space")"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Match the Dell XPS 13 DX13260 with an Intel Panther Lake GPU.
|
||||
|
||||
omarchy-hw-match "DX13260" && omarchy-hw-intel-ptl
|
||||
@@ -16,7 +16,7 @@ wake() {
|
||||
on() {
|
||||
if omarchy-hyprland-toggle-enabled $TOGGLE; then
|
||||
omarchy-hyprland-toggle $TOGGLE off
|
||||
omarchy-notification-send -g "Laptop display enabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Laptop display enabled")"
|
||||
fi
|
||||
|
||||
wake
|
||||
@@ -24,25 +24,25 @@ on() {
|
||||
|
||||
off() {
|
||||
if [[ -z $INTERNAL ]]; then
|
||||
omarchy-notification-send -g "No laptop display found"
|
||||
omarchy-notification-send -g "$(omarchy-text "No laptop display found")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The name is written into generated Lua below, so only a plain connector
|
||||
# name may pass; anything else could execute on the next reload.
|
||||
if [[ ! $INTERNAL =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||
omarchy-notification-send -g "$(omarchy-text "Refusing unsafe monitor name")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! omarchy-hyprland-monitor-external-active; then
|
||||
omarchy-notification-send -g "Can't disable the only active display"
|
||||
omarchy-notification-send -g "$(omarchy-text "Can't disable the only active display")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if omarchy-hyprland-toggle-disabled $TOGGLE && omarchy-hyprland-toggle-disabled $MIRROR_TOGGLE; then
|
||||
printf 'hl.monitor({ output = "%s", disabled = true })\n' "$INTERNAL" >"$TOGGLE_FLAG"
|
||||
omarchy-notification-send -g "Laptop display disabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Laptop display disabled")"
|
||||
hyprctl reload
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ EXTERNAL=$(hyprctl monitors -j | jq -r '.[] | select(.name | test("^(eDP|LVDS|DS
|
||||
|
||||
on() {
|
||||
if [[ -z $EXTERNAL ]]; then
|
||||
omarchy-notification-send -g "No external monitors found for mirror"
|
||||
omarchy-notification-send -g "$(omarchy-text "No external monitors found for mirror")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $INTERNAL ]]; then
|
||||
omarchy-notification-send -g "No laptop monitor found to mirror"
|
||||
omarchy-notification-send -g "$(omarchy-text "No laptop monitor found to mirror")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -26,7 +26,7 @@ on() {
|
||||
# names may pass; a user-created headless output can carry any name.
|
||||
for output in "$INTERNAL" "$EXTERNAL"; do
|
||||
if [[ ! $output =~ ^[A-Za-z0-9._-]+$ ]]; then
|
||||
omarchy-notification-send -g "Refusing unsafe monitor name"
|
||||
omarchy-notification-send -g "$(omarchy-text "Refusing unsafe monitor name")"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
@@ -35,7 +35,7 @@ on() {
|
||||
|
||||
if omarchy-hyprland-toggle-disabled $TOGGLE; then
|
||||
printf 'hl.monitor({ output = "%s", mode = "preferred", position = "auto", scale = 1, mirror = "%s" })\n' "$EXTERNAL" "$INTERNAL" >"$TOGGLE_FLAG"
|
||||
omarchy-notification-send -g "Mirroring enabled ($EXTERNAL)"
|
||||
omarchy-notification-send -g "$(omarchy-text "Mirroring enabled (%1)" "$EXTERNAL")"
|
||||
hyprctl reload
|
||||
fi
|
||||
}
|
||||
@@ -43,7 +43,7 @@ on() {
|
||||
off() {
|
||||
if omarchy-hyprland-toggle-enabled $TOGGLE; then
|
||||
omarchy-hyprland-toggle $TOGGLE off
|
||||
omarchy-notification-send -g "Extended mode restored"
|
||||
omarchy-notification-send -g "$(omarchy-text "Extended mode restored")"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
# omarchy:summary=Toggle single-window square aspect ratio.
|
||||
|
||||
case $(omarchy-hyprland-toggle single-window-aspect-ratio) in
|
||||
on) omarchy-notification-send -g "Enable single-window square aspect ratio" ;;
|
||||
off) omarchy-notification-send -g "Disable single-window square aspect ratio" ;;
|
||||
on) omarchy-notification-send -g "$(omarchy-text "Enable single-window square aspect ratio")" ;;
|
||||
off) omarchy-notification-send -g "$(omarchy-text "Disable single-window square aspect ratio")" ;;
|
||||
esac
|
||||
@@ -40,14 +40,14 @@ notify_missing_width() {
|
||||
local key="$1"
|
||||
local workspace="$2"
|
||||
|
||||
omarchy-notification-send -g "No saved width found for $key on workspace $workspace" "Use Super + Alt + Home to save one for this workspace."
|
||||
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."
|
||||
}
|
||||
|
||||
notify_saved_width() {
|
||||
local key="$1"
|
||||
local workspace="$2"
|
||||
|
||||
omarchy-notification-send -g "Saved width for $key on workspace $workspace" "Restore using Super + Home on this workspace."
|
||||
omarchy-notification-send -g "$(omarchy-text "Saved width for %1 on workspace %2" "$key" "$workspace")" "Restore using Super + Home on this workspace."
|
||||
}
|
||||
|
||||
hypr_dispatch() {
|
||||
|
||||
@@ -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 keyword workspace "$ACTIVE_WORKSPACE, layout:$NEW_LAYOUT"
|
||||
omarchy-notification-send -g "Workspace layout set to $NEW_LAYOUT"
|
||||
omarchy-notification-send -g "$(omarchy-text "Workspace layout set to %1" "$NEW_LAYOUT")"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/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" "$@"
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/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
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/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
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/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
|
||||
@@ -11,5 +11,5 @@ if [[ -z $path ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
omarchy-notification-send -u low "Editing config file" "$path"
|
||||
omarchy-notification-send -u low "$(omarchy-text "Editing config file")" "$path"
|
||||
exec omarchy-launch-editor "$path"
|
||||
@@ -16,7 +16,7 @@ terminal=$(xdg-terminal-exec --print-id)
|
||||
case $terminal in
|
||||
*Alacritty* | *ghostty* | *foot* | *kitty*) ;;
|
||||
*)
|
||||
omarchy-notification-send -g ✋ "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty"
|
||||
omarchy-notification-send -g ✋ "$(omarchy-text "Screensaver only runs in Alacritty, Foot, Ghostty, or Kitty")"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
# 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.
|
||||
run_shell() {
|
||||
QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
|
||||
local startup_background
|
||||
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" &
|
||||
shell_pid=$!
|
||||
|
||||
|
||||
@@ -332,6 +332,7 @@ dynamic_bindings() {
|
||||
static_bindings() {
|
||||
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 "CTRL SHIFT,SPACE,Switch input language,exec,fcitx5-remote -t"
|
||||
}
|
||||
|
||||
# Actions Omarchy binds to a second chord meant as an alternative, named one at
|
||||
@@ -533,7 +534,7 @@ output_binding_records_uncached() {
|
||||
|
||||
keybindings_cache_key() {
|
||||
{
|
||||
printf 'v14\n'
|
||||
printf 'v15\n'
|
||||
hyprctl devices 2>/dev/null | grep -F 'active keymap:'
|
||||
hyprctl binds 2>/dev/null
|
||||
} | sha256sum | awk '{ print $1 }'
|
||||
|
||||
@@ -28,7 +28,7 @@ rows=$(jq -r --arg icon "$PLUGIN_ICON" \
|
||||
". as \$plugins
|
||||
| .[] | select($filter)
|
||||
| \$icon + \"\\t\" + .name + \"\\t\" + .id" <<<"$plugins")
|
||||
[[ -n $rows ]] || { omarchy-notification-send "No plugin to ${1}"; exit 0; }
|
||||
[[ -n $rows ]] || { omarchy-notification-send "$(omarchy-text "No plugin to $1")"; exit 0; }
|
||||
|
||||
selection=$(omarchy-menu-select "${1^} plugin" <<<"$rows") || exit 0
|
||||
[[ -n $selection ]] || exit 0
|
||||
|
||||
@@ -33,7 +33,7 @@ else
|
||||
picked=$(omarchy-file-select "${select_args[@]}") || status=$?
|
||||
|
||||
if ((${status:-0} > 1)); then
|
||||
omarchy-notification-send -g "" -u critical "Could not share" "The file chooser did not open"
|
||||
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not share")" "$(omarchy-text "The file chooser did not open")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ set -e
|
||||
timezone=$(timedatectl list-timezones | omarchy-menu-select "Set timezone" -- --width 520 --maxheight 520) || exit 1
|
||||
sudo timedatectl set-timezone "$timezone"
|
||||
omarchy-shell -q omarchy.clock refresh
|
||||
omarchy-notification-send "Timezone is now set to $timezone"
|
||||
omarchy-notification-send "$(omarchy-text "Timezone is now set to %1" "$timezone")"
|
||||
@@ -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)
|
||||
|
||||
if (( pending_count == 1 )); then
|
||||
message="Click to run 1 pending migration."
|
||||
message=$(omarchy-text "Click to run 1 pending migration.")
|
||||
else
|
||||
message="Click to run $pending_count pending migrations."
|
||||
message=$(omarchy-text "Click to run %1 pending migrations." "$pending_count")
|
||||
fi
|
||||
|
||||
# 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
|
||||
# invitation over and exit instead of staying activated until it is answered.
|
||||
omarchy-notification-send -u critical -g "Pending Omarchy Migrations" "$message" \
|
||||
omarchy-notification-send -u critical -g "$(omarchy-text "Pending Omarchy Migrations")" "$message" \
|
||||
--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
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
# omarchy:summary=Show the current time and date notification
|
||||
|
||||
omarchy-notification-send -g -u low "$(date +"%A %H:%M · %d %B %Y · Week %V")"
|
||||
omarchy-notification-send -g -u low "$(date +"%A %H:%M · %d %B %Y") · $(omarchy-text "Week %1" "$(date +%V)")"
|
||||
@@ -159,8 +159,8 @@ done
|
||||
omarchy-plugin-enable "$new_id" >/dev/null
|
||||
clone_complete=1
|
||||
omarchy-notification-send -g \
|
||||
"Editing Cloned Plugin" \
|
||||
"Original plugin has been replace by clone."
|
||||
"$(omarchy-text "Editing Cloned Plugin")" \
|
||||
"$(omarchy-text "Original plugin has been replace by clone.")"
|
||||
echo "Cloned $source_id to $target_dir and switched to $new_id"
|
||||
if (( edit_clone )); then
|
||||
# Word-splitting is deliberate: EDITOR may carry flags.
|
||||
|
||||
@@ -68,8 +68,6 @@ run_first_run_step() {
|
||||
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" \
|
||||
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook"
|
||||
run_first_run_step "install agent setup post-update hook" \
|
||||
|
||||
@@ -599,6 +599,7 @@ keyboard_form() {
|
||||
done
|
||||
|
||||
apply_keyboard "$keyboard"
|
||||
omarchy_persist_input_selection
|
||||
}
|
||||
|
||||
confirm_reboot() {
|
||||
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
#!/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
|
||||
+15
-14
@@ -57,16 +57,16 @@ show_reminders() {
|
||||
[[ -f $reminder_dir/${timer%.timer}.message ]] && reminder_message=$(<"$reminder_dir/${timer%.timer}.message")
|
||||
|
||||
if [[ -n $reminder_message ]]; then
|
||||
body+="$reminder_message in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
|
||||
body+="$(omarchy-text "%1 in %2 (%3)" "$reminder_message" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n'
|
||||
else
|
||||
body+="${reminder_minutes}-min reminder in $(format_remaining $remaining) ($(date -d "@$next" +%-H:%M))"$'\n'
|
||||
body+="$(omarchy-text "%1-min reminder in %2 (%3)" "$reminder_minutes" "$(format_remaining $remaining)" "$(date -d "@$next" +%-H:%M)")"$'\n'
|
||||
fi
|
||||
done < <(active_reminder_timers "$now")
|
||||
|
||||
if [[ -z $body ]]; then
|
||||
omarchy-notification-send -g "Upcoming reminders" "No outstanding reminders"
|
||||
omarchy-notification-send -g "$(omarchy-text "Upcoming reminders")" "$(omarchy-text "No outstanding reminders")"
|
||||
else
|
||||
omarchy-notification-send -g "Upcoming reminders" "${body%$'\n'}"
|
||||
omarchy-notification-send -g "$(omarchy-text "Upcoming reminders")" "${body%$'\n'}"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ show_json() {
|
||||
local reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
|
||||
local now=$(date +%s)
|
||||
local count=0
|
||||
local tooltip="Set Reminder"
|
||||
local tooltip
|
||||
tooltip=$(omarchy-text "Set Reminder")
|
||||
|
||||
while IFS=$'\t' read -r timer next; do
|
||||
count=$((count + 1))
|
||||
@@ -91,7 +92,7 @@ show_json() {
|
||||
if [[ -n $reminder_message ]]; then
|
||||
label=$reminder_message
|
||||
else
|
||||
label="${reminder_minutes}-min reminder"
|
||||
label=$(omarchy-text "%1-min reminder" "$reminder_minutes")
|
||||
fi
|
||||
|
||||
item_json=$(jq -cn \
|
||||
@@ -109,9 +110,9 @@ show_json() {
|
||||
done < <(active_reminder_timers "$now")
|
||||
|
||||
if ((count == 1)); then
|
||||
tooltip="1 reminder"
|
||||
tooltip=$(omarchy-text "1 reminder")
|
||||
elif ((count > 1)); then
|
||||
tooltip="$count reminders"
|
||||
tooltip=$(omarchy-text "%1 reminders" "$count")
|
||||
fi
|
||||
|
||||
jq -cn --argjson count "$count" --arg tooltip "$tooltip" --argjson reminders "$reminders_json" '{count:$count,active:($count > 0),tooltip:$tooltip,reminders:$reminders}'
|
||||
@@ -129,7 +130,7 @@ clear_reminders() {
|
||||
|
||||
rm -f "$reminder_dir"/omarchy-reminder-*.message 2>/dev/null || true
|
||||
refresh_indicator
|
||||
omarchy-notification-send -g "All reminders have been cleared"
|
||||
omarchy-notification-send -g "$(omarchy-text "All reminders have been cleared")"
|
||||
}
|
||||
|
||||
usage() {
|
||||
@@ -176,7 +177,7 @@ if [[ -z $minutes ]] || [[ ! $minutes =~ ^[0-9]+$ ]] || ((minutes == 0)); then
|
||||
fi
|
||||
|
||||
if [[ -z $message ]]; then
|
||||
message="Your ${minutes} minutes are up"
|
||||
message=$(omarchy-text "Your %1 minutes are up" "$minutes")
|
||||
fi
|
||||
|
||||
set_at=$(date +%s)
|
||||
@@ -184,18 +185,18 @@ remind_at=$(date -d "+${minutes} minutes" +%H:%M)
|
||||
unit="omarchy-reminder-${minutes}m-$set_at"
|
||||
reminder_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-reminders"
|
||||
message_file="$reminder_dir/$unit.message"
|
||||
confirmation="You'll be reminded at $remind_at"
|
||||
confirmation_title="Reminder set for ${minutes} minutes"
|
||||
confirmation=$(omarchy-text "You'll be reminded at %1" "$remind_at")
|
||||
confirmation_title=$(omarchy-text "Reminder set for %1 minutes" "$minutes")
|
||||
|
||||
mkdir -p "$reminder_dir"
|
||||
|
||||
if [[ -n $custom_message ]]; then
|
||||
printf "%s" "$custom_message" >"$message_file"
|
||||
confirmation_title="$custom_message in ${minutes} minutes"
|
||||
confirmation_title=$(omarchy-text "%1 in %2 minutes" "$custom_message" "$minutes")
|
||||
fi
|
||||
|
||||
systemd-run --user --quiet --collect --on-active="${minutes}m" --unit="$unit" \
|
||||
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"
|
||||
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"
|
||||
|
||||
refresh_indicator
|
||||
omarchy-notification-send -g "$confirmation_title" "$confirmation"
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/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
|
||||
@@ -18,6 +18,10 @@ if omarchy-cmd-present voxtype; then
|
||||
omarchy-pkg-drop voxtype-bin
|
||||
rm -rf ~/.config/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
|
||||
else
|
||||
echo "Voxtype was not installed."
|
||||
@@ -2,9 +2,30 @@
|
||||
|
||||
# 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
|
||||
# instance would exit on arrival and the stale process would keep serving the
|
||||
# old ~/.XCompose -- a restart that reports success and changes nothing.
|
||||
systemctl --user stop omarchy-fcitx5.service
|
||||
pkill -x fcitx5 >/dev/null 2>&1 || true
|
||||
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
|
||||
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
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/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."
|
||||
+194
-47
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash -p
|
||||
|
||||
# omarchy:summary=Toggle passwordless sudo for the current user.
|
||||
# omarchy:args=[MINUTES]
|
||||
# omarchy:args=[MINUTES|permanent|--active|--disable]
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
if [[ $- != *p* && ${BASH_SOURCE[0]} == "$0" ]]; then
|
||||
@@ -22,7 +22,6 @@ fi
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly DEFAULT_MINUTES=15
|
||||
readonly MAX_MINUTES=1440
|
||||
readonly LOCK_FILE=/run/lock/omarchy-sudo-passwordless.lock
|
||||
readonly BOOT_CLEANUP_FILE=/etc/tmpfiles.d/omarchy-nopasswd-sudo.conf
|
||||
@@ -34,7 +33,7 @@ readonly INSTALLED_SELF=/usr/bin/omarchy-sudo-passwordless
|
||||
readonly STATUS_INACTIVE=3
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-sudo-passwordless [MINUTES]" >&2
|
||||
echo "Usage: omarchy-sudo-passwordless [MINUTES|permanent|--active|--disable]" >&2
|
||||
echo "MINUTES must be between 1 and $MAX_MINUTES." >&2
|
||||
exit 1
|
||||
}
|
||||
@@ -43,6 +42,10 @@ valid_minutes() {
|
||||
[[ $1 =~ ^0*[1-9][0-9]{0,3}$ ]] && ((10#$1 <= MAX_MINUTES))
|
||||
}
|
||||
|
||||
valid_duration() {
|
||||
[[ $1 == "permanent" ]] || valid_minutes "$1"
|
||||
}
|
||||
|
||||
valid_uid() {
|
||||
[[ $1 =~ ^0*[1-9][0-9]{0,9}$ ]] && ((10#$1 <= 4294967294))
|
||||
}
|
||||
@@ -96,14 +99,39 @@ with_root_lock() {
|
||||
}
|
||||
|
||||
rule_file() {
|
||||
printf '/etc/sudoers.d/99-omarchy-nopasswd-%s' "$1"
|
||||
local uid=$((10#$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
|
||||
# an unreadable, unsafe, or administrator-modified file.
|
||||
read_grant() {
|
||||
local file contents
|
||||
local file permanent contents
|
||||
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"
|
||||
verify_root_path "$file" && [[ -f $file ]] || return 2
|
||||
contents=$(/usr/bin/cat -- "$file") || return 2
|
||||
@@ -121,6 +149,9 @@ classify_generated_rule() {
|
||||
[[ -f $file && ! -L $file ]] || return 1
|
||||
contents=$(/usr/bin/cat -- "$file") || return 2
|
||||
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
|
||||
# filename and the rule. That exact relationship is its fingerprint, so an
|
||||
@@ -138,14 +169,21 @@ classify_generated_rule() {
|
||||
valid_account_name "$name" && [[ $contents =~ ^[a-z_][a-z0-9_-]*\$?\ ALL=\(ALL\)\ NOTAFTER=[0-9]{14}Z\ NOPASSWD:\ ALL$ ]]
|
||||
}
|
||||
|
||||
cleanup_uid_locked() {
|
||||
local file
|
||||
file=$(rule_file "$1")
|
||||
cleanup_rule_file() {
|
||||
local file=$1
|
||||
[[ -e $file || -L $file ]] || return 0
|
||||
verify_root_path "$file" && classify_generated_rule "$file" || return 1
|
||||
/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
|
||||
# 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
|
||||
@@ -263,6 +301,7 @@ migrate_locked() {
|
||||
expire_locked() {
|
||||
local status now
|
||||
if read_grant "$1"; then
|
||||
[[ -n $GRANT_DEADLINE ]] || return 0
|
||||
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
|
||||
[[ $now < $GRANT_DEADLINE ]] && return 0
|
||||
cleanup_uid_locked "$1"
|
||||
@@ -281,6 +320,7 @@ status_locked() {
|
||||
resolve_account "$1" || return 2
|
||||
if read_grant "$1"; then
|
||||
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 2
|
||||
[[ -n $GRANT_DEADLINE ]] || return 0
|
||||
now=$(/usr/bin/date -u +%Y%m%d%H%M%SZ) || return 2
|
||||
if [[ $now < $GRANT_DEADLINE ]]; then
|
||||
return 0
|
||||
@@ -300,7 +340,11 @@ finish_enable() {
|
||||
if cleanup_uid_locked "$uid"; then
|
||||
[[ -z $timer ]] || /usr/bin/systemctl stop "$timer.timer" "$timer.service" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "Could not revoke passwordless sudo; expiry remains armed. Administrator cleanup is required." >&2
|
||||
if [[ -n $timer ]]; then
|
||||
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
|
||||
[[ -z $pending ]] || /usr/bin/rm -f -- "$pending"
|
||||
@@ -309,9 +353,15 @@ finish_enable() {
|
||||
|
||||
enable_locked() (
|
||||
local uid=$1 minutes=$2 now expires deadline token timer="" pending="" file status
|
||||
resolve_account "$uid" && valid_minutes "$minutes" || return 1
|
||||
verify_boot_cleanup && verify_root_path /etc/sudoers.d || return 1
|
||||
file=$(rule_file "$uid")
|
||||
resolve_account "$uid" && valid_duration "$minutes" || return 1
|
||||
verify_root_path /etc/sudoers.d || return 1
|
||||
if [[ $minutes != "permanent" ]]; then
|
||||
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
|
||||
[[ $GRANT_NAME == "$ACCOUNT_NAME" ]] || return 1
|
||||
else
|
||||
@@ -320,39 +370,69 @@ enable_locked() (
|
||||
fi
|
||||
trap finish_enable EXIT
|
||||
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
|
||||
/usr/bin/printf '%s ALL=(ALL) NOTAFTER=%s NOPASSWD: ALL\n' "$ACCOUNT_NAME" "$deadline" >"$pending" || return 1
|
||||
if [[ $minutes == "permanent" ]]; then
|
||||
/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/sbin/visudo -cf "$pending" >/dev/null || return 1
|
||||
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1
|
||||
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
|
||||
timer="omarchy-nopasswd-expire-$uid-$token"
|
||||
/usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \
|
||||
--timer-property=AccuracySec=1s --unit="$timer" \
|
||||
-- "$INSTALLED_SELF" __expire "$uid" || return 1
|
||||
/usr/bin/systemctl is-active --quiet "$timer.timer" || return 1
|
||||
if [[ $minutes != "permanent" ]]; then
|
||||
token=$(/usr/bin/tr -d '-' </proc/sys/kernel/random/uuid) || return 1
|
||||
[[ $token =~ ^[0-9a-f]{32}$ ]] || return 1
|
||||
timer="omarchy-nopasswd-expire-$uid-$token"
|
||||
/usr/bin/systemd-run --quiet --collect --on-calendar="@$expires" \
|
||||
--timer-property=AccuracySec=1s --unit="$timer" \
|
||||
-- "$INSTALLED_SELF" __expire "$uid" || 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
|
||||
# sudoers.d publishes the complete validated policy in one operation.
|
||||
/usr/bin/mv -fT -- "$pending" "$file" || return 1
|
||||
pending=""
|
||||
now=$(/usr/bin/date +%s) || return 1
|
||||
(( now < expires )) && verify_boot_cleanup && /usr/bin/systemctl is-active --quiet "$timer.timer"
|
||||
# Keep access uninterrupted during renewal. When changing duration type,
|
||||
# remove the other rule only after its replacement is published. The lock
|
||||
# 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() {
|
||||
local action="$1"
|
||||
local action="$1" status
|
||||
shift
|
||||
case "$action" in
|
||||
__status)
|
||||
(($# == 1)) && verify_sudo_caller "$1" || return 2
|
||||
with_root_lock status_locked "$1"
|
||||
if with_root_lock status_locked "$1"; then
|
||||
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)
|
||||
(($# == 2)) && verify_sudo_caller "$1" && valid_minutes "$2" || return 1
|
||||
with_root_lock enable_locked "$1" "$2"
|
||||
(($# == 2)) && verify_sudo_caller "$1" && valid_duration "$2" || return 1
|
||||
if with_root_lock enable_locked "$1" "$2"; then
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
echo "Could not enable passwordless sudo. Check existing sudoers policy and cleanup support." >&2
|
||||
return "$status"
|
||||
fi
|
||||
;;
|
||||
__disable)
|
||||
(($# == 1)) && verify_sudo_caller "$1" || return 1
|
||||
@@ -382,6 +462,14 @@ root_dispatch() {
|
||||
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
|
||||
__status|__enable|__disable|__expire|__cleanup-all|__package-removing|__migrate|__migration-complete)
|
||||
action=$1
|
||||
@@ -391,9 +479,30 @@ case "${1:-}" in
|
||||
;;
|
||||
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
|
||||
minutes=${1:-$DEFAULT_MINUTES}
|
||||
valid_minutes "$minutes" || usage
|
||||
minutes=${1:-}
|
||||
(($# == 0)) || [[ $minutes == "--disable" ]] || valid_duration "$minutes" || usage
|
||||
uid=$(/usr/bin/id -u)
|
||||
valid_uid "$uid" || {
|
||||
echo "omarchy-sudo-passwordless: cannot grant passwordless sudo to this account" >&2
|
||||
@@ -411,38 +520,76 @@ omarchy_security_install_sudo_cleanup_traps
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Toggle passwordless sudo..."
|
||||
if /usr/bin/sudo -N -- "$INSTALLED_SELF" __status "$uid"; then
|
||||
if (($# == 0)); then
|
||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __disable "$uid"
|
||||
echo "Passwordless sudo has been DISABLED. Sudo will require a password again."
|
||||
else
|
||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
||||
echo "Passwordless sudo expiry updated. It will automatically disable in ${minutes} minutes."
|
||||
# Inspect policy without authenticating. Only inspect the root-owned grant
|
||||
# when policy already permits it without a password; otherwise the eventual
|
||||
# enable action is the sole authentication boundary. enable_locked validates
|
||||
# any existing policy before publishing, including expired or modified rules.
|
||||
status=$STATUS_INACTIVE
|
||||
if policy=$(/usr/bin/sudo -n -N -l -l -- "$INSTALLED_SELF" __status "$uid" 2>/dev/null); then
|
||||
if /usr/bin/grep -q '!authenticate' <<<"$policy"; then
|
||||
status=0
|
||||
/usr/bin/sudo -n -N -- "$INSTALLED_SELF" __status "$uid" || status=$?
|
||||
fi
|
||||
else
|
||||
status=$?
|
||||
if (( status != STATUS_INACTIVE )); then
|
||||
# A listing password is independent of command authorization. An active
|
||||
# grant can still be inspected/revoked without prompting under listpw=always.
|
||||
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
|
||||
exit 1
|
||||
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 "⚠️ WARNING: This will allow ANY process running as your user to"
|
||||
echo "execute ANY command as root WITHOUT a password for ${minutes} minutes."
|
||||
echo "execute ANY command as root WITHOUT a password $duration."
|
||||
echo ""
|
||||
echo "This is useful for AI agents that need to run sudo commands,"
|
||||
echo "but it significantly weakens the security of your system."
|
||||
echo "Anyone or anything with access to your user account gets full root."
|
||||
echo ""
|
||||
echo "Passwordless sudo will automatically disable after ${minutes} minutes,"
|
||||
echo "including if the machine reboots before the deadline."
|
||||
echo "Run this command again to disable it early."
|
||||
if [[ $minutes == "permanent" ]]; then
|
||||
echo "Passwordless sudo will remain enabled across reboots until you disable it."
|
||||
else
|
||||
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 ""
|
||||
|
||||
if /usr/bin/gum confirm "Enable passwordless sudo for ${minutes} minutes? This is a significant security risk!"; then
|
||||
if /usr/bin/gum confirm "Enable passwordless sudo $duration? This is a significant security risk!"; then
|
||||
/usr/bin/sudo -N -- "$INSTALLED_SELF" __enable "$uid" "$minutes"
|
||||
echo ""
|
||||
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${minutes} minutes."
|
||||
show_enabled
|
||||
else
|
||||
echo "Aborted. No changes made."
|
||||
fi
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
nohup bash -c "sleep 2 && uwsm stop" >/dev/null 2>&1 &
|
||||
|
||||
omarchy-osd -i logout -m "Logging out" -d 5000
|
||||
omarchy-osd -i logout -m "$(omarchy-text "Logging out")" -d 5000
|
||||
|
||||
# Now close all windows
|
||||
omarchy-hyprland-window-close-all
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
# 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
|
||||
|
||||
omarchy-osd -i reboot -m "Rebooting" -d 5000
|
||||
omarchy-osd -i reboot -m "$(omarchy-text "Rebooting")" -d 5000
|
||||
|
||||
omarchy-state clear re*-required
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
if [[ ${1:-} == "--inhibited" ]]; then
|
||||
# Close windows in the background so they cannot delay power-off.
|
||||
(
|
||||
omarchy-osd -i shutdown -m "Shutting down" -d 5000
|
||||
omarchy-osd -i shutdown -m "$(omarchy-text "Shutting down")" -d 5000
|
||||
omarchy-state clear re*-required
|
||||
omarchy-hyprland-window-close-all
|
||||
) &
|
||||
@@ -21,7 +21,7 @@ elif [[ ${1:-} == "--inhibit" ]]; then
|
||||
--who=Omarchy --why="Shutdown in progress" --mode=block \
|
||||
"$0" --inhibited || {
|
||||
status=$?
|
||||
omarchy-notification-send -u critical "Shutdown failed" "Could not complete shutdown. Please try again."
|
||||
omarchy-notification-send -u critical "$(omarchy-text "Shutdown failed")" "$(omarchy-text "Could not complete shutdown. Please try again.")"
|
||||
exit "$status"
|
||||
}
|
||||
else
|
||||
|
||||
@@ -98,8 +98,8 @@ report_unsecured() {
|
||||
"$1" >&2
|
||||
|
||||
omarchy-notification-send -u critical -g \
|
||||
"Screen did not lock before suspend" \
|
||||
"The session was left unlocked ($1)." >/dev/null 2>&1 || true
|
||||
"$(omarchy-text "Screen did not lock before suspend")" \
|
||||
"$(omarchy-text "The session was left unlocked (%1)." "$1")" >/dev/null 2>&1 || true
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ claim_path() {
|
||||
announce() {
|
||||
local path="$1"
|
||||
local name="${path##*/}"
|
||||
local args=("Received $name" "Saved to ${dir/#$HOME/~}" -u critical)
|
||||
local args=("$(omarchy-text "Received %1" "$name")" "$(omarchy-text "Saved to %1" "${dir/#$HOME/~}")" -u critical)
|
||||
|
||||
case "${name,,}" in
|
||||
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
|
||||
|
||||
@@ -27,8 +27,8 @@ if ((${#files[@]} == 0)); then
|
||||
picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$?
|
||||
|
||||
if ((${status:-0} > 1)); then
|
||||
omarchy-notification-send -g "" -u critical "Could not send to $name" \
|
||||
"The file chooser did not open"
|
||||
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not send to %1" "$name")" \
|
||||
"$(omarchy-text "The file chooser did not open")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -43,8 +43,8 @@ else
|
||||
fi
|
||||
|
||||
if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then
|
||||
omarchy-notification-send -g "" "Sent to $name" "$what"
|
||||
omarchy-notification-send -g "" "$(omarchy-text "Sent to %1" "$name")" "$what"
|
||||
else
|
||||
omarchy-notification-send -g "" -u critical "Could not send to $name" "${error:-Taildrop transfer failed}"
|
||||
omarchy-notification-send -g "" -u critical "$(omarchy-text "Could not send to %1" "$name")" "${error:-$(omarchy-text "Taildrop transfer failed")}"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/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"
|
||||
Executable
+201
@@ -0,0 +1,201 @@
|
||||
#!/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
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/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
|
||||
@@ -17,7 +17,7 @@ mapfile -d '' -t BACKGROUNDS < <(
|
||||
TOTAL=${#BACKGROUNDS[@]}
|
||||
|
||||
if (( TOTAL == 0 )); then
|
||||
omarchy-notification-send "No background was found for theme" -t 2000
|
||||
omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000
|
||||
else
|
||||
# Get current background from symlink
|
||||
if [[ -L $CURRENT_BACKGROUND_LINK ]]; then
|
||||
|
||||
@@ -35,5 +35,5 @@ fi
|
||||
|
||||
# Now remove the theme directory for THEME_NAME
|
||||
rm -rf "$THEME_PATH"
|
||||
omarchy-notification-send "Theme removed" "$THEME_NAME" || true
|
||||
omarchy-notification-send "$(omarchy-text "Theme removed")" "$THEME_NAME" || true
|
||||
echo "Removed $THEME_NAME"
|
||||
+85
-7
@@ -50,6 +50,13 @@ shell_ipc() {
|
||||
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() {
|
||||
[[ ${1,,} =~ \.(mp4|m4v|mov|webm|mkv|avi)$ ]]
|
||||
}
|
||||
@@ -70,10 +77,18 @@ snapshot_background_path() {
|
||||
}
|
||||
|
||||
snapshot_current_background() {
|
||||
local current_background
|
||||
local current_background snapshot
|
||||
|
||||
current_background=$(readlink -f "$CURRENT_BACKGROUND_LINK" 2>/dev/null || true)
|
||||
snapshot_background_path "$current_background" "previous"
|
||||
# An interrupted intro is still moving; its final still would flash as a
|
||||
# cover. Capture the frame already playing instead, when OWE supports it.
|
||||
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() {
|
||||
@@ -171,17 +186,48 @@ set_theme_background_link() {
|
||||
}
|
||||
|
||||
set_theme_background() {
|
||||
local new_background new_background_snapshot
|
||||
local new_background new_background_snapshot theme_id intro_prepared=false
|
||||
|
||||
if [[ -z $CHOSEN_THEME_BACKGROUND || ! -f $CHOSEN_THEME_BACKGROUND ]]; then
|
||||
if ! choose_theme_background; then
|
||||
omarchy-notification-send "No background was found for theme" -t 2000
|
||||
omarchy-notification-send "$(omarchy-text "No background was found for theme")" -t 2000
|
||||
shell_ipc shell applyTheme "$colors_payload" "$shell_payload" || true
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
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=""
|
||||
if [[ -n $PREPARED_BACKGROUND_SNAPSHOT && $PREPARED_BACKGROUND == "$new_background" ]]; then
|
||||
new_background_snapshot="$PREPARED_BACKGROUND_SNAPSHOT"
|
||||
@@ -337,6 +383,12 @@ fi
|
||||
exec 9>"$THEME_SET_LOCK"
|
||||
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)
|
||||
remember_current_theme_background
|
||||
|
||||
@@ -365,11 +417,24 @@ OLD_BACKGROUND_SNAPSHOT=""
|
||||
PREPARED_BACKGROUND=""
|
||||
PREPARED_BACKGROUND_SNAPSHOT=""
|
||||
BACKGROUND_TRANSITION_SNAPSHOTS=true
|
||||
THEME_BACKGROUND_INTRO=""
|
||||
INTRO_PREPARE_PID=""
|
||||
THEME_INTRO_TOKEN=""
|
||||
if [[ $THEME_HEADLESS != "1" && $OMARCHY_THEME_SKIP_BACKGROUND != "1" ]]; then
|
||||
# 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.
|
||||
if choose_staged_theme_background; then
|
||||
if background_transition_uses_snapshots "$CHOSEN_THEME_BACKGROUND"; then
|
||||
if [[ $PREVIOUS_THEME_NAME != "$THEME_NAME" && ! -f $HOME/.local/state/omarchy/toggles/background-intros-off ]] &&
|
||||
[[ $(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)
|
||||
|
||||
# Hand the shell the next background now, so it decodes while the
|
||||
@@ -389,6 +454,12 @@ fi
|
||||
# Generate dynamic configs
|
||||
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
|
||||
rm -rf "$CURRENT_THEME_PATH"
|
||||
mv "$NEXT_THEME_PATH" "$CURRENT_THEME_PATH"
|
||||
@@ -418,7 +489,6 @@ flock -u 9
|
||||
|
||||
post_theme_commands=(
|
||||
omarchy-restart-terminal
|
||||
omarchy-restart-hyprctl
|
||||
omarchy-restart-btop
|
||||
omarchy-restart-opencode
|
||||
omarchy-restart-helix
|
||||
@@ -437,6 +507,14 @@ post_theme_commands=(
|
||||
)
|
||||
|
||||
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[@]}"
|
||||
|
||||
# Call hook on theme set
|
||||
|
||||
@@ -29,6 +29,8 @@ if [[ -n $OMARCHY_THEME_SYNC_FROM ]] || ! omarchy-toggle-enabled herdr-theme-syn
|
||||
fi
|
||||
|
||||
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.
|
||||
# SSH carries no desktop session, so the environment comes from the live Hyprland instance.
|
||||
@@ -66,10 +68,13 @@ read -r -d '' REMOTE_SCRIPT <<'EOF'
|
||||
} </dev/null
|
||||
EOF
|
||||
|
||||
# One machine at a time, so an SSH agent like 1Password asks for approval at most once.
|
||||
# Reuse each authenticated connection for ten idle minutes, so an SSH agent like
|
||||
# 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.
|
||||
for target in $targets; do
|
||||
result=$(printf 'theme=%q from=%q\n%s\n' "$theme" "$(hostname)" "$REMOTE_SCRIPT" |
|
||||
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 "$target" bash -l -s 2>&1 | tail -n 1)
|
||||
timeout 60 ssh -o BatchMode=yes -o ConnectTimeout=5 \
|
||||
-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}"
|
||||
done
|
||||
@@ -16,7 +16,7 @@ off) omarchy-hyprland-toggle no-animations on ;;
|
||||
esac
|
||||
|
||||
if omarchy-hyprland-toggle-enabled no-animations; then
|
||||
omarchy-notification-send -g "Animations disabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Animations disabled")"
|
||||
else
|
||||
omarchy-notification-send -g "Animations enabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Animations enabled")"
|
||||
fi
|
||||
@@ -11,8 +11,8 @@ omarchy-toggle crash-capture-off
|
||||
# starting only settles this session; the next login reads the flag itself.
|
||||
if omarchy-toggle-enabled crash-capture-off; then
|
||||
systemctl --user stop omarchy-crash-watch.service
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture disabled"
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture disabled")"
|
||||
else
|
||||
systemctl --user start omarchy-crash-watch.service
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture enabled"
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "$(omarchy-text "Crash capture enabled")"
|
||||
fi
|
||||
@@ -5,7 +5,7 @@
|
||||
omarchy-toggle screensaver-off
|
||||
|
||||
if omarchy-toggle-enabled screensaver-off; then
|
||||
omarchy-notification-send -g "Screensaver disabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Screensaver disabled")"
|
||||
else
|
||||
omarchy-notification-send -g "Screensaver enabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Screensaver enabled")"
|
||||
fi
|
||||
@@ -5,7 +5,7 @@
|
||||
omarchy-toggle suspend-off
|
||||
|
||||
if omarchy-toggle-enabled suspend-off; then
|
||||
omarchy-notification-send -g "Suspend removed from system menu"
|
||||
omarchy-notification-send -g "$(omarchy-text "Suspend removed from system menu")"
|
||||
else
|
||||
omarchy-notification-send -g "Suspend now available in system menu"
|
||||
omarchy-notification-send -g "$(omarchy-text "Suspend now available in system menu")"
|
||||
fi
|
||||
@@ -5,7 +5,7 @@
|
||||
omarchy-toggle herdr-theme-sync
|
||||
|
||||
if omarchy-toggle-enabled herdr-theme-sync; then
|
||||
omarchy-notification-send -g "Herdr theme sync enabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Herdr theme sync enabled")"
|
||||
else
|
||||
omarchy-notification-send -g "Herdr theme sync disabled"
|
||||
omarchy-notification-send -g "$(omarchy-text "Herdr theme sync disabled")"
|
||||
fi
|
||||
@@ -193,14 +193,14 @@ main() {
|
||||
output=$(output_path "$input" "$format" "$resolution")
|
||||
|
||||
if [[ $type == "video" ]]; then
|
||||
omarchy-notification-send -g "Transcoding video…" "$(basename -- "$input") to $format ($resolution)"
|
||||
omarchy-notification-send -g "$(omarchy-text "Transcoding video…")" "$(omarchy-text "%1 to %2 (%3)" "$(basename -- "$input")" "$format" "$resolution")"
|
||||
transcode_video "$input" "$format" "$resolution" "$output"
|
||||
copy_to_clipboard "$output"
|
||||
omarchy-notification-send -g "Transcoded to $resolution $format" "Saved and copied to clipboard."
|
||||
omarchy-notification-send -g "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
|
||||
else
|
||||
transcode_picture "$input" "$format" "$resolution" "$output"
|
||||
copy_to_clipboard "$output"
|
||||
omarchy-notification-send -g "Transcoded to $resolution $format" "Saved and copied to clipboard."
|
||||
omarchy-notification-send -g "$(omarchy-text "Transcoded to %1 %2" "$resolution" "$format")" "Saved and copied to clipboard."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "$ICON_DIR/$icon_name.png" "$ICON_DIR/$APP_NAME.png" "$OLD_ICON_DIR/$APP_NAME.png"
|
||||
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
|
||||
omarchy-notification-send -g "TUI removed" "$APP_NAME"
|
||||
omarchy-notification-send -g "$(omarchy-text "TUI removed")" "$APP_NAME"
|
||||
fi
|
||||
+1
-1
@@ -127,7 +127,7 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||
omarchy-update-system-pkgs
|
||||
|
||||
omarchy-migrate
|
||||
omarchy-update-orphan-pkgs
|
||||
omarchy-update-orphan-pkgs -y
|
||||
|
||||
omarchy-update-analyze-logs
|
||||
omarchy-update-status
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 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
|
||||
|
||||
set -e
|
||||
@@ -12,18 +14,18 @@ echo -e "\e[32m\nOrphan system packages\e[0m"
|
||||
printf ' %s\n' "${orphans[@]}"
|
||||
echo
|
||||
|
||||
if [[ ! -t 0 || ! -t 1 ]]; then
|
||||
if [[ ${1:-} != "-y" && ( ! -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
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then
|
||||
if [[ ${1:-} != "-y" ]] && ! gum confirm --default=false "Remove ${#orphans[@]} orphaned package(s)?"; then
|
||||
echo "Keeping orphaned packages."
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "\e[32m\nRemoving orphan system packages\e[0m"
|
||||
sudo pacman -Rns "${orphans[@]}"
|
||||
omarchy-pkg-drop "${orphans[@]}"
|
||||
echo
|
||||
@@ -1802,6 +1802,8 @@ copy_missing_config_defaults() {
|
||||
while IFS= read -r -d '' source_path; do
|
||||
rel="${source_path#$source_dir/}"
|
||||
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"
|
||||
[[ -e $target_path || -L $target_path ]] && continue
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/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
|
||||
@@ -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"
|
||||
|
||||
if [[ ${OMARCHY_REMOVE_NOTIFY:-true} != "false" ]]; then
|
||||
omarchy-notification-send -g "Web app removed" "$APP_NAME"
|
||||
omarchy-notification-send -g "$(omarchy-text "Web app removed")" "$APP_NAME"
|
||||
fi
|
||||
update-desktop-database "$DESKTOP_DIR" &>/dev/null
|
||||
@@ -1414,7 +1414,7 @@ launch_windows() {
|
||||
if ! priv up_wait; then
|
||||
echo "❌ Failed to start Windows VM!"
|
||||
echo " Try checking: omarchy-windows-vm status"
|
||||
omarchy-notification-send -u critical "Windows VM" "Failed to start Windows VM"
|
||||
omarchy-notification-send -u critical "$(omarchy-text "Windows VM")" "$(omarchy-text "Failed to start Windows VM")"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
--ozone-platform=wayland
|
||||
--ozone-platform-hint=wayland
|
||||
--password-store=gnome-libsecret
|
||||
--enable-features=TouchpadOverscrollHistoryNavigation
|
||||
--enable-features=TouchpadOverscrollHistoryNavigation,OverlayScrollbar
|
||||
--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
|
||||
@@ -0,0 +1,3 @@
|
||||
# Keep Pinyin offline without prompting to enable cloud prediction.
|
||||
FirstRun=False
|
||||
CloudPinyinEnabled=False
|
||||
@@ -0,0 +1,2 @@
|
||||
[TriggerKey]
|
||||
0=Super+semicolon
|
||||
@@ -0,0 +1 @@
|
||||
Allow Overriding System XKB Settings=False
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
@@ -0,0 +1 @@
|
||||
superwhisper
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# To put it into use, remove .sample from this file name.
|
||||
|
||||
# Example: add an Include line above [core].
|
||||
# Example: add an Include line above the first repository, [omarchy].
|
||||
# Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself.
|
||||
|
||||
CONF=/etc/pacman.conf
|
||||
@@ -21,4 +21,4 @@ MARKER="Include = $SNIPPET"
|
||||
[[ -r $SNIPPET ]] || exit 0
|
||||
grep -qxF "$MARKER" "$CONF" && exit 0
|
||||
|
||||
sudo sed -i "0,/^\[core\]/s||$MARKER\n\n[core]|" "$CONF"
|
||||
sudo sed -i "0,/^\[omarchy\]/s||$MARKER\n\n[omarchy]|" "$CONF"
|
||||
@@ -1,6 +1,298 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<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">
|
||||
<test name="family" qual="any">
|
||||
<string>sans-serif</string>
|
||||
|
||||
@@ -6,12 +6,16 @@ hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation
|
||||
|
||||
-- Launcher, image selector, emojis, clipboard overlays, the OSD, reminders, the
|
||||
-- Wi-Fi QR code and keyboard-driven panels should pop without compositor layer
|
||||
-- animations. The overlays stay mapped between opens and grow from a parked
|
||||
-- 1x1 when shown, which Hyprland would otherwise animate as a slide in from
|
||||
-- the corner. Panels keep their own QML opacity transition for normal
|
||||
-- open/close, and skip it for panel handoff.
|
||||
-- animations. Overlays open as soon as their scale is ready, without a
|
||||
-- compositor fade or slide. 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" })
|
||||
|
||||
-- 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
|
||||
-- SUPER+ALT+F so component previews have the whole workspace.
|
||||
o.window({ class = "^org.quickshell$", title = "^Omarchy shell – dev gallery$" }, { maximize = true })
|
||||
@@ -1,4 +1,12 @@
|
||||
require("default.hypr.startup-cursor")
|
||||
|
||||
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.
|
||||
hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)")
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd --all")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
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
|
||||
@@ -0,0 +1,66 @@
|
||||
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 })
|
||||
@@ -0,0 +1,43 @@
|
||||
-- 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 411 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user