Compare commits

..
Author SHA1 Message Date
ZacharyZhang-NY 16faf06fcf Switch the region under Setup > Region, and offer China Apps
omarchy region cn|global switches an installed machine: cn upgrades,
trusts ArchLinuxCN through the rendered China config, records the
marker and links the developer mirrors, then refreshes pacman;
global undoes only what Omarchy made. The developer mirrors reach
new sessions only, so a terminal switch offers to log out.

Install > China Apps, shown only in China, installs WeChat, QQ,
Feishu, DingTalk, Tencent Meeting and WPS Office from the AUR and
sets up Tencent Docs, Yuque and Shimo as web apps.

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

No files matched your search

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