Compare commits
108
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5b01ace6b | ||
|
|
848e9610c0 | ||
|
|
95d4e57db9 | ||
|
|
6da63c8942 | ||
|
|
562684f9fa | ||
|
|
b914d694f2 | ||
|
|
199a7f23fc | ||
|
|
1824bc3066 | ||
|
|
371fd05f03 | ||
|
|
557ba1d270 | ||
|
|
fdd457c0fa | ||
|
|
8798461461 | ||
|
|
4521dac023 | ||
|
|
c7402fe653 | ||
|
|
ec4579a461 | ||
|
|
d9a781e3c9 | ||
|
|
a6c3480ff8 | ||
|
|
545190596d | ||
|
|
b381093e32 | ||
|
|
9d74c813f1 | ||
|
|
fce85abf48 | ||
|
|
3c68a1bb04 | ||
|
|
b89def6c7f | ||
|
|
0de49b231a | ||
|
|
40642c0d35 | ||
|
|
ee358c251b | ||
|
|
b333308b2d | ||
|
|
8415c8d2a2 | ||
|
|
f273eed840 | ||
|
|
d62dff95b6 | ||
|
|
036e501e58 | ||
|
|
35f8eef1bc | ||
|
|
9a57690e7d | ||
|
|
2841563e6c | ||
|
|
cd73dc6cfd | ||
|
|
d2e4a95aed | ||
|
|
1abe693d73 | ||
|
|
fcda55944a | ||
|
|
22514b59f2 | ||
|
|
0f14a84934 | ||
|
|
cd0025b62f | ||
|
|
9fe6a04715 | ||
|
|
5fc227b06c | ||
|
|
051952600d | ||
|
|
619979a716 | ||
|
|
3e00a48c00 | ||
|
|
1454e04b7f | ||
|
|
d53b24cfc7 | ||
|
|
f63ecb6697 | ||
|
|
a1bdc46119 | ||
|
|
7a60c8dea4 | ||
|
|
6be88d14a5 | ||
|
|
2b2302a279 | ||
|
|
e236713d9f | ||
|
|
e590f22de6 | ||
|
|
df44695ea7 | ||
|
|
7f0c94d480 | ||
|
|
f9c36b2f3b | ||
|
|
a77c246d38 | ||
|
|
a3e40cb8b0 | ||
|
|
09da70c303 | ||
|
|
df338c4003 | ||
|
|
1f5d29a3ac | ||
|
|
41c491ae4c | ||
|
|
b268838479 | ||
|
|
bf08fedf8e | ||
|
|
50a26be6a7 | ||
|
|
d5ef1c1d15 | ||
|
|
d024c82a9d | ||
|
|
d7f17a0424 | ||
|
|
87a2931209 | ||
|
|
647979bde7 | ||
|
|
123535ea14 | ||
|
|
645cfc2a36 | ||
|
|
e989960895 | ||
|
|
48f7c411c0 | ||
|
|
7727d40c92 | ||
|
|
7f9711ed9c | ||
|
|
8956964ee5 | ||
|
|
ebb0273584 | ||
|
|
3c3f23222b | ||
|
|
4e323342ca | ||
|
|
dff33f16e3 | ||
|
|
6fe83bfa47 | ||
|
|
dae9edef4c | ||
|
|
77c8e3f413 | ||
|
|
ab6804f8cc | ||
|
|
2ad241159e | ||
|
|
4eafef5025 | ||
|
|
e1471efa42 | ||
|
|
b51f36a9fe | ||
|
|
07d5f49963 | ||
|
|
5f6c803b21 | ||
|
|
56c8344fd2 | ||
|
|
465d4da567 | ||
|
|
151a530d15 | ||
|
|
da6045be3f | ||
|
|
23072924c4 | ||
|
|
f4bb97018e | ||
|
|
343501011f | ||
|
|
600734790c | ||
|
|
ab028e80eb | ||
|
|
a62771e2b4 | ||
|
|
32ebf6b85b | ||
|
|
b6bf851af4 | ||
|
|
c86af5cea5 | ||
|
|
83d158ad07 | ||
|
|
0c0f4881cb |
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
|
||||
|
||||
@@ -58,6 +58,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 +74,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"
|
||||
|
||||
@@ -78,10 +78,22 @@ claude)
|
||||
command=(claude --permission-mode auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
dim)
|
||||
# `dim exec` is the one-shot form; bare dim is the TUI and has no prompts to skip.
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(dim exec "$prompt")
|
||||
else
|
||||
command=(dim)
|
||||
fi
|
||||
;;
|
||||
grok)
|
||||
command=(grok --permission-mode bypassPermissions)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
kimi)
|
||||
command=(kimi --auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
;;
|
||||
openclaw)
|
||||
# The launcher owns onboarding and the gateway dance: OpenClaw's terminal UI
|
||||
# must attach to the running gateway (the embedded `openclaw chat` refuses to
|
||||
|
||||
@@ -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
|
||||
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Restore channel and regional pacman defaults in an installed target
|
||||
# omarchy:args=<stable|rc|edge> [target-root] | --render <stable|rc|edge> <output-dir> [target-root]
|
||||
# omarchy:requires-sudo=true
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-apply-pacman <stable|rc|edge> [target-root]" >&2
|
||||
echo " omarchy-apply-pacman --render <stable|rc|edge> <output-dir> [target-root]" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --render only builds the two files, so an unprivileged caller can hand root
|
||||
# nothing but plain copies of the finished result.
|
||||
render_dir=""
|
||||
if [[ ${1:-} == "--render" ]]; then
|
||||
(( $# == 3 || $# == 4 )) || usage
|
||||
channel="$2"
|
||||
render_dir="$3"
|
||||
[[ -d $render_dir ]] || { echo "Render directory not found: $render_dir" >&2; exit 1; }
|
||||
etc="${4:-/}/etc"
|
||||
else
|
||||
(( $# >= 1 && $# <= 2 )) || usage
|
||||
channel="$1"
|
||||
etc="${2:-/}/etc"
|
||||
fi
|
||||
|
||||
case "$channel" in
|
||||
stable|rc|edge) ;;
|
||||
*) echo "Invalid pacman channel: $channel" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# The ISO writes this plain-text marker before target finalization. Missing
|
||||
# markers keep existing installations global; never source it as shell code.
|
||||
region=global
|
||||
if [[ -e $etc/omarchy/region ]]; then
|
||||
region=$(<"$etc/omarchy/region")
|
||||
fi
|
||||
case "$region" in
|
||||
global|cn) ;;
|
||||
*) echo "Unsupported region: $region (expected global or cn)" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Build both files before replacing either. Explicit refreshes retain backups;
|
||||
# ordinary updates do not call this command or enforce regional defaults.
|
||||
if [[ -n $render_dir ]]; then
|
||||
staging="$render_dir"
|
||||
else
|
||||
staging=$(mktemp -d)
|
||||
trap 'rm -rf "$staging"' EXIT
|
||||
fi
|
||||
cp "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" "$staging/pacman.conf"
|
||||
if [[ $region == "global" ]]; then
|
||||
cp "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" "$staging/mirrorlist"
|
||||
else
|
||||
profile="$OMARCHY_PATH/default/regions/$region/pacman"
|
||||
printf '\n' >> "$staging/pacman.conf"
|
||||
cat "$profile/pacman.conf.append" >> "$staging/pacman.conf"
|
||||
# pacman tries servers in order, so the regional mirror leads and the
|
||||
# channel's own mirror stays as the fallback.
|
||||
cat "$profile/mirrorlist-$channel.prepend" "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" > "$staging/mirrorlist"
|
||||
fi
|
||||
|
||||
if [[ -z $render_dir ]]; then
|
||||
mkdir -p "$etc/pacman.d"
|
||||
for file in pacman.conf pacman.d/mirrorlist; do
|
||||
if [[ -e $etc/$file ]]; then
|
||||
cp -f "$etc/$file" "$etc/$file.bak"
|
||||
fi
|
||||
done
|
||||
# Copying over an existing file keeps its owner and mode, as refresh always has.
|
||||
cp -f "$staging/pacman.conf" "$etc/pacman.conf"
|
||||
cp -f "$staging/mirrorlist" "$etc/pacman.d/mirrorlist"
|
||||
fi
|
||||
@@ -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")"
|
||||
@@ -47,9 +47,31 @@ validate_dev_checkout() {
|
||||
fi
|
||||
}
|
||||
|
||||
# The dev channel refreshes pacman with the checkout's own tools. A checkout
|
||||
# without this machine's region profile would silently drop its repositories.
|
||||
validate_dev_region() {
|
||||
local checkout="$1" region_file="${OMARCHY_REGION_FILE:-/etc/omarchy/region}" region=global
|
||||
|
||||
[[ -f $region_file ]] && region=$(<"$region_file")
|
||||
[[ $region == "global" ]] && return 0
|
||||
|
||||
# The refresh after linking needs a runnable helper and every fragment, not
|
||||
# just a directory with the region's name: dev refreshes edge, and leaving
|
||||
# dev later refreshes stable or rc with this checkout's tools.
|
||||
local profile="$checkout/default/regions/$region/pacman" fragment supported=true
|
||||
[[ -x $checkout/bin/omarchy-apply-pacman ]] || supported=false
|
||||
for fragment in pacman.conf.append mirrorlist-stable.prepend mirrorlist-rc.prepend mirrorlist-edge.prepend; do
|
||||
[[ -r $profile/$fragment ]] || supported=false
|
||||
done
|
||||
if ! $supported; then
|
||||
fail "$checkout does not support the '$region' region, so switching would drop its package repositories. Check out a branch with default/regions/$region, or remove $region_file to use global defaults."
|
||||
fi
|
||||
}
|
||||
|
||||
link_dev_checkout() {
|
||||
local checkout="$1" required
|
||||
[[ -d $checkout/.git ]] || git clone https://github.com/omacom/omarchy.git "$checkout"
|
||||
validate_dev_region "$checkout"
|
||||
|
||||
# Check the destination before changing /etc/omarchy.conf or sudo's path.
|
||||
# An existing checkout is not pulled automatically and may predate this policy.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Set and launch the default coding agent
|
||||
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent|muse]
|
||||
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent|muse|dim|kimi]
|
||||
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent claude
|
||||
|
||||
installing=false
|
||||
@@ -31,7 +31,9 @@ ori | openrouter) agent="ori"; name="Ori"; agent_package="github:OpenRouterLabs/
|
||||
claude | claude-code) agent="claude"; name="Claude Code" ;;
|
||||
codex) agent="codex"; name="Codex" ;;
|
||||
crush) agent="crush"; name="Crush" ;;
|
||||
dim | dimcode | dim-agent) agent="dim"; name="Dim Agent"; agent_package="npm:dimcode" ;;
|
||||
grok) agent="grok"; name="Grok" ;;
|
||||
kimi | kimi-code) agent="kimi"; name="Kimi Code"; agent_package="npm:@moonshot-ai/kimi-code" ;;
|
||||
openclaw) agent="openclaw"; name="OpenClaw"; agent_installer="omarchy-install-openclaw-cli" ;;
|
||||
agy | antigravity | antigravity-cli | gemini | gemini-cli) agent="agy"; name="Antigravity"; agent_package="antigravity-cli" ;;
|
||||
hermes) agent="hermes"; name="Hermes"; agent_installer="omarchy-install-hermes-cli" ;;
|
||||
@@ -43,7 +45,7 @@ muse | muse-code | musecode)
|
||||
;;
|
||||
cursor | cursor-agent) agent="cursor-agent"; name="Cursor CLI" ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent|muse>"
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent|muse|dim|kimi>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -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")"
|
||||
@@ -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")"
|
||||
@@ -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")"
|
||||
@@ -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
+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
|
||||
|
||||
@@ -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)")"
|
||||
+9
-3
@@ -5,11 +5,17 @@
|
||||
# omarchy:examples=omarchy pkg add jq ripgrep
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
if omarchy-pkg-missing "$@"; then
|
||||
# Only the missing ones: an installed package from no repository is a "target not found" to pacman -S.
|
||||
missing=()
|
||||
for pkg in "$@"; do
|
||||
omarchy-pkg-missing "$pkg" && missing+=("$pkg")
|
||||
done
|
||||
|
||||
if (( ${#missing[@]} > 0 )); then
|
||||
if (( EUID == 0 )); then
|
||||
pacman -S --noconfirm --needed -- "$@" || exit 1
|
||||
pacman -S --noconfirm --needed -- "${missing[@]}" || exit 1
|
||||
else
|
||||
sudo pacman -S --noconfirm --needed -- "$@" || exit 1
|
||||
sudo pacman -S --noconfirm --needed -- "${missing[@]}" || exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -599,6 +599,7 @@ keyboard_form() {
|
||||
done
|
||||
|
||||
apply_keyboard "$keyboard"
|
||||
omarchy_persist_input_selection
|
||||
}
|
||||
|
||||
confirm_reboot() {
|
||||
|
||||
@@ -30,11 +30,25 @@ if [[ $channel != "stable" && $channel != "rc" && $channel != "edge" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Render the channel templates plus this machine's regional additions as the
|
||||
# caller, so root only copies finished files. The scratch directory goes on
|
||||
# every exit, including a failed render, a refused sudo prompt, or a signal;
|
||||
# revoking the sudo timestamp stays the last thing the exit path does.
|
||||
rendered=$(mktemp -d)
|
||||
cleanup_refresh() {
|
||||
local status=$?
|
||||
rm -rf "$rendered"
|
||||
omarchy_security_exit_with_revoked_sudo "$status"
|
||||
}
|
||||
trap cleanup_refresh EXIT
|
||||
"$OMARCHY_PATH/bin/omarchy-apply-pacman" --render "$channel" "$rendered"
|
||||
|
||||
sudo cp -f /etc/pacman.conf /etc/pacman.conf.bak
|
||||
sudo cp -f /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
|
||||
echo "Setting channel to $channel"
|
||||
sudo cp -f "$OMARCHY_PATH/default/pacman/pacman-$channel.conf" /etc/pacman.conf
|
||||
sudo cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-$channel" /etc/pacman.d/mirrorlist
|
||||
sudo cp -f "$rendered/pacman.conf" /etc/pacman.conf
|
||||
sudo cp -f "$rendered/mirrorlist" /etc/pacman.d/mirrorlist
|
||||
rm -rf "$rendered"
|
||||
|
||||
# Allow user customization of /etc/pacman.conf before the upgrade runs, so
|
||||
# custom repositories and IgnorePkg entries shape the downgrade-capable
|
||||
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/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"
|
||||
omarchy-setup-claude-api --remove
|
||||
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"
|
||||
@@ -24,6 +24,15 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
|
||||
rm -f ~/.local/bin/cursor-agent
|
||||
fi
|
||||
|
||||
# Only Omarchy's mise wrappers for Dim and Kimi are preinstalls.
|
||||
for wrapper in "dim npm:dimcode" "kimi npm:@moonshot-ai/kimi-code"; do
|
||||
read -r command package <<<"$wrapper"
|
||||
if [[ -f ~/.local/bin/$command && ! -L ~/.local/bin/$command ]] &&
|
||||
grep -qF "mise use -g --quiet \"$package\"" ~/.local/bin/$command; then
|
||||
rm -f ~/.local/bin/$command
|
||||
fi
|
||||
done
|
||||
|
||||
# Preserve a user-managed Muse launcher at the same path.
|
||||
if [[ -f ~/.local/bin/muse && ! -L ~/.local/bin/muse ]] &&
|
||||
grep -Eq '^mise use -g .*"http:muse\[' ~/.local/bin/muse; then
|
||||
|
||||
@@ -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
+61
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Point Claude Code at a third-party API key and model, or remove that setup
|
||||
# omarchy:args=[--remove]
|
||||
# omarchy:examples=omarchy setup claude-api | omarchy setup claude-api --remove
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
settings="$HOME/.claude/settings.json"
|
||||
keys=(ANTHROPIC_BASE_URL ANTHROPIC_AUTH_TOKEN ANTHROPIC_MODEL ANTHROPIC_DEFAULT_OPUS_MODEL
|
||||
ANTHROPIC_DEFAULT_SONNET_MODEL ANTHROPIC_DEFAULT_HAIKU_MODEL CLAUDE_CODE_SUBAGENT_MODEL)
|
||||
|
||||
keys_json=$(printf '%s\n' "${keys[@]}" | jq -R . | jq -s .)
|
||||
|
||||
# rewrite <file> <jq filter> [jq args...] replaces the file with a fresh 0600 copy.
|
||||
rewrite() {
|
||||
local file=$1 filter=$2 tmp
|
||||
shift 2
|
||||
mkdir -p "${file%/*}"
|
||||
[[ -f $file ]] || echo '{}' >"$file"
|
||||
tmp=$(mktemp "$file.XXXXXX")
|
||||
jq --argjson keys "$keys_json" "$@" "$filter" "$file" >"$tmp" || { rm -f "$tmp"; exit 1; }
|
||||
mv "$tmp" "$file"
|
||||
}
|
||||
|
||||
if [[ ${1:-} == "--remove" ]]; then
|
||||
if [[ -f $settings ]]; then
|
||||
rewrite "$settings" 'if .env then .env |= delpaths($keys | map([.])) | if .env == {} then del(.env) end end'
|
||||
fi
|
||||
echo "Claude Code third-party API removed from $settings"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
provider=$(gum choose --header "Claude Code API provider" DeepSeek "Kimi Code" "GLM (Zhipu)" MiniMax Custom)
|
||||
case "$provider" in
|
||||
DeepSeek) base_url="https://api.deepseek.com/anthropic" model="deepseek-flash[1m]" key_url="https://platform.deepseek.com/api_keys" ;;
|
||||
"Kimi Code") base_url="https://api.kimi.com/coding/" model="kimi-for-coding" key_url="https://www.kimi.com/code" ;;
|
||||
"GLM (Zhipu)") base_url="https://open.bigmodel.cn/api/anthropic" model="glm-5.3[1m]" key_url="https://open.bigmodel.cn/usercenter/apikeys" ;;
|
||||
MiniMax) base_url="https://api.minimaxi.com/anthropic" model="MiniMax-M3[1m]" key_url="https://platform.minimaxi.com/user-center/basic-information/interface-key" ;;
|
||||
Custom)
|
||||
base_url=$(gum input --header "Anthropic-compatible base URL" --placeholder "https://example.com/anthropic")
|
||||
model=""
|
||||
key_url=""
|
||||
;;
|
||||
esac
|
||||
|
||||
key=$(gum input --password --header "$provider API key${key_url:+ ($key_url)}")
|
||||
model=$(gum input --header "Model for every Claude Code tier" --value "$model")
|
||||
|
||||
if [[ -z $base_url || -z $key || -z $model ]]; then
|
||||
echo "Base URL, API key and model are all required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rewrite "$settings" '.env = (.env // {}) + ([$keys[] | {key: ., value: $model}] | from_entries) + {ANTHROPIC_BASE_URL: $url, ANTHROPIC_AUTH_TOKEN: $token}' \
|
||||
--arg url "$base_url" --arg token "$key" --arg model "$model"
|
||||
|
||||
# Third-party keys skip Anthropic's sign-in screen.
|
||||
rewrite "$HOME/.claude.json" '.hasCompletedOnboarding = true'
|
||||
|
||||
echo "Claude Code now uses $model at $base_url"
|
||||
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."
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -175,7 +175,7 @@ set_theme_background() {
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -22,5 +22,5 @@ if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then
|
||||
|
||||
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
|
||||
omarchy-notification-send -g "$(omarchy-text "Voxtype Dictation Ready")" "$(omarchy-text "Hold F9 to dictate (or toggle with Super + Ctrl + X).")" -t 10000
|
||||
fi
|
||||
@@ -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,OverlayScrollbar
|
||||
--enable-features=TouchpadOverscrollHistoryNavigation
|
||||
--load-extension=/usr/share/omarchy/default/chromium/extensions/copy-url,/usr/share/omarchy/default/chromium/extensions/yt-dlp,/usr/share/omarchy/default/chromium/extensions/whatsapp-slim
|
||||
@@ -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
|
||||
@@ -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,9 +6,10 @@ 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. 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.
|
||||
-- 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.
|
||||
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" })
|
||||
|
||||
-- Dev gallery is the main shell workbench; open it maximized like
|
||||
|
||||
@@ -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)
|
||||
+2
-21
@@ -1,32 +1,13 @@
|
||||
-- https://wiki.hypr.land/Configuring/Basics/Variables/#input
|
||||
|
||||
local function read_vconsole()
|
||||
local values = {}
|
||||
local file = io.open("/etc/vconsole.conf", "r")
|
||||
if not file then
|
||||
return values
|
||||
end
|
||||
|
||||
for line in file:lines() do
|
||||
local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$")
|
||||
if key and value then
|
||||
value = value:gsub("%s+#.*$", "")
|
||||
value = value:gsub('^"(.*)"$', "%1")
|
||||
value = value:gsub("^'(.*)'$", "%1")
|
||||
values[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
file:close()
|
||||
return values
|
||||
end
|
||||
local keyboard = require("default.hypr.keyboard")
|
||||
|
||||
-- Layouts that can't type Latin letters. Keep in sync with the list in
|
||||
-- etc/mkinitcpio.conf.d/omarchy_hooks.conf.
|
||||
local non_latin_layouts =
|
||||
" af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua "
|
||||
|
||||
local vconsole = read_vconsole()
|
||||
local vconsole = keyboard.installed()
|
||||
|
||||
local kb_layout = vconsole.XKBLAYOUT or "us"
|
||||
local kb_variant = vconsole.XKBVARIANT or ""
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
-- The keyboard layout picked at install time. The installer and first-boot
|
||||
-- setup persist it to /etc/vconsole.conf, so Hyprland reads it from there
|
||||
-- instead of rewriting every user's config.
|
||||
|
||||
local keyboard = {}
|
||||
|
||||
local function read_values(path)
|
||||
local values = {}
|
||||
local file = io.open(path, "r")
|
||||
if not file then
|
||||
return values
|
||||
end
|
||||
|
||||
for line in file:lines() do
|
||||
local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$")
|
||||
if key and value then
|
||||
value = value:gsub("%s+#.*$", "")
|
||||
value = value:gsub('^"(.*)"$', "%1")
|
||||
value = value:gsub("^'(.*)'$", "%1")
|
||||
values[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
file:close()
|
||||
return values
|
||||
end
|
||||
|
||||
function keyboard.vconsole()
|
||||
return read_values("/etc/vconsole.conf")
|
||||
end
|
||||
|
||||
function keyboard.installed()
|
||||
local values = keyboard.vconsole()
|
||||
if read_values("/etc/omarchy/input-method").XKB_LAYOUT == "kr"
|
||||
and (values.XKBLAYOUT or "us") == "us"
|
||||
and (values.XKBVARIANT or "") == "" then
|
||||
values.XKBLAYOUT, values.XKBVARIANT = "kr", ""
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
-- Japanese (JIS) keyboards put +, ^, and the Henkan/Muhenkan keys where US
|
||||
-- keyboards don't, so a few defaults only apply when that layout leads.
|
||||
function keyboard.jis()
|
||||
local layout = keyboard.installed().XKBLAYOUT or "us"
|
||||
return layout:match("^[^,]*") == "jp"
|
||||
end
|
||||
|
||||
return keyboard
|
||||
@@ -8,6 +8,8 @@ require("default.hypr.autostart")
|
||||
if _G.omarchy_default_bindings ~= false then
|
||||
require("default.hypr.bindings.media")
|
||||
require("default.hypr.bindings.clipboard")
|
||||
require("default.hypr.bindings.input-method")
|
||||
require("default.hypr.bindings.jis")
|
||||
require("default.hypr.bindings.tiling")
|
||||
require("default.hypr.bindings.utilities")
|
||||
require("default.hypr.bindings.voxtype")
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
"""One Fcitx setup path for offline finalization and a running desktop."""
|
||||
|
||||
import argparse
|
||||
from contextlib import contextmanager
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
ROOT = Path(os.environ["OMARCHY_PATH"])
|
||||
PRESETS = json.loads((ROOT / "default/input-methods/presets.json").read_text())
|
||||
NON_LATIN = set("af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua".split())
|
||||
CONTROLLER = ["busctl", "--user", "call", "org.fcitx.Fcitx5", "/controller", "org.fcitx.Fcitx.Controller1"]
|
||||
|
||||
|
||||
def run(args, **kwargs):
|
||||
return subprocess.run(args, check=True, text=True, capture_output=True, **kwargs).stdout.strip()
|
||||
|
||||
|
||||
def read(path):
|
||||
return path.read_text() if path.exists() else ""
|
||||
|
||||
|
||||
def values(text):
|
||||
result = {}
|
||||
for line in text.splitlines():
|
||||
match = re.match(r"^\s*([\w ]+)\s*=\s*(.*?)\s*(?:\s+#.*)?$", line)
|
||||
if match:
|
||||
result[match[1].strip()] = match[2].strip("\"'")
|
||||
return result
|
||||
|
||||
|
||||
def sections(text):
|
||||
result = {}
|
||||
current = ""
|
||||
for line in text.splitlines():
|
||||
match = re.match(r"^\s*\[([^]]+)\]\s*$", line)
|
||||
if match:
|
||||
current = match[1]
|
||||
result.setdefault(current, {})
|
||||
elif current:
|
||||
result[current].update(values(line))
|
||||
return result
|
||||
|
||||
|
||||
def atomic_write(path, text):
|
||||
# Follow a user's config symlink rather than replacing the link itself.
|
||||
path = path.resolve()
|
||||
if read(path) == text:
|
||||
return False
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
mode = path.stat().st_mode & 0o777 if path.exists() else 0o644
|
||||
fd, temporary = tempfile.mkstemp(prefix=".omarchy-input-", dir=path.parent)
|
||||
try:
|
||||
with os.fdopen(fd, "w") as file:
|
||||
file.write(text)
|
||||
os.chmod(temporary, mode)
|
||||
os.replace(temporary, path)
|
||||
finally:
|
||||
if os.path.exists(temporary):
|
||||
os.unlink(temporary)
|
||||
return True
|
||||
|
||||
|
||||
def key_list(entries):
|
||||
# Fcitx canonicalizes modifiers (e.g. Shift+Super instead of Super+Shift).
|
||||
return {frozenset(value.split("+")) for value in entries.values()}
|
||||
|
||||
|
||||
def defaults(config_home, fresh=False):
|
||||
changed = False
|
||||
files = ["config", "conf/quickphrase.conf", "conf/wayland.conf", "conf/xcb.conf", "conf/pinyin.conf"]
|
||||
for name in files:
|
||||
target = config_home / "fcitx5" / name
|
||||
original = read(target)
|
||||
shipped = read(ROOT / "config/fcitx5" / name)
|
||||
if name == "config" or name == "conf/quickphrase.conf":
|
||||
existing = sections(original)
|
||||
additions = []
|
||||
for section, entries in sections(shipped).items():
|
||||
# Missing hotkey sections also represent a choice: Fcitx's defaults.
|
||||
if not fresh and section.startswith("Hotkey/"):
|
||||
continue
|
||||
# Existing lists (including explicitly empty lists) are user choices.
|
||||
# A fresh profile has no working IME shortcut to preserve.
|
||||
stock_lists = {
|
||||
"Hotkey/TriggerKeys": {"0": "Control+space", "1": "Zenkaku_Hankaku", "2": "Hangul"},
|
||||
"Hotkey/ActivateKeys": {"0": "Hangul_Hanja"},
|
||||
"Hotkey/DeactivateKeys": {"0": "Hangul_Romaja"},
|
||||
"Hotkey/AltTriggerKeys": {"0": "Shift_L"},
|
||||
"Hotkey/EnumerateGroupForwardKeys": {"0": "Super+space"},
|
||||
"Hotkey/EnumerateGroupBackwardKeys": {"0": "Super+Shift+space"},
|
||||
"TriggerKey": {"0": "Super+grave", "1": "Super+semicolon"},
|
||||
}
|
||||
if fresh and section in existing and section in stock_lists and (key_list(existing[section]) == key_list(stock_lists[section]) or (not existing[section] and section in {"Hotkey/ActivateKeys", "Hotkey/DeactivateKeys"})):
|
||||
original = re.sub(r"(?ms)^\[" + re.escape(section) + r"\]\s*\n.*?(?=^\[|\Z)", "", original)
|
||||
existing.pop(section)
|
||||
if section not in existing:
|
||||
additions.append(f"[{section}]\n" + "".join(f"{key}={value}\n" for key, value in entries.items()))
|
||||
if additions:
|
||||
changed |= atomic_write(target, original.rstrip() + ("\n\n" if original.strip() else "") + "\n".join(additions))
|
||||
elif name == "conf/pinyin.conf":
|
||||
# Suppress the first-use cloud prompt while preserving any explicit
|
||||
# cloud-prediction preference and all other existing engine settings.
|
||||
if not original:
|
||||
updated = shipped
|
||||
elif "FirstRun" in values(original):
|
||||
updated = re.sub(r"(?m)^\s*FirstRun\s*=.*$", "FirstRun=False", original)
|
||||
else:
|
||||
updated = "FirstRun=False\n" + original
|
||||
changed |= atomic_write(target, updated)
|
||||
elif not target.exists() and not target.is_symlink():
|
||||
changed |= atomic_write(target, shipped)
|
||||
return changed
|
||||
|
||||
|
||||
def activation_default():
|
||||
data_home = Path(os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local/share"))
|
||||
path = data_home / "dbus-1/services/org.fcitx.Fcitx5.service"
|
||||
original = read(path)
|
||||
# Repair the package's plain activation entry, but retain custom launchers.
|
||||
packaged = values(original).get("Exec") in ("/usr/bin/fcitx5", "/usr/bin/fcitx5 -d")
|
||||
if not original or (packaged and "SystemdService" not in values(original)):
|
||||
return atomic_write(path, read(ROOT / "default/input-methods/org.fcitx.Fcitx5.service"))
|
||||
return False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transaction(config_home, live=False):
|
||||
data_home = Path(os.environ.get("XDG_DATA_HOME") or str(Path.home() / ".local/share"))
|
||||
paths = [config_home / "fcitx5" / name for name in ["profile", "config", "conf/quickphrase.conf", "conf/wayland.conf", "conf/xcb.conf", "conf/pinyin.conf"]]
|
||||
paths += [config_home / "fontconfig/conf.d/50-omarchy-input-method.conf", data_home / "dbus-1/services/org.fcitx.Fcitx5.service"]
|
||||
before = {path.resolve(): path.read_text() if path.exists() else None for path in paths}
|
||||
try:
|
||||
yield
|
||||
except BaseException as setup_error:
|
||||
if live:
|
||||
# Stop before restoring disk: a graceful stop saves the in-memory profile.
|
||||
try:
|
||||
run(["systemctl", "--user", "stop", "omarchy-fcitx5.service"])
|
||||
subprocess.run(["pkill", "-u", str(os.getuid()), "-x", "fcitx5"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
for attempt in range(50):
|
||||
if subprocess.run(["pgrep", "-u", str(os.getuid()), "-x", "fcitx5"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0:
|
||||
break
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
raise RuntimeError("Fcitx did not stop for rollback; previous settings could not safely be restored")
|
||||
except (OSError, subprocess.CalledProcessError, RuntimeError) as error:
|
||||
raise RuntimeError(f"Could not stop Fcitx for input rollback: {error} (setup failed: {setup_error})") from error
|
||||
for path, original in before.items():
|
||||
if original is None:
|
||||
path.unlink(missing_ok=True)
|
||||
else:
|
||||
atomic_write(path, original)
|
||||
if live:
|
||||
# Restore memory as well as disk; otherwise Fcitx saves the failed change
|
||||
# again on exit. Bus activation caches also need to forget the new entry.
|
||||
reload_bus()
|
||||
try:
|
||||
run(["omarchy-restart-xcompose"])
|
||||
run(CONTROLLER + ["ReloadConfig"])
|
||||
except (OSError, subprocess.CalledProcessError, RuntimeError) as error:
|
||||
print(f"Could not reload restored input settings: {error}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
def selection():
|
||||
vconsole = values(read(Path(os.environ.get("OMARCHY_VCONSOLE", "/etc/vconsole.conf"))))
|
||||
preference = values(read(Path(os.environ.get("OMARCHY_INPUT_SELECTION", "/etc/omarchy/input-method"))))
|
||||
layout = vconsole.get("XKBLAYOUT", "us").split(",")[0] or "us"
|
||||
variant = vconsole.get("XKBVARIANT", "").split(",")[0]
|
||||
if preference.get("XKB_LAYOUT") == "kr" and vconsole.get("XKBLAYOUT", "us") == "us" and not variant:
|
||||
layout, variant = "kr", ""
|
||||
method = preference.get("INPUT_METHOD", "mozc" if layout == "jp" else "none")
|
||||
if method not in PRESETS:
|
||||
raise ValueError(f"Unknown installed input method: {method}")
|
||||
if layout in NON_LATIN:
|
||||
layout, variant = "us", ""
|
||||
group_layout = layout + (f"-{variant}" if variant else "")
|
||||
if not re.fullmatch(r"[A-Za-z0-9_-]+", group_layout):
|
||||
raise ValueError("Invalid installed keyboard layout")
|
||||
return method, group_layout
|
||||
|
||||
|
||||
def stock_profile(text):
|
||||
if not text.strip():
|
||||
return True
|
||||
groups = sections(text)
|
||||
items = [data.get("Name", "") for key, data in groups.items() if re.fullmatch(r"Groups/\d+/Items/\d+", key)]
|
||||
group = groups.get("Groups/0", {})
|
||||
return (items == ["keyboard-us"]
|
||||
and list(key for key in groups if re.fullmatch(r"Groups/\d+", key)) == ["Groups/0"]
|
||||
and group.get("Name", "Default") == "Default"
|
||||
and group.get("Default Layout", "us") == "us"
|
||||
and not groups.get("Groups/0/Items/0", {}).get("Layout"))
|
||||
|
||||
|
||||
def profile_text(layout, method):
|
||||
default_im = method if method != "none" else f"keyboard-{layout}"
|
||||
text = f"[Groups/0]\nName=Default\nDefault Layout={layout}\nDefaultIM={default_im}\n\n[Groups/0/Items/0]\nName=keyboard-{layout}\nLayout=\n"
|
||||
if method != "none":
|
||||
text += f"\n[Groups/0/Items/1]\nName={method}\nLayout=\n"
|
||||
return text + "\n[GroupOrder]\n0=Default\n"
|
||||
|
||||
|
||||
def font_default(config_home, method):
|
||||
variant = PRESETS[method]["font"]
|
||||
path = config_home / "fontconfig/conf.d/50-omarchy-input-method.conf"
|
||||
if not variant or path.exists() or path.is_symlink():
|
||||
return
|
||||
# Only CJK fallback changes; retain the interface and terminal's Latin font.
|
||||
rules = []
|
||||
for families, face in [("sans-serif|Liberation Sans", "Sans"), ("serif|Liberation Serif", "Serif"), ("monospace|JetBrainsMono Nerd Font", "Sans Mono")]:
|
||||
for family in families.split("|"):
|
||||
rules.append(f'<alias><family>{family}</family><accept><family>Noto {face} CJK {variant}</family></accept></alias>')
|
||||
atomic_write(path, '<?xml version="1.0"?>\n<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">\n<fontconfig>\n' + "\n".join(rules) + "\n</fontconfig>\n")
|
||||
|
||||
|
||||
def live_group():
|
||||
group = run(["fcitx5-remote", "-q"])
|
||||
info = json.loads(run(CONTROLLER[:1] + ["--json=short"] + CONTROLLER[1:] + ["InputMethodGroupInfo", "s", group]))["data"]
|
||||
layout, items = info[:2]
|
||||
return group, layout, items
|
||||
|
||||
|
||||
def live_set(group, layout, items):
|
||||
args = [group, layout, str(len(items))]
|
||||
for name, item_layout in items:
|
||||
args.extend([name, item_layout])
|
||||
run(CONTROLLER + ["SetInputMethodGroupInfo", "ssa(ss)"] + args)
|
||||
run(CONTROLLER + ["Save"])
|
||||
|
||||
|
||||
def reload_bus():
|
||||
# Offline finalization and updates over SSH may have no session bus.
|
||||
try:
|
||||
run(["busctl", "--user", "call", "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "ReloadConfig"])
|
||||
except (OSError, subprocess.CalledProcessError, RuntimeError) as error:
|
||||
print(f"Input activation reload deferred until login: {error}", file=sys.stderr)
|
||||
|
||||
|
||||
def available_methods():
|
||||
return json.loads(run(CONTROLLER[:1] + ["--json=short"] + CONTROLLER[1:] + ["AvailableInputMethods"]))["data"][0]
|
||||
|
||||
|
||||
def wait_ready(method):
|
||||
for attempt in range(100):
|
||||
try:
|
||||
available = available_methods()
|
||||
if any(item[0] == method for item in available) and live_group()[2]:
|
||||
return
|
||||
except (OSError, ValueError, KeyError, IndexError, subprocess.CalledProcessError):
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
raise RuntimeError(f"Fcitx did not load the {method} engine after restart")
|
||||
|
||||
|
||||
def live_append(method):
|
||||
group, layout, items = live_group()
|
||||
if any(item[0] == method for item in items):
|
||||
return
|
||||
try:
|
||||
live_set(group, layout, items + [[method, ""]])
|
||||
if not any(item[0] == method for item in live_group()[2]):
|
||||
raise RuntimeError(f"Fcitx did not retain the {method} engine")
|
||||
except BaseException:
|
||||
live_set(group, layout, items)
|
||||
raise
|
||||
|
||||
|
||||
def select_live_method(method, profile):
|
||||
# Clients reconnect asynchronously after restart. SetCurrentIM silently does
|
||||
# nothing without a focused input context, so verify the saved group default.
|
||||
for attempt in range(30):
|
||||
run(["fcitx5-remote", "-s", method])
|
||||
run(["fcitx5-remote", "-c"])
|
||||
run(CONTROLLER + ["Save"])
|
||||
group = run(["fcitx5-remote", "-q"])
|
||||
for section, entries in sections(read(profile)).items():
|
||||
if re.fullmatch(r"Groups/\d+", section) and entries.get("Name") == group and entries.get("DefaultIM") == method:
|
||||
return
|
||||
time.sleep(0.1)
|
||||
print(f"Input method added; select {method} in Fcitx settings to switch to it.", file=sys.stderr)
|
||||
|
||||
|
||||
def configure(args):
|
||||
config_home = Path(os.environ.get("XDG_CONFIG_HOME") or str(Path.home() / ".config"))
|
||||
running = subprocess.run(["pgrep", "-u", str(os.getuid()), "-x", "fcitx5"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||
# Finalization can be retried after login. A live daemon owns its profile.
|
||||
if args.seed and running:
|
||||
return
|
||||
with transaction(config_home, live=running and not args.defaults):
|
||||
configure_inner(args, config_home, running)
|
||||
|
||||
|
||||
def configure_inner(args, config_home, running):
|
||||
if args.seed or args.defaults:
|
||||
method, layout = selection()
|
||||
else:
|
||||
method, layout = args.method or selection()[0], None
|
||||
if args.method:
|
||||
method = args.method
|
||||
profile = config_home / "fcitx5/profile"
|
||||
original = read(profile)
|
||||
if args.seed and not stock_profile(original):
|
||||
return
|
||||
if not args.seed and not args.defaults and not running:
|
||||
raise RuntimeError("Log in to an Omarchy desktop before changing input methods")
|
||||
if not args.seed and not args.defaults and method == "none":
|
||||
raise ValueError("Choose an input engine to add")
|
||||
|
||||
if args.seed:
|
||||
atomic_write(profile, profile_text(layout, method))
|
||||
defaults(config_home, fresh=True)
|
||||
activation_default()
|
||||
font_default(config_home, method)
|
||||
elif args.defaults:
|
||||
fresh = stock_profile(original)
|
||||
changed = defaults(config_home, fresh=fresh)
|
||||
if activation_default():
|
||||
reload_bus()
|
||||
# Upgrades can run through sudo/SSH without access to the live user's bus.
|
||||
# Never overwrite its profile or restart input in that context.
|
||||
if fresh:
|
||||
if running:
|
||||
try:
|
||||
group, old_layout, items = live_group()
|
||||
if old_layout == "us" and items == [["keyboard-us", ""]]:
|
||||
new_items = [[f"keyboard-{layout}", ""]]
|
||||
if method != "none":
|
||||
if any(item[0] == method for item in available_methods()):
|
||||
new_items.append([method, ""])
|
||||
else:
|
||||
print(f"Finish input setup after login: omarchy setup input {method}", file=sys.stderr)
|
||||
live_set(group, layout, new_items)
|
||||
if live_group()[1:] != (layout, new_items):
|
||||
live_set(group, old_layout, items)
|
||||
raise RuntimeError("Fcitx did not retain the installed keyboard settings")
|
||||
except (OSError, ValueError, KeyError, IndexError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||
print(f"Input profile alignment deferred until login: {error}", file=sys.stderr)
|
||||
else:
|
||||
atomic_write(profile, profile_text(layout, method))
|
||||
if running and changed:
|
||||
try:
|
||||
run(CONTROLLER + ["ReloadConfig"])
|
||||
except (OSError, subprocess.CalledProcessError) as error:
|
||||
print(f"Input defaults reload deferred until login: {error}", file=sys.stderr)
|
||||
else:
|
||||
# New addons are discovered only at startup. Supervision remains with the
|
||||
# existing unit; never spawn a second fcitx instance ourselves.
|
||||
activation_default()
|
||||
defaults(config_home, fresh=stock_profile(original))
|
||||
reload_bus()
|
||||
run(["omarchy-restart-xcompose"])
|
||||
wait_ready(method)
|
||||
live_append(method)
|
||||
# The selected language becomes the next active engine; leave Latin input
|
||||
# active until the user deliberately switches. Keep all other group items.
|
||||
select_live_method(method, profile)
|
||||
font_default(config_home, method)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
commands = parser.add_subparsers(dest="command", required=True)
|
||||
commands.add_parser("status")
|
||||
configure_parser = commands.add_parser("configure")
|
||||
mode = configure_parser.add_mutually_exclusive_group()
|
||||
mode.add_argument("--seed", action="store_true")
|
||||
mode.add_argument("--defaults", action="store_true")
|
||||
configure_parser.add_argument("method", nargs="?", choices=PRESETS)
|
||||
args = parser.parse_args()
|
||||
if args.command == "status":
|
||||
method, layout = selection()
|
||||
print(json.dumps({"installed_method": method, "layout": layout}))
|
||||
else:
|
||||
configure(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||
raise SystemExit(f"Input method setup failed: {error}")
|
||||
@@ -0,0 +1,139 @@
|
||||
"""Live Fcitx state for the existing keyboard layout widget."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
|
||||
BUS = "org.fcitx.Fcitx5"
|
||||
INTERFACE = "org.fcitx.Fcitx.Controller1"
|
||||
|
||||
|
||||
def call(bus, method, args=None):
|
||||
# Reading the bar must neither activate a stopped service nor wait indefinitely.
|
||||
return bus.call_sync(BUS, "/controller", INTERFACE, method, args, None,
|
||||
Gio.DBusCallFlags.NO_AUTO_START, 1000, None).unpack()
|
||||
|
||||
|
||||
def snapshot(bus):
|
||||
group = call(bus, "CurrentInputMethodGroup")[0]
|
||||
methods = list(dict.fromkeys(item[0] for item in
|
||||
call(bus, "InputMethodGroupInfo", GLib.Variant("(s)", (group,)))[1]))
|
||||
info = call(bus, "CurrentInputMethodInfo")
|
||||
return {"methods": methods, "current": info[0], "name": info[1], "label": info[4], "language": info[5]}
|
||||
|
||||
|
||||
def cycle(bus):
|
||||
state = snapshot(bus)
|
||||
methods = state["methods"]
|
||||
if len(methods) > 1 and state["current"] in methods:
|
||||
following = methods[(methods.index(state["current"]) + 1) % len(methods)]
|
||||
call(bus, "SetCurrentIM", GLib.Variant("(s)", (following,)))
|
||||
|
||||
|
||||
class Indicator:
|
||||
def __init__(self, bus):
|
||||
self.bus = bus
|
||||
self.pending = ""
|
||||
self.last = ""
|
||||
self.entries = {}
|
||||
|
||||
def select_next(self):
|
||||
state = snapshot(self.bus)
|
||||
methods = state["methods"]
|
||||
if len(methods) < 2:
|
||||
return
|
||||
current = self.pending or state["current"] or self.last or methods[0]
|
||||
if current not in methods:
|
||||
current = methods[0]
|
||||
self.pending = methods[(methods.index(current) + 1) % len(methods)]
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
state = snapshot(self.bus)
|
||||
methods = state["methods"]
|
||||
if self.pending not in methods:
|
||||
self.pending = ""
|
||||
if self.pending and state["current"]:
|
||||
# Before the first application gains focus Fcitx has no input context.
|
||||
# Retain the click until it has one, without changing startup defaults.
|
||||
call(self.bus, "SetCurrentIM", GLib.Variant("(s)", (self.pending,)))
|
||||
state = snapshot(self.bus)
|
||||
if state["current"] == self.pending:
|
||||
self.pending = ""
|
||||
if state["current"]:
|
||||
self.last = state["current"]
|
||||
else:
|
||||
chosen = self.pending or (self.last if self.last in methods else "") or next(iter(methods), "")
|
||||
if chosen not in self.entries:
|
||||
self.entries = {entry[0]: entry for entry in call(self.bus, "AvailableInputMethods")[0]}
|
||||
entry = self.entries.get(chosen)
|
||||
if entry:
|
||||
state.update(current=entry[0], name=entry[1], label=entry[4], language=entry[5])
|
||||
return state
|
||||
|
||||
|
||||
def watch(bus):
|
||||
previous = None
|
||||
indicator = Indicator(bus)
|
||||
loop = GLib.MainLoop()
|
||||
buffer = ""
|
||||
timer = 0
|
||||
|
||||
def refresh():
|
||||
nonlocal previous
|
||||
try:
|
||||
state = indicator.refresh()
|
||||
except GLib.Error:
|
||||
indicator.pending = ""
|
||||
indicator.last = ""
|
||||
state = {"methods": [], "current": "", "name": "", "language": ""}
|
||||
line = json.dumps(state, ensure_ascii=False)
|
||||
if line != previous:
|
||||
print(line, flush=True)
|
||||
previous = line
|
||||
|
||||
def poll():
|
||||
nonlocal timer
|
||||
refresh()
|
||||
timer = GLib.timeout_add(50 if indicator.pending else 500, poll)
|
||||
return GLib.SOURCE_REMOVE
|
||||
|
||||
def command(source, condition):
|
||||
nonlocal buffer, timer
|
||||
data = os.read(sys.stdin.fileno(), 4096)
|
||||
if not data:
|
||||
loop.quit()
|
||||
return GLib.SOURCE_REMOVE
|
||||
buffer += data.decode()
|
||||
while "\n" in buffer:
|
||||
line, buffer = buffer.split("\n", 1)
|
||||
if line == "cycle":
|
||||
try:
|
||||
indicator.select_next()
|
||||
except GLib.Error:
|
||||
indicator.pending = ""
|
||||
refresh()
|
||||
GLib.source_remove(timer)
|
||||
timer = GLib.timeout_add(50 if indicator.pending else 500, poll)
|
||||
return GLib.SOURCE_CONTINUE
|
||||
|
||||
# Fcitx's controller has no current-method-changed signal. Keep one bus
|
||||
# connection open instead of spawning a command on every poll. Polling also
|
||||
# follows per-application input contexts and recovers after service restarts.
|
||||
GLib.io_add_watch(sys.stdin, GLib.IO_IN | GLib.IO_HUP, command)
|
||||
poll()
|
||||
loop.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
if sys.argv[1:] == ["cycle"]:
|
||||
cycle(bus)
|
||||
else:
|
||||
watch(bus)
|
||||
except GLib.Error as error:
|
||||
raise SystemExit(str(error))
|
||||
@@ -0,0 +1,5 @@
|
||||
# Installed per-user so D-Bus activation uses the supervised Omarchy unit.
|
||||
[D-BUS Service]
|
||||
Name=org.fcitx.Fcitx5
|
||||
Exec=/usr/bin/fcitx5
|
||||
SystemdService=omarchy-fcitx5.service
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"none": {"label": "Keyboard only", "packages": [], "font": ""},
|
||||
"mozc": {"label": "Japanese (Mozc)", "packages": ["fcitx5-mozc"], "font": "JP"},
|
||||
"hangul": {"label": "Korean (Hangul)", "packages": ["fcitx5-hangul"], "font": "KR"},
|
||||
"pinyin": {"label": "Chinese (Simplified, Pinyin)", "packages": ["fcitx5-chinese-addons"], "font": "SC"},
|
||||
"chewing": {"label": "Chinese (Traditional, Zhuyin)", "packages": ["fcitx5-chewing"], "font": "TC"}
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"setup.monitors": {"icon":"","label":"Monitors","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""},
|
||||
"setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""},
|
||||
"setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""},
|
||||
"setup.input-method": {"icon":"","label":"Input Language","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-setup-input'"},
|
||||
"setup.network": {"icon":"","label":"Network","aliases":["network"]},
|
||||
"setup.network.dns": {"icon":"","label":"DNS","aliases":["dns"]},
|
||||
"setup.network.dns.dhcp": {"icon":"","label":"DHCP","checked":"[[ \"$(omarchy-dns)\" == \"DHCP\" ]]","action":"omarchy-dns DHCP"},
|
||||
@@ -135,6 +136,7 @@
|
||||
"setup.network.dns.google": {"icon":"","label":"Google","checked":"[[ \"$(omarchy-dns)\" == \"Google\" ]]","action":"omarchy-dns Google"},
|
||||
"setup.network.dns.custom": {"icon":"","label":"Custom","checked":"[[ \"$(omarchy-dns)\" == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-dns Custom'"},
|
||||
"setup.network.qr": {"icon":"","label":"QR Code","aliases":["wifi-qr"],"when":"[[ $(omarchy-network-status) == wifi* ]]","action":"omarchy-shell shell summon omarchy.wifiqr"},
|
||||
"setup.claude-api": {"icon":"","label":"Claude Code API","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-claude-api"},
|
||||
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
|
||||
"setup.default.agent": {"icon":"","label":"Agent","title":"Default Agent"},
|
||||
"setup.default.agent.agy": {"icon":"","label":"Antigravity","checked":"[[ \"$(omarchy-default-agent)\" == \"agy\" ]]","action":"omarchy-default-agent agy"},
|
||||
@@ -143,8 +145,10 @@
|
||||
"setup.default.agent.copilot": {"icon":"","label":"Copilot","checked":"[[ \"$(omarchy-default-agent)\" == \"copilot\" ]]","action":"omarchy-default-agent copilot"},
|
||||
"setup.default.agent.crush": {"icon":"","label":"Crush","checked":"[[ \"$(omarchy-default-agent)\" == \"crush\" ]]","action":"omarchy-default-agent crush"},
|
||||
"setup.default.agent.cursor-agent": {"icon":"","iconFont":"omarchy","label":"Cursor CLI","checked":"[[ \"$(omarchy-default-agent)\" == \"cursor-agent\" ]]","action":"omarchy-default-agent cursor-agent"},
|
||||
"setup.default.agent.dim": {"icon":"","label":"Dim Agent","checked":"[[ \"$(omarchy-default-agent)\" == \"dim\" ]]","action":"omarchy-default-agent dim"},
|
||||
"setup.default.agent.grok": {"icon":"","iconFont":"omarchy","label":"Grok","checked":"[[ \"$(omarchy-default-agent)\" == \"grok\" ]]","action":"omarchy-default-agent grok"},
|
||||
"setup.default.agent.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes","checked":"[[ \"$(omarchy-default-agent)\" == \"hermes\" ]]","action":"omarchy-default-agent hermes"},
|
||||
"setup.default.agent.kimi": {"icon":"","label":"Kimi Code","checked":"[[ \"$(omarchy-default-agent)\" == \"kimi\" ]]","action":"omarchy-default-agent kimi"},
|
||||
"setup.default.agent.muse": {"icon":"","label":"Muse Code","checked":"[[ \"$(omarchy-default-agent)\" == \"muse\" ]]","action":"omarchy-default-agent muse"},
|
||||
"setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"},
|
||||
"setup.default.agent.openclaw": {"icon":"","iconFont":"omarchy","label":"OpenClaw","checked":"[[ \"$(omarchy-default-agent)\" == \"openclaw\" ]]","action":"omarchy-default-agent openclaw"},
|
||||
@@ -173,6 +177,12 @@
|
||||
"setup.default.editor.helix": {"icon":"","label":"Helix","checked":"[[ \"$(omarchy-default-editor)\" == \"helix\" ]]","action":"omarchy-default-editor helix"},
|
||||
"setup.default.editor.vim": {"icon":"","label":"Vim","checked":"[[ \"$(omarchy-default-editor)\" == \"vim\" ]]","action":"omarchy-default-editor vim"},
|
||||
"setup.default.editor.emacs": {"icon":" ","label":"Emacs","checked":"[[ \"$(omarchy-default-editor)\" == \"emacs\" ]]","action":"omarchy-default-editor emacs"},
|
||||
"setup.language": {"icon":"","label":"Language"},
|
||||
"setup.language.en": {"icon":"","label":"English","checked":"[[ \"$(omarchy-language)\" == \"en_US.UTF-8\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-language en'"},
|
||||
"setup.language.zh": {"icon":"","label":"简体中文","checked":"[[ \"$(omarchy-language)\" == \"zh_CN.UTF-8\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-language zh'"},
|
||||
"setup.region": {"icon":"","label":"Region"},
|
||||
"setup.region.global": {"icon":"","label":"Global","checked":"[[ \"$(omarchy-region)\" == \"Global\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-region global'"},
|
||||
"setup.region.china": {"icon":"","label":"China","checked":"[[ \"$(omarchy-region)\" == \"China\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-region cn'"},
|
||||
"setup.plugin": {"icon":"","label":"Plugins","aliases":["plugin","plugins"]},
|
||||
"setup.plugin.enable": {"icon":"","label":"Enable Plugin","action":"omarchy-menu-plugin enable"},
|
||||
"setup.plugin.disable": {"icon":"","label":"Disable Plugin","action":"omarchy-menu-plugin disable"},
|
||||
@@ -216,6 +226,16 @@
|
||||
"install.tui": {"icon":"","label":"TUI","action":"omarchy-launch-floating-terminal-with-presentation omarchy-tui-install"},
|
||||
"install.windows": {"icon":"","label":"Windows","disabled":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
||||
"install.aur": {"icon":"","label":"AUR","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-aur-install"},
|
||||
"install.china": {"icon":"","label":"China Apps","when":"[[ \"$(omarchy-region)\" == \"China\" ]]"},
|
||||
"install.china.wechat": {"icon":"","label":"WeChat","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present wechat-universal-bwrap","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add wechat-universal-bwrap'"},
|
||||
"install.china.qq": {"icon":"","label":"QQ","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present linuxqq","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add linuxqq'"},
|
||||
"install.china.feishu": {"icon":"","label":"Feishu","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present feishu-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add feishu-bin'"},
|
||||
"install.china.dingtalk": {"icon":"","label":"DingTalk","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present dingtalk-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add dingtalk-bin'"},
|
||||
"install.china.wemeet": {"icon":"","label":"Tencent Meeting","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present wemeet-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add wemeet-bin'"},
|
||||
"install.china.wps": {"icon":"","label":"WPS Office","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"omarchy-pkg-present wps-office-cn","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-pkg-aur-add wps-office-cn'"},
|
||||
"install.china.tencent-docs": {"icon":"","label":"Tencent Docs","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"[[ -f \"$HOME/.local/share/applications/腾讯文档.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-webapp-install 腾讯文档 https://docs.qq.com \"\"'"},
|
||||
"install.china.yuque": {"icon":"","label":"Yuque","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"[[ -f \"$HOME/.local/share/applications/语雀.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-webapp-install 语雀 https://www.yuque.com/dashboard \"\"'"},
|
||||
"install.china.shimo": {"icon":"","label":"Shimo","when":"[[ \"$(omarchy-region)\" == \"China\" ]]","disabled":"[[ -f \"$HOME/.local/share/applications/石墨文档.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-webapp-install 石墨文档 https://shimo.im/desktop \"\"'"},
|
||||
"install.preinstalls": {"icon":"","label":"Preinstalls","disabled":"[[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
||||
"install.browser.chrome": {"icon":"","label":"Chrome","disabled":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
||||
"install.browser.edge": {"icon":"","label":"Edge","disabled":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
// Chinese menu labels, overlaid on omarchy-menu.jsonc when LANG is zh_CN.
|
||||
{
|
||||
"apps": {"label":"应用"},
|
||||
"learn": {"label":"学习"},
|
||||
"trigger": {"label":"触发"},
|
||||
"style": {"label":"风格"},
|
||||
"setup": {"label":"设置"},
|
||||
"install": {"label":"安装"},
|
||||
"remove": {"label":"移除"},
|
||||
"update": {"label":"更新"},
|
||||
"about": {"label":"关于"},
|
||||
"system": {"label":"系统"},
|
||||
"system.screensaver": {"label":"屏保"},
|
||||
"system.lock": {"label":"锁屏"},
|
||||
"system.suspend": {"label":"睡眠"},
|
||||
"system.hibernate": {"label":"休眠"},
|
||||
"system.logout": {"label":"注销"},
|
||||
"system.reboot": {"label":"重启"},
|
||||
"system.shutdown": {"label":"关机"},
|
||||
"learn.keybindings": {"label":"快捷键"},
|
||||
"learn.omarchy": {"label":"Omarchy 手册"},
|
||||
"learn.community": {"label":"社区"},
|
||||
"trigger.emoji": {"label":"表情"},
|
||||
"trigger.reminder": {"label":"提醒"},
|
||||
"trigger.capture": {"label":"截取"},
|
||||
"trigger.capture.screenshot": {"label":"截图"},
|
||||
"trigger.capture.screenrecord.stop": {"label":"停止录屏"},
|
||||
"trigger.capture.screenrecord": {"label":"录屏"},
|
||||
"trigger.capture.text": {"label":"OCR 取字"},
|
||||
"trigger.capture.qr": {"label":"二维码"},
|
||||
"trigger.capture.color": {"label":"取色"},
|
||||
"trigger.capture.screenrecord.no-audio": {"label":"无音频"},
|
||||
"trigger.capture.screenrecord.desktop-audio": {"label":"含桌面音频"},
|
||||
"trigger.capture.screenrecord.microphone": {"label":"含桌面与麦克风音频"},
|
||||
"trigger.capture.screenrecord.webcam": {"label":"含桌面、麦克风与摄像头"},
|
||||
"trigger.transcode": {"label":"转码"},
|
||||
"trigger.share": {"label":"分享"},
|
||||
"trigger.toggle": {"label":"开关"},
|
||||
"trigger.hardware": {"label":"硬件"},
|
||||
"trigger.tests": {"label":"测速"},
|
||||
"trigger.hardware.laptop-display": {"label":"笔记本屏幕"},
|
||||
"trigger.hardware.mirror-display": {"label":"镜像显示"},
|
||||
"trigger.hardware.hybrid-gpu": {"label":"混合显卡"},
|
||||
"trigger.hardware.touchpad": {"label":"触控板"},
|
||||
"trigger.hardware.touchpad-haptics": {"label":"触控板振动"},
|
||||
"trigger.hardware.touchpad-haptics.low": {"label":"低"},
|
||||
"trigger.hardware.touchpad-haptics.mid": {"label":"中"},
|
||||
"trigger.hardware.touchpad-haptics.high": {"label":"高"},
|
||||
"trigger.hardware.touchscreen": {"label":"触摸屏"},
|
||||
"trigger.reminder.set": {"label":"设一个提醒"},
|
||||
"trigger.reminder.show": {"label":"查看全部"},
|
||||
"trigger.reminder.clear": {"label":"清除全部"},
|
||||
"trigger.share.clipboard": {"label":"剪贴板"},
|
||||
"trigger.share.file": {"label":"文件"},
|
||||
"trigger.share.folder": {"label":"文件夹"},
|
||||
"trigger.share.receive": {"label":"接收"},
|
||||
"trigger.toggle.idle-lock": {"label":"保持唤醒"},
|
||||
"trigger.toggle.notifications": {"label":"通知"},
|
||||
"trigger.toggle.crash-capture": {"label":"崩溃捕获"},
|
||||
"trigger.toggle.screensaver": {"label":"屏保"},
|
||||
"trigger.toggle.nightlight": {"label":"夜间模式"},
|
||||
"trigger.toggle.top-bar": {"label":"菜单栏"},
|
||||
"trigger.toggle.battery-percentage": {"label":"电量百分比"},
|
||||
"trigger.toggle.workspace-layout": {"label":"工作区布局"},
|
||||
"trigger.toggle.window-gaps": {"label":"窗口间距"},
|
||||
"trigger.toggle.one-window-ratio": {"label":"单窗口比例"},
|
||||
"trigger.tests.network-speedtest": {"label":"网络测速"},
|
||||
"trigger.tests.disk-speedtest": {"label":"磁盘测速"},
|
||||
"style.theme": {"label":"主题"},
|
||||
"style.background": {"label":"壁纸"},
|
||||
"style.unlock": {"label":"解锁画面"},
|
||||
"style.font": {"label":"字体"},
|
||||
"style.bar": {"label":"菜单栏"},
|
||||
"style.bar.position": {"label":"位置"},
|
||||
"style.bar.transparency": {"label":"透明"},
|
||||
"style.screensaver": {"label":"屏保"},
|
||||
"style.about": {"label":"关于"},
|
||||
"style.bar.position.top": {"label":"顶部"},
|
||||
"style.bar.position.bottom": {"label":"底部"},
|
||||
"style.bar.position.left": {"label":"左侧"},
|
||||
"style.bar.position.right": {"label":"右侧"},
|
||||
"style.about.text": {"label":"编辑文字"},
|
||||
"style.about.image": {"label":"取自图片"},
|
||||
"style.about.default": {"label":"恢复默认"},
|
||||
"style.screensaver.text": {"label":"编辑文字"},
|
||||
"style.screensaver.image": {"label":"取自图片"},
|
||||
"style.screensaver.default": {"label":"恢复默认"},
|
||||
"setup.monitors": {"label":"显示器"},
|
||||
"setup.keybindings": {"label":"快捷键"},
|
||||
"setup.input": {"label":"输入设备"},
|
||||
"setup.input-method": {"label":"输入语言"},
|
||||
"setup.language": {"label":"语言"},
|
||||
"setup.network": {"label":"网络"},
|
||||
"setup.network.dns.custom": {"label":"自定义"},
|
||||
"setup.network.qr": {"label":"二维码"},
|
||||
"setup.default": {"label":"默认程序"},
|
||||
"setup.default.agent": {"title":"默认 Agent"},
|
||||
"setup.default.browser": {"label":"浏览器","title":"默认浏览器"},
|
||||
"setup.default.terminal": {"label":"终端","title":"默认终端"},
|
||||
"setup.default.editor": {"label":"编辑器","title":"默认编辑器"},
|
||||
"setup.plugin": {"label":"插件"},
|
||||
"setup.plugin.enable": {"label":"启用插件"},
|
||||
"setup.plugin.disable": {"label":"停用插件"},
|
||||
"setup.plugin.add": {"label":"添加插件"},
|
||||
"setup.plugin.clone": {"label":"克隆插件"},
|
||||
"setup.plugin.remove": {"label":"移除插件"},
|
||||
"setup.security": {"label":"安全"},
|
||||
"setup.config": {"label":"配置"},
|
||||
"setup.security.fingerprint": {"label":"指纹"},
|
||||
"setup.security.passwordless-sudo": {"label":"免密 sudo"},
|
||||
"setup.security.sudoless-docker": {"label":"免 sudo Docker"},
|
||||
"setup.direct-boot": {"label":"直接启动"},
|
||||
"setup.reset": {"label":"恢复出厂设置"},
|
||||
"install.package": {"label":"软件包"},
|
||||
"install.service": {"label":"服务"},
|
||||
"install.development": {"label":"开发环境"},
|
||||
"install.editor": {"label":"编辑器"},
|
||||
"install.style": {"label":"风格"},
|
||||
"install.style.theme": {"label":"主题"},
|
||||
"install.style.background": {"label":"壁纸"},
|
||||
"install.style.font": {"label":"字体"},
|
||||
"install.gaming": {"label":"游戏"},
|
||||
"install.browser": {"label":"浏览器"},
|
||||
"install.webapp": {"label":"网页应用"},
|
||||
"install.terminal": {"label":"终端"},
|
||||
"install.preinstalls": {"label":"预装应用"},
|
||||
"install.service.chromium-account": {"label":"Chromium 账号"},
|
||||
"install.ai.dictation": {"label":"语音听写"},
|
||||
"install.gaming.xbox-cloud": {"label":"Xbox 云游戏"},
|
||||
"install.gaming.xbox-controllers": {"label":"Xbox 手柄"},
|
||||
"install.gaming.retro-launcher": {"label":"RetroArch 启动器"},
|
||||
"install.development.docker-dbs": {"label":"Docker 数据库"},
|
||||
"remove.package": {"label":"软件包"},
|
||||
"remove.ai": {"title":"移除"},
|
||||
"remove.service": {"label":"服务","title":"移除"},
|
||||
"remove.development": {"label":"开发环境","title":"移除"},
|
||||
"remove.theme": {"label":"主题"},
|
||||
"remove.gaming": {"label":"游戏","title":"移除"},
|
||||
"remove.browser": {"label":"浏览器","title":"移除"},
|
||||
"remove.webapp": {"label":"网页应用"},
|
||||
"remove.preinstalls": {"label":"预装应用"},
|
||||
"remove.security": {"label":"安全","title":"移除"},
|
||||
"remove.security.fingerprint": {"label":"指纹"},
|
||||
"remove.security.sudoless-docker": {"label":"免 sudo Docker"},
|
||||
"remove.ai.dictation": {"label":"语音听写"},
|
||||
"remove.gaming.xbox-cloud": {"label":"Xbox 云游戏"},
|
||||
"remove.gaming.xbox-controllers": {"label":"Xbox 手柄 ()"},
|
||||
"remove.development.javascript": {"title":"移除"},
|
||||
"remove.development.php": {"title":"移除"},
|
||||
"remove.development.elixir": {"title":"移除"},
|
||||
"update.channel": {"label":"通道"},
|
||||
"update.config": {"label":"配置","title":"重置为默认"},
|
||||
"update.themes": {"label":"附加主题"},
|
||||
"update.process": {"label":"进程","title":"重启"},
|
||||
"update.hardware": {"label":"硬件","title":"重启"},
|
||||
"update.firmware": {"label":"固件"},
|
||||
"update.password": {"label":"密码"},
|
||||
"update.timezone": {"label":"时区"},
|
||||
"update.time": {"label":"时间"},
|
||||
"update.hardware.audio": {"label":"音频"},
|
||||
"update.hardware.bluetooth": {"label":"蓝牙"},
|
||||
"update.hardware.trackpad": {"label":"触控板"},
|
||||
"update.password.drive": {"label":"磁盘加密"},
|
||||
"update.password.user": {"label":"用户"},
|
||||
"trigger.toggle.animations": {"label":"动画"},
|
||||
"trigger.toggle.theme-sync": {"label":"Herdr 主题同步"},
|
||||
"setup.region": {"label":"地区"},
|
||||
"setup.region.global": {"label":"全球"},
|
||||
"setup.region.china": {"label":"中国"},
|
||||
"install.china": {"label":"国内应用"},
|
||||
"install.china.wechat": {"label":"微信"},
|
||||
"install.china.feishu": {"label":"飞书"},
|
||||
"install.china.dingtalk": {"label":"钉钉"},
|
||||
"install.china.wemeet": {"label":"腾讯会议"},
|
||||
"install.china.tencent-docs": {"label":"腾讯文档"},
|
||||
"install.china.yuque": {"label":"语雀"},
|
||||
"install.china.shimo": {"label":"石墨文档"},
|
||||
"update.channel.stable": {"label":"稳定版"},
|
||||
"update.channel.rc": {"label":"候选版"},
|
||||
"update.channel.edge": {"label":"尝鲜版"},
|
||||
"update.channel.dev": {"label":"开发版"},
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[source.crates-io]
|
||||
replace-with = "aliyun"
|
||||
|
||||
[source.aliyun]
|
||||
registry = "sparse+https://mirrors.aliyun.com/crates.io-index/"
|
||||
@@ -0,0 +1,8 @@
|
||||
NPM_CONFIG_REGISTRY=https://registry.npmmirror.com
|
||||
PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
|
||||
UV_DEFAULT_INDEX=https://mirrors.aliyun.com/pypi/simple/
|
||||
GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
|
||||
RUSTUP_DIST_SERVER=https://mirrors.aliyun.com/rustup
|
||||
RUSTUP_UPDATE_ROOT=https://mirrors.aliyun.com/rustup/rustup
|
||||
BUNDLE_MIRROR__ALL=https://mirrors.aliyun.com/rubygems/
|
||||
MAVEN_ARGS=--global-settings /usr/share/omarchy/default/regions/cn/maven-settings.xml
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
:sources:
|
||||
- https://mirrors.aliyun.com/rubygems/
|
||||
@@ -0,0 +1,9 @@
|
||||
<settings>
|
||||
<mirrors>
|
||||
<mirror>
|
||||
<id>aliyun</id>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</mirror>
|
||||
</mirrors>
|
||||
</settings>
|
||||
@@ -0,0 +1,2 @@
|
||||
# Installed from the ISO's offline repository before enabling ArchLinuxCN.
|
||||
archlinuxcn-keyring
|
||||
@@ -0,0 +1 @@
|
||||
Server = https://mirror.omarchy.cn/$repo/os/$arch
|
||||
@@ -0,0 +1 @@
|
||||
Server = https://rc-mirror.omarchy.cn/$repo/os/$arch
|
||||
Loaded 100 of 229 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user