Compare commits
77
Commits
No files matched your search
@@ -6,6 +6,7 @@ matching guide before starting:
|
||||
- [`agents/skills/command-metadata.md`](agents/skills/command-metadata.md) - adding or changing commands in `bin/`
|
||||
- [`agents/skills/install-scripts.md`](agents/skills/install-scripts.md) - working under `install/` or on system/user setup commands
|
||||
- [`agents/skills/shell-dev.md`](agents/skills/shell-dev.md) - editing the Quickshell desktop under `shell/`
|
||||
- [`agents/skills/icon-font.md`](agents/skills/icon-font.md) - adding branded glyphs to `default/fonts/omarchy/omarchy.ttf`
|
||||
- [`agents/skills/acceptance-tests.md`](agents/skills/acceptance-tests.md) - writing or running graphical acceptance tests under `test/acceptance.d/`
|
||||
- [`agents/skills/visual-verification.md`](agents/skills/visual-verification.md) - verifying any change with a visual effect in the running UI
|
||||
- [`docs/migrations.md`](docs/migrations.md) - creating or changing migrations under `migrations/`
|
||||
|
||||
@@ -4,6 +4,77 @@ Omarchy is a beautiful, modern & opinionated Linux distribution by DHH.
|
||||
|
||||
Read more at [omarchy.org](https://omarchy.org).
|
||||
|
||||
## The Omarchy Manual
|
||||
|
||||
The manual lives in [`manual/`](manual/), which is its authoritative source. It's
|
||||
mirrored to [learn.omacom.io](https://learn.omacom.io/2/the-omarchy-manual), where
|
||||
its screenshots are also hosted.
|
||||
|
||||
- [Welcome to Omarchy!](manual/01-welcome-to-omarchy.md)
|
||||
|
||||
**The Basics**
|
||||
|
||||
- [Getting Started](manual/02-getting-started.md)
|
||||
- [Coming From Mac or Windows](manual/03-coming-from-mac-or-windows.md)
|
||||
- [Navigation](manual/04-navigation.md)
|
||||
- [The top bar](manual/05-the-top-bar.md)
|
||||
- [Themes](manual/06-themes.md)
|
||||
- [Hotkeys](manual/07-hotkeys.md)
|
||||
- [Unified Clipboard & History](manual/08-unified-clipboard-history.md)
|
||||
- [Reminders](manual/09-reminders.md)
|
||||
- [Notices](manual/10-notices.md)
|
||||
- [Text Extraction & Dictation](manual/11-text-extraction-dictation.md)
|
||||
- [Screenshots & Recording](manual/12-screenshots-recording.md)
|
||||
- [Toggles, idle & screensaver](manual/13-toggles-idle-screensaver.md)
|
||||
- [Omarchy CLI](manual/14-omarchy-cli.md)
|
||||
|
||||
**The Applications**
|
||||
|
||||
- [Terminal](manual/15-terminal.md)
|
||||
- [Neovim](manual/16-neovim.md)
|
||||
- [AI](manual/17-ai.md)
|
||||
- [Development Tools](manual/18-development-tools.md)
|
||||
- [Shell Tools](manual/19-shell-tools.md)
|
||||
- [Shell Functions](manual/20-shell-functions.md)
|
||||
- [TUIs](manual/21-tuis.md)
|
||||
- [GUIs](manual/22-guis.md)
|
||||
- [Browsers](manual/23-browsers.md)
|
||||
- [Commercial apps/services](manual/24-commercial-apps-services.md)
|
||||
- [Web Apps](manual/25-web-apps.md)
|
||||
- [Gaming](manual/26-gaming.md)
|
||||
- [Filling out PDFs](manual/27-filling-out-pdfs.md)
|
||||
- [Windows VM](manual/28-windows-vm.md)
|
||||
- [Other Packages](manual/29-other-packages.md)
|
||||
|
||||
**Configuration**
|
||||
|
||||
- [Updates](manual/30-updates.md)
|
||||
- [Dotfiles](manual/31-dotfiles.md)
|
||||
- [Shell plugins](manual/32-shell-plugins.md)
|
||||
- [Monitors](manual/33-monitors.md)
|
||||
- [Keyboard, Mouse, Trackpad](manual/34-keyboard-mouse-trackpad.md)
|
||||
- [Networking](manual/35-networking.md)
|
||||
- [System sleep](manual/36-system-sleep.md)
|
||||
- [Hardware authentication](manual/37-hardware-authentication.md)
|
||||
- [Fonts](manual/38-fonts.md)
|
||||
- [Backgrounds](manual/39-backgrounds.md)
|
||||
- [Prompt](manual/40-prompt.md)
|
||||
- [Branding](manual/41-branding.md)
|
||||
- [Common tweaks](manual/42-common-tweaks.md)
|
||||
- [Extra themes](manual/43-extra-themes.md)
|
||||
- [Making your own theme](manual/44-making-your-own-theme.md)
|
||||
|
||||
**The Rest**
|
||||
|
||||
- [Mac support](manual/45-mac-support.md)
|
||||
- [Troubleshooting](manual/46-troubleshooting.md)
|
||||
- [FAQ](manual/47-faq.md)
|
||||
- [System snapshots](manual/48-system-snapshots.md)
|
||||
- [Security](manual/49-security.md)
|
||||
- [Omarchy on...](manual/50-omarchy-on.md)
|
||||
- [Dual Boot Install](manual/51-dual-boot-install.md)
|
||||
- [Unattended Installs](manual/52-unattended-installs.md)
|
||||
|
||||
## License
|
||||
|
||||
Omarchy is released under the [MIT License](https://opensource.org/licenses/MIT).
|
||||
@@ -0,0 +1,79 @@
|
||||
# Omarchy Icon Font
|
||||
|
||||
Read this before adding a branded glyph to `default/fonts/omarchy/omarchy.ttf`.
|
||||
|
||||
The Omarchy icon font is a small private-use font carrying the marks Nerd
|
||||
Fonts does not have: the Omarchy logo and the agent and app brand marks. The
|
||||
menu draws one by naming the font on an entry:
|
||||
|
||||
```jsonc
|
||||
"setup.default.agent.grok": {"icon":"","iconFont":"omarchy","label":"Grok", ...}
|
||||
```
|
||||
|
||||
Without `iconFont`, an entry's `icon` is drawn in the menu font, so reach for
|
||||
this font only when a Nerd Font glyph would misrepresent the thing. A generic
|
||||
robot for four different AI apps is the case that justifies a real mark; a
|
||||
folder or a microphone is not.
|
||||
|
||||
`default/fonts/omarchy/README.md` lists every glyph with the URL its artwork
|
||||
came from. Keep that list accurate — it is the only record of provenance.
|
||||
|
||||
## Adding a glyph
|
||||
|
||||
`omarchy dev font` does the work:
|
||||
|
||||
```bash
|
||||
omarchy dev font list
|
||||
omarchy dev font add ollama https://simpleicons.org/icons/ollama.svg
|
||||
```
|
||||
|
||||
`add` fetches the SVG, scales it into the same 64..960 box the existing marks
|
||||
use so it lands at their optical size, appends it at the next free private-use
|
||||
codepoint, and adds a line to the font README. It prints the codepoint and the
|
||||
glyph itself.
|
||||
|
||||
The source must be a **monochrome SVG with a single `<path>`**, because the
|
||||
menu recolors the glyph with the active theme's foreground and selection
|
||||
colors. Brand icon sets such as <https://simpleicons.org> publish exactly that
|
||||
shape. App favicons usually do not: they are multi-color, carry a container
|
||||
tile, or split across several paths. Prefer the official mark when it is
|
||||
published as flat monochrome art, and fall back to an icon set's redraw when it
|
||||
is not.
|
||||
|
||||
Two-tone marks are a trap. Every path becomes solid foreground, so a logo whose
|
||||
meaning depends on lighter and darker halves turns into an unreadable blob.
|
||||
Pick a source whose silhouette alone reads.
|
||||
|
||||
## After adding
|
||||
|
||||
The command prints these, and all of them matter:
|
||||
|
||||
- Point the menu entry at the new codepoint with `"iconFont":"omarchy"`.
|
||||
- Bump the charset range asserted in `test/shell.d/menu-test.sh`; that test
|
||||
pins the font's coverage and fails until it matches.
|
||||
- Check the README line the command added, and give it a proper display name
|
||||
with `--label` if the glyph name is not the brand's name.
|
||||
|
||||
The font is package-owned: `omarchy-settings` installs it to
|
||||
`/usr/share/fonts/omarchy/omarchy.ttf`, so a new glyph reaches the desktop
|
||||
through a settings release, not through `omarchy update`. Between the merge and
|
||||
that release, a pulled checkout renders the new entry with no icon.
|
||||
|
||||
## Verifying
|
||||
|
||||
Render the whole font and look at it, which catches inverted contours and
|
||||
filled counters that a glyph list cannot show:
|
||||
|
||||
```bash
|
||||
python3 -c "open('/tmp/row.txt','w').write(' '.join(chr(c) for c in range(0xE900, 0xE910)))"
|
||||
magick -background white -fill black -font default/fonts/omarchy/omarchy.ttf \
|
||||
-pointsize 110 label:@/tmp/row.txt /tmp/font-row.png
|
||||
```
|
||||
|
||||
Then confirm it in the running menu per
|
||||
[`visual-verification.md`](visual-verification.md). Fontconfig prefers the
|
||||
packaged font over a copy in `~/.local/share/fonts` for the same family, so a
|
||||
preview needs either the real file replaced or a `<rejectfont>` rule in
|
||||
`~/.config/fontconfig/conf.d/` pointing fontconfig away from the packaged one.
|
||||
Restart the shell afterwards — Qt reads the font database at startup, so
|
||||
`omarchy menu refresh` alone will not pick up a changed font.
|
||||
@@ -1,8 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Name=ChatGPT
|
||||
Exec=omarchy-launch-webapp https://chatgpt.com/
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=chatgpt
|
||||
StartupNotify=true
|
||||
+29
-6
@@ -78,7 +78,6 @@ GROUP_DESCRIPTIONS[setup]="Interactive setup wizards"
|
||||
GROUP_DESCRIPTIONS[shell]="Omarchy shell IPC helpers"
|
||||
GROUP_DESCRIPTIONS[screensaver]="Screensaver branding and animation"
|
||||
GROUP_DESCRIPTIONS[snapshot]="System snapshots"
|
||||
GROUP_DESCRIPTIONS[style]="Global UI style controls"
|
||||
GROUP_DESCRIPTIONS[sudo]="Sudo configuration helpers"
|
||||
GROUP_DESCRIPTIONS[system]="System status, reboot, shutdown, logout, and lock"
|
||||
GROUP_DESCRIPTIONS[tailscale]="Tailscale helpers"
|
||||
@@ -86,7 +85,6 @@ GROUP_DESCRIPTIONS[theme]="Theme management"
|
||||
GROUP_DESCRIPTIONS[toggle]="Toggle Omarchy features"
|
||||
GROUP_DESCRIPTIONS[transcode]="Image and video transcoding"
|
||||
GROUP_DESCRIPTIONS[tui]="Terminal UI launchers"
|
||||
GROUP_DESCRIPTIONS[tz]="Timezone selection"
|
||||
GROUP_DESCRIPTIONS[update]="Omarchy and system updates"
|
||||
GROUP_DESCRIPTIONS[version]="Version and channel information"
|
||||
GROUP_DESCRIPTIONS[voxtype]="Voxtype dictation"
|
||||
@@ -122,6 +120,31 @@ append_pipe_value() {
|
||||
fi
|
||||
}
|
||||
|
||||
# A route can resolve to a binary before all args are consumed (`update aur` only matches the `update` binary, leaving `aur` and `--help` as leftovers)
|
||||
# --help must never be lost among those leftovers and get forwarded into the real command, so this checks the whole remainder rather than just the first token.
|
||||
# A `--` marks everything after it as belonging to the command itself, so scanning stops there.
|
||||
remaining_has_help_flag() {
|
||||
local token=""
|
||||
|
||||
for token in "$@"; do
|
||||
[[ $token == "--" ]] && break
|
||||
[[ $token == "--help" || $token == "-h" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
remaining_has_json_flag() {
|
||||
local token=""
|
||||
|
||||
for token in "$@"; do
|
||||
[[ $token == "--" ]] && break
|
||||
[[ $token == "--json" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
register_route() {
|
||||
local route="$1"
|
||||
local key="$2"
|
||||
@@ -930,7 +953,7 @@ dispatch_fast_or_help() {
|
||||
remaining=("${args[@]:DIRECT_RESOLVED_COUNT}")
|
||||
binary_path="$OMARCHY_BIN_DIR/$DIRECT_RESOLVED_BINARY"
|
||||
|
||||
if (( ${#remaining[@]} > 0 )) && [[ ${remaining[0]} == "--help" || ${remaining[0]} == "-h" ]]; then
|
||||
if remaining_has_help_flag "${remaining[@]}"; then
|
||||
if ! load_command_by_binary "$DIRECT_RESOLVED_BINARY"; then
|
||||
echo "Binary is missing or not executable: $DIRECT_RESOLVED_BINARY" >&2
|
||||
return 127
|
||||
@@ -941,7 +964,7 @@ dispatch_fast_or_help() {
|
||||
fi
|
||||
|
||||
key="${BINARY_TO_KEY[$DIRECT_RESOLVED_BINARY]}"
|
||||
if [[ " ${remaining[*]} " == *" --json "* ]]; then
|
||||
if remaining_has_json_flag "${remaining[@]}"; then
|
||||
show_command_json "$key"
|
||||
else
|
||||
show_command_help "$key"
|
||||
@@ -994,8 +1017,8 @@ dispatch_or_help() {
|
||||
key="$RESOLVED_KEY"
|
||||
remaining=("${args[@]:RESOLVED_COUNT}")
|
||||
|
||||
if (( ${#remaining[@]} > 0 )) && [[ ${remaining[0]} == "--help" || ${remaining[0]} == "-h" ]]; then
|
||||
if [[ " ${remaining[*]} " == *" --json "* ]]; then
|
||||
if remaining_has_help_flag "${remaining[@]}"; then
|
||||
if remaining_has_json_flag "${remaining[@]}"; then
|
||||
show_command_json "$key"
|
||||
else
|
||||
show_command_help "$key"
|
||||
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Launch the default coding agent in a terminal
|
||||
# omarchy:args=[--inline] [--pick]
|
||||
# omarchy:examples=omarchy agent | omarchy agent --inline
|
||||
|
||||
inline=false
|
||||
pick=false
|
||||
|
||||
# Flags only. A bare prompt would shadow the subcommands under this group, so
|
||||
# prompts go through omarchy-agent-prompt, which passes one here as --prompt.
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--inline)
|
||||
inline=true
|
||||
shift
|
||||
;;
|
||||
--pick)
|
||||
pick=true
|
||||
shift
|
||||
;;
|
||||
--prompt)
|
||||
prompt=${2:?--prompt needs a value}
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected argument: $1" >&2
|
||||
echo "To pass a prompt: omarchy agent prompt \"$*\"" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Agents refuse to remember trust for $HOME, so launches from the keybinding or
|
||||
# menu start in the work directory instead of re-asking on every session.
|
||||
[[ $PWD == "$HOME" && -d $HOME/Work ]] && cd "$HOME/Work"
|
||||
|
||||
agent=$(omarchy-default-agent)
|
||||
|
||||
# Omarchy ships without a default, so there is nothing to launch until one is
|
||||
# picked. --pick offers the choice instead of the error, which is what the
|
||||
# keybinding wants: a keypress that opens nothing explains nothing.
|
||||
if [[ -z $agent ]]; then
|
||||
[[ $pick == "true" ]] && exec omarchy-menu summon setup.default.agent
|
||||
echo "Choose default agent with: omarchy default agent <name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if omarchy-cmd-missing "$agent"; then
|
||||
echo "$agent is not installed. Choose an installed agent with: omarchy default agent <name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Agents launched from the keybinding or menu run unattended, so each one starts
|
||||
# with its own spelling of "don't stop to ask". Pi has no approval prompts.
|
||||
case "$agent" in
|
||||
opencode)
|
||||
command=(opencode --auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
;;
|
||||
gemini)
|
||||
command=(gemini --yolo)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt-interactive "$prompt")
|
||||
;;
|
||||
copilot)
|
||||
command=(copilot --allow-all)
|
||||
[[ -n ${prompt:-} ]] && command+=(--interactive "$prompt")
|
||||
;;
|
||||
crush)
|
||||
# --yolo belongs to the interactive command only; `crush run` never prompts.
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(crush run "$prompt")
|
||||
else
|
||||
command=(crush --yolo)
|
||||
fi
|
||||
;;
|
||||
claude | grok)
|
||||
command=("$agent" --permission-mode bypassPermissions)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
codex)
|
||||
command=(codex --dangerously-bypass-approvals-and-sandbox)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
omp)
|
||||
command=(omp --auto-approve)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
pi)
|
||||
command=(pi)
|
||||
[[ -n ${prompt:-} ]] && command+=("$prompt")
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported default agent: $agent" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $inline == "true" ]]; then
|
||||
exec "${command[@]}"
|
||||
else
|
||||
# A fixed app-id rather than the default org.omarchy.<binary>, so every agent
|
||||
# window shares one class for window rules and themes to single out.
|
||||
exec omarchy-launch-tui --app-id=org.omarchy.agent "${command[@]}"
|
||||
fi
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Diagnose a crashed process with the default coding agent
|
||||
# omarchy:args=<pid> [comm] [exe] [signal]
|
||||
# omarchy:examples=omarchy agent crash 1516893
|
||||
|
||||
# Clicked from a "Process crashed:" notification, or run by hand against any PID
|
||||
# in `coredumpctl list`. The method lives in the diagnose-crash skill so it is
|
||||
# edited in one place and works with whichever agent is default; this only
|
||||
# gathers the facts and points at it.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
pid=${1:?usage: omarchy-agent-crash <pid> [comm] [exe] [signal]}
|
||||
|
||||
if [[ ! $pid =~ ^[0-9]+$ ]]; then
|
||||
echo "Not a PID: $pid" >&2
|
||||
echo "Usage: omarchy agent crash <pid> (see: coredumpctl list)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
comm=${2:-unknown}
|
||||
exe=${3:-unknown}
|
||||
signal=${4:-unknown}
|
||||
|
||||
skill="$OMARCHY_PATH/default/agents/skills/diagnose-crash/SKILL.md"
|
||||
|
||||
# Looked up live so a hand-run PID still gets a timestamp. A rotated-away core
|
||||
# only costs the timestamp, so failure is tolerated.
|
||||
when=$(coredumpctl list "$pid" --no-pager --no-legend 2>/dev/null | tail -1 | cut -d' ' -f1-4) || true
|
||||
when=${when:-unknown}
|
||||
|
||||
prompt=$(
|
||||
cat <<PROMPT
|
||||
A process crashed on this Omarchy machine and I want to know why.
|
||||
|
||||
What systemd-coredump recorded:
|
||||
process: $comm
|
||||
PID: $pid
|
||||
binary: $exe
|
||||
signal: $signal
|
||||
time: $when
|
||||
|
||||
Use the diagnose-crash skill: it covers how to investigate, what to report, and
|
||||
when a crash is worth reporting upstream to Omarchy. If your harness has no skill
|
||||
mechanism, read the skill files directly and follow them instead:
|
||||
|
||||
$skill
|
||||
PROMPT
|
||||
)
|
||||
|
||||
exec omarchy-agent --prompt "$prompt"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Launch the default coding agent with a prompt
|
||||
# omarchy:args=[--inline] <prompt...>
|
||||
# omarchy:examples=omarchy agent prompt "Review this project"
|
||||
|
||||
# Prompts live here rather than on `omarchy agent`, where a bare prompt would
|
||||
# shadow the subcommands under that group.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
inline=()
|
||||
if [[ ${1:-} == "--inline" ]]; then
|
||||
inline=(--inline)
|
||||
shift
|
||||
fi
|
||||
|
||||
if (($# == 0)); then
|
||||
echo "Usage: omarchy agent prompt [--inline] <prompt...>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec omarchy-agent "${inline[@]}" --prompt "$*"
|
||||
@@ -765,24 +765,67 @@ def probe_limits(access_token: str) -> dict[str, Any]:
|
||||
return {"ok": True, "limits": limits}
|
||||
|
||||
|
||||
# A cached percentage outlives the probe that measured it, but only until its
|
||||
# window rolls over: once a window has reset, the figure describes a period
|
||||
# that is over, and a stale 78% would misreport an allowance that is now
|
||||
# untouched. A window with no reset time, or one that will not parse, is kept
|
||||
# — an unreadable timestamp is no reason to throw away a real number.
|
||||
def limit_window_open(entry: dict[str, Any], now: dt.datetime) -> bool:
|
||||
raw = str(entry.get("resetsAt") or "")
|
||||
if raw == "":
|
||||
return True
|
||||
try:
|
||||
resets_at = dt.datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
||||
except Exception:
|
||||
return True
|
||||
if resets_at.tzinfo is None:
|
||||
resets_at = resets_at.replace(tzinfo=dt.timezone.utc)
|
||||
return resets_at > now
|
||||
|
||||
|
||||
def usable_cached_limits(cached: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
entries = cached.get("limits")
|
||||
if not isinstance(entries, list):
|
||||
return []
|
||||
now = dt.datetime.now(dt.timezone.utc)
|
||||
return [entry for entry in entries if isinstance(entry, dict) and limit_window_open(entry, now)]
|
||||
|
||||
|
||||
def collect_limits(access_token: str, expires_at_ms: int, force: bool) -> dict[str, Any]:
|
||||
result = {"limits": [], "usageStatusText": "", "authHelpText": AUTH_HELP}
|
||||
|
||||
if access_token == "":
|
||||
result["usageStatusText"] = "Waiting for auth"
|
||||
return result
|
||||
if expires_at_ms > 0 and expires_at_ms <= time.time() * 1000:
|
||||
return result
|
||||
|
||||
# A panel that is opened and shut repeatedly must not turn into a request
|
||||
# per flick, so recent probe results are reused for a short window — and
|
||||
# kept as the answer of record when a later probe fails.
|
||||
probe_cache = cache_root() / "claude-limits.json"
|
||||
cached = read_fresh_json(probe_cache, float("inf")) or {}
|
||||
fallback = usable_cached_limits(cached)
|
||||
|
||||
# Probing needs a live token and only the Claude Code CLI can mint one: it
|
||||
# refreshes the credential file when it runs, so a machine left alone long
|
||||
# enough finds the saved token lapsed. Say so — an empty limits list with
|
||||
# nothing else set hides the whole section and explains nothing — and keep
|
||||
# showing the last numbers whose window has not since reset.
|
||||
if access_token == "":
|
||||
result["limits"] = fallback
|
||||
result["usageStatusText"] = "Waiting for auth"
|
||||
return result
|
||||
if expires_at_ms > 0 and expires_at_ms <= time.time() * 1000:
|
||||
result["limits"] = fallback
|
||||
result["usageStatusText"] = "Sign-in expired"
|
||||
result["authHelpText"] = (
|
||||
"Claude Code's saved sign-in expired"
|
||||
+ (" — showing the last known limits." if fallback else ".")
|
||||
+ " Start Claude Code, or run `claude auth login`, to refresh it."
|
||||
)
|
||||
return result
|
||||
|
||||
# --force is a person asking for fresh numbers, so it skips the reuse window
|
||||
# entirely; the interval is there to absorb repeated panel opens, not to
|
||||
# overrule someone who pressed refresh.
|
||||
fetched_at = number(cached.get("fetchedAtMs")) / 1000
|
||||
min_interval = 0 if force else PROBE_MIN_INTERVAL_SECONDS
|
||||
if cached.get("limits") and time.time() - fetched_at < max(min_interval, PROBE_MIN_INTERVAL_SECONDS):
|
||||
result["limits"] = cached["limits"]
|
||||
if fallback and not force and time.time() - fetched_at < PROBE_MIN_INTERVAL_SECONDS:
|
||||
result["limits"] = fallback
|
||||
return result
|
||||
|
||||
probe = probe_limits(access_token)
|
||||
@@ -795,8 +838,8 @@ def collect_limits(access_token: str, expires_at_ms: int, force: bool) -> dict[s
|
||||
# route. Ask the shell to try again sooner than its regular interval.
|
||||
if probe.get("transport"):
|
||||
result["retryAdvised"] = True
|
||||
if cached.get("limits"):
|
||||
result["limits"] = cached["limits"]
|
||||
if fallback:
|
||||
result["limits"] = fallback
|
||||
else:
|
||||
result["usageStatusText"] = "Claude limits unavailable"
|
||||
result["authHelpText"] = probe["helpText"]
|
||||
|
||||
+196
-24
@@ -11,12 +11,16 @@ panel only ever reads the JSON this prints.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import select
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
@@ -25,6 +29,14 @@ AGENT_ID = "codex"
|
||||
AGENT_NAME = "Codex"
|
||||
AUTH_HELP = "Run `codex login` to authenticate."
|
||||
|
||||
# A scan this recent is only reused to dedup concurrent collector runs (the
|
||||
# update command backgrounds one per agent while the panel refreshes on its
|
||||
# own); every periodic widget refresh lands a real rescan, however low
|
||||
# refreshIntervalSec is set. --limits-only promises only fresh limits, so it
|
||||
# may reuse a scan for up to 15 minutes.
|
||||
SCAN_REUSE_SECONDS = 20
|
||||
LIMITS_ONLY_REUSE_SECONDS = 900
|
||||
|
||||
|
||||
def local_day(value):
|
||||
if value is None:
|
||||
@@ -197,16 +209,47 @@ def scan_opencode_sessions():
|
||||
# A subscription burned entirely through opencode leaves no native session
|
||||
# files, but opencode records per-message provider, model, and token usage
|
||||
# in its own database. Read-only: opencode may be writing right now.
|
||||
# Returns whether the scan ran to completion: a scan cut short by a
|
||||
# database error still contributes what it read, but must not be cached
|
||||
# as if it were the whole story.
|
||||
db = Path(os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share")) / "opencode" / "opencode.db"
|
||||
if not db.is_file():
|
||||
return
|
||||
return True
|
||||
try:
|
||||
conn = sqlite3.connect(db.resolve().as_uri() + "?mode=ro", uri=True, timeout=2)
|
||||
except sqlite3.Error:
|
||||
return
|
||||
return False
|
||||
try:
|
||||
conn.execute("PRAGMA query_only = ON")
|
||||
for session_id, raw in conn.execute("SELECT session_id, data FROM message"):
|
||||
# OpenCode DBs grow huge (every historical message, JSON included), and
|
||||
# Python-side json.loads of every row wasted ~600 MB of RSS on machines
|
||||
# whose subscription never ran on OpenAI. The json_extract conditions are
|
||||
# the authority for the rows that reach them: role == "assistant" and
|
||||
# providerID == "openai", the same exact-match values the Python filter
|
||||
# below checks. The guards in front are pure acceleration, not a perfect
|
||||
# proxy for the Python filter:
|
||||
# - The LIKE gates skip rows whose JSON cannot contain the two
|
||||
# key/value pairs, avoiding the JSON parse of tens-of-MB blobs. They
|
||||
# can differ from json.loads on duplicate keys (Python keeps the
|
||||
# last, SQLite json_extract keeps the first) and ASCII-escaped
|
||||
# values (\"ass\\u0069stant\" decodes for Python but not for LIKE),
|
||||
# so a row the authority would accept can be gated out. Both cases
|
||||
# are vanishingly rare in real opencode data.
|
||||
# - json_valid guards the parse itself: json_extract() RAISES on
|
||||
# malformed JSON instead of returning NULL, and one such row would
|
||||
# otherwise abort the whole scan. SQLite does not promise that AND
|
||||
# terms evaluate left to right, so the guard is a CASE around each
|
||||
# json_extract rather than a separate AND term. Rows that are not
|
||||
# well-formed JSON are skipped here; the per-row try/except below
|
||||
# stays as the final safety net for rows that pass the SQL filter
|
||||
# but fail json.loads.
|
||||
for session_id, raw in conn.execute(
|
||||
"SELECT session_id, data FROM message"
|
||||
" WHERE data LIKE '%\"role\"%:%\"assistant\"%'"
|
||||
" AND data LIKE '%\"providerID\"%:%\"openai\"%'"
|
||||
" AND CASE WHEN json_valid(data) THEN json_extract(data, '$.role') END = 'assistant'"
|
||||
" AND CASE WHEN json_valid(data) THEN json_extract(data, '$.providerID') END = 'openai'"
|
||||
):
|
||||
# One malformed row must not abort the scan, so every shape assumption
|
||||
# lives inside the try.
|
||||
try:
|
||||
@@ -232,9 +275,12 @@ def scan_opencode_sessions():
|
||||
continue
|
||||
add_usage(day, "opencode:" + str(session_id), model, input_tokens, output_tokens, cache_read, cache_write)
|
||||
except sqlite3.Error:
|
||||
pass
|
||||
# Transient lock, schema migration, corruption: the numbers stop here,
|
||||
# incomplete.
|
||||
return False
|
||||
finally:
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
|
||||
def scan_native_codex_sessions():
|
||||
@@ -291,6 +337,143 @@ def scan_native_codex_sessions():
|
||||
continue
|
||||
|
||||
|
||||
def cache_root():
|
||||
root = Path(os.environ.get("XDG_CACHE_HOME") or (Path.home() / ".cache")) / "omarchy" / "agent-usage"
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
return root
|
||||
|
||||
|
||||
def scan_cache_paths():
|
||||
codex_home = Path(os.environ.get("CODEX_HOME") or (Path.home() / ".codex"))
|
||||
db = Path(os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share")) / "opencode" / "opencode.db"
|
||||
# The digest covers every data path the scan reads: the codex session
|
||||
# roots, the opencode DB, and (via Path.home()) the pi/omp session roots.
|
||||
digest = hashlib.sha1((str(Path.home()) + "\n" + str(codex_home) + "\n" + str(db)).encode("utf-8")).hexdigest()[:16]
|
||||
root = cache_root()
|
||||
return root / f"codex-scan-{digest}.json", root / f"codex-scan-{digest}.lock"
|
||||
|
||||
|
||||
def read_fresh_json(path, max_age_seconds):
|
||||
if max_age_seconds <= 0 or not path.exists():
|
||||
return None
|
||||
try:
|
||||
# A negative age means the mtime is in the future: the clock moved
|
||||
# backwards since the write, so the cache's freshness cannot be trusted.
|
||||
age = time.time() - path.stat().st_mtime
|
||||
if 0 <= age <= max_age_seconds:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def write_json(path, payload):
|
||||
# A temp name unique to this writer, not derived from the target: several
|
||||
# collectors can run at once (the update command backgrounds one per agent,
|
||||
# the panel refreshes on its own), and a shared temp path means the second
|
||||
# replace finds the first one's file already moved away.
|
||||
handle_fd, tmp_name = tempfile.mkstemp(dir=path.parent, prefix=path.name + ".", suffix=".tmp")
|
||||
tmp = Path(tmp_name)
|
||||
try:
|
||||
with os.fdopen(handle_fd, "w", encoding="utf-8") as handle:
|
||||
handle.write(json.dumps(payload, separators=(",", ":")) + "\n")
|
||||
# mkstemp opens at 0600; nothing in the cache is sensitive, so open it
|
||||
# up to the usual 0644.
|
||||
tmp.chmod(0o644)
|
||||
tmp.replace(path)
|
||||
except BaseException:
|
||||
tmp.unlink(missing_ok=True)
|
||||
raise
|
||||
|
||||
|
||||
# The cache payload is a versioned envelope around the local-stats dict, so a
|
||||
# corrupted or foreign-shaped file is a cache miss (rescan + rewrite) instead
|
||||
# of a crash or a garbage record.
|
||||
def read_cached_stats(cache_file, max_age_seconds):
|
||||
cached = read_fresh_json(cache_file, max_age_seconds)
|
||||
if not isinstance(cached, dict) or cached.get("schemaVersion") != 1:
|
||||
return None
|
||||
# today* fields only mean "today" on the day they were scanned. A cache
|
||||
# from another local date (midnight passed, or the clock moved) is a miss,
|
||||
# not merely old, whatever its mtime says.
|
||||
if cached.get("scanDate") != today:
|
||||
return None
|
||||
stats = cached.get("stats")
|
||||
if not isinstance(stats, dict):
|
||||
return None
|
||||
if not all(key in stats for key in ("todayPrompts", "todayTotalTokens", "recentDays", "activeDates", "modelUsage")):
|
||||
return None
|
||||
return stats
|
||||
|
||||
|
||||
def write_cached_stats(cache_file, stats):
|
||||
try:
|
||||
write_json(cache_file, {"schemaVersion": 1, "scanDate": today, "stats": stats})
|
||||
except Exception as exc:
|
||||
print(f"omarchy-agent-usage-codex: could not write usage cache ({exc})", file=sys.stderr)
|
||||
|
||||
|
||||
def local_stats():
|
||||
"""Snapshot the aggregated local usage into the record's stats dict."""
|
||||
return {
|
||||
"todayPrompts": today_prompts,
|
||||
"todaySessions": len(today_sessions),
|
||||
"todayTotalTokens": today_total_tokens,
|
||||
"todayTokensByModel": today_tokens_by_model,
|
||||
"recentDays": [recent[day] for day in recent_dates],
|
||||
"totalPrompts": total_prompts,
|
||||
"totalSessions": len(total_sessions),
|
||||
# Days with any recorded usage, for the all-time "N days" summary. The
|
||||
# dates travel too: merging snapshots from several machines needs their
|
||||
# union, which a count alone cannot give.
|
||||
"activeDays": len(active_days),
|
||||
"activeDates": sorted(active_days),
|
||||
"modelUsage": model_usage,
|
||||
}
|
||||
|
||||
|
||||
def run_local_scans():
|
||||
scan_pi_sessions()
|
||||
scan_native_codex_sessions()
|
||||
complete = scan_opencode_sessions()
|
||||
return local_stats(), complete
|
||||
|
||||
|
||||
def cached_local_stats(max_age):
|
||||
"""Local stats, with the cache as a pure optimization.
|
||||
|
||||
The cache must never take the collector down: any cache-layer failure
|
||||
(unwritable cache root, lock errors, disk full) degrades to a direct scan
|
||||
and a warning on stderr. The JSON record is the contract; the cache is not.
|
||||
"""
|
||||
try:
|
||||
return _cached_local_stats(max_age)
|
||||
except Exception as exc:
|
||||
print(f"omarchy-agent-usage-codex: cache unavailable ({exc}); scanning directly", file=sys.stderr)
|
||||
stats, _ = run_local_scans()
|
||||
return stats
|
||||
|
||||
|
||||
def _cached_local_stats(max_age):
|
||||
cache_file, lock_file = scan_cache_paths()
|
||||
|
||||
cached = read_cached_stats(cache_file, max_age)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
with lock_file.open("w") as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
cached = read_cached_stats(cache_file, max_age)
|
||||
if cached is not None:
|
||||
return cached
|
||||
stats, complete = run_local_scans()
|
||||
# An interrupted scan still serves this run, but caching it would
|
||||
# suppress the missing usage for every reader until the cache expires.
|
||||
if complete:
|
||||
write_cached_stats(cache_file, stats)
|
||||
return stats
|
||||
|
||||
|
||||
def rpc_request(proc, request_id, method, params=None, timeout=8):
|
||||
payload = {"id": request_id, "method": method, "params": params or {}}
|
||||
proc.stdin.write(json.dumps(payload) + "\n")
|
||||
@@ -390,16 +573,17 @@ def fetch_codex_rpc():
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
# Local stats and limits come from the same cheap scan, so there is no
|
||||
# cache to force past and no faster limits-only path. The flags exist so
|
||||
# every collector accepts the same invocation.
|
||||
# --force rescans everything and rewrites the cache. --limits-only is kept
|
||||
# for CLI compatibility with the panel's refreshLimits() call: only the
|
||||
# limits probe must be fresh, so it may reuse a scan for far longer than a
|
||||
# normal run, whose short window exists purely to dedup concurrent
|
||||
# collector runs.
|
||||
parser.add_argument("--force", action="store_true")
|
||||
parser.add_argument("--limits-only", action="store_true")
|
||||
parser.parse_args()
|
||||
args = parser.parse_args()
|
||||
|
||||
scan_pi_sessions()
|
||||
scan_native_codex_sessions()
|
||||
scan_opencode_sessions()
|
||||
max_age = 0 if args.force else (LIMITS_ONLY_REUSE_SECONDS if args.limits_only else SCAN_REUSE_SECONDS)
|
||||
stats = cached_local_stats(max_age)
|
||||
rpc = fetch_codex_rpc()
|
||||
|
||||
record = {
|
||||
@@ -409,20 +593,8 @@ def main():
|
||||
"updatedAt": datetime.now(timezone.utc).isoformat(),
|
||||
"ready": True,
|
||||
"hasLocalStats": True,
|
||||
"todayPrompts": today_prompts,
|
||||
"todaySessions": len(today_sessions),
|
||||
"todayTotalTokens": today_total_tokens,
|
||||
"todayTokensByModel": today_tokens_by_model,
|
||||
"recentDays": [recent[day] for day in recent_dates],
|
||||
"totalPrompts": total_prompts,
|
||||
"totalSessions": len(total_sessions),
|
||||
# Days with any recorded usage, for the all-time "N days" summary. The
|
||||
# dates travel too: merging snapshots from several machines needs their
|
||||
# union, which a count alone cannot give.
|
||||
"activeDays": len(active_days),
|
||||
"activeDates": sorted(active_days),
|
||||
"modelUsage": model_usage,
|
||||
}
|
||||
record.update(stats)
|
||||
record.update(rpc)
|
||||
print(json.dumps(record, separators=(",", ":")))
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ start_webcam_overlay() {
|
||||
|
||||
# Auto-detect first available webcam if none specified
|
||||
if [[ -z $WEBCAM_DEVICE ]]; then
|
||||
WEBCAM_DEVICE=$(v4l2-ctl --list-devices 2>/dev/null | grep -m1 "^[[:space:]]*/dev/video" | tr -d '\t')
|
||||
WEBCAM_DEVICE=$(omarchy-capture-webcam-list | sed -n '1s/[[:space:]].*//p')
|
||||
if [[ -z $WEBCAM_DEVICE ]]; then
|
||||
omarchy-notification-send -u critical -t 3000 "No webcam devices found"
|
||||
return 1
|
||||
|
||||
@@ -3,21 +3,7 @@
|
||||
# omarchy:summary=Pick a webcam and start a screen recording with it
|
||||
# omarchy:examples=omarchy capture screenrecording-with-webcam
|
||||
|
||||
set -o pipefail
|
||||
|
||||
webcam_list() {
|
||||
v4l2-ctl --list-devices 2>/dev/null | while IFS= read -r line; do
|
||||
if [[ $line != $'\t'* && -n $line ]]; then
|
||||
local name="$line"
|
||||
local device
|
||||
IFS= read -r device || break
|
||||
device=$(tr -d '\t' <<<"$device" | head -1)
|
||||
[[ -n $device ]] && echo "$device $name"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
mapfile -t devices < <(webcam_list)
|
||||
mapfile -t devices < <(omarchy-capture-webcam-list)
|
||||
if (( ${#devices[@]} == 0 )); then
|
||||
omarchy-notification-send "No webcam devices found" -u critical -t 3000
|
||||
exit 1
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=List webcam devices that support video capture
|
||||
# omarchy:hidden=true
|
||||
|
||||
capture_capable() {
|
||||
local device="$1"
|
||||
|
||||
v4l2-ctl --device "$device" --info 2>/dev/null | awk '
|
||||
/^[[:space:]]*Device Caps[[:space:]]*:/ { inspect = 1; next }
|
||||
inspect && /^[[:space:]]*Video Capture/ { found = 1 }
|
||||
END { exit !found }
|
||||
'
|
||||
}
|
||||
|
||||
name=""
|
||||
emitted=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ -n $line && $line != [[:space:]]* ]]; then
|
||||
name="$line"
|
||||
emitted=0
|
||||
elif (( ! emitted )); then
|
||||
device="${line#"${line%%[![:space:]]*}"}"
|
||||
|
||||
if [[ $device == /dev/video* ]] && capture_capable "$device"; then
|
||||
emitted=1
|
||||
printf '%s %s\n' "$device" "$name"
|
||||
fi
|
||||
fi
|
||||
done < <(v4l2-ctl --list-devices 2>/dev/null)
|
||||
|
||||
exit 0
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Watch for process crashes and offer an AI diagnosis
|
||||
# omarchy:hidden=true
|
||||
|
||||
# systemd-coredump journals every core dump under a known MESSAGE_ID with
|
||||
# structured COREDUMP_* fields, which carry more than the core filenames do.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# See systemd.journal-fields(7).
|
||||
readonly COREDUMP_MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1
|
||||
|
||||
# nf-md-robot_dead, escaped so this file reads without a Nerd Font.
|
||||
readonly CRASH_GLYPH=$'\U000f16a1'
|
||||
|
||||
# Crash loops dump core repeatedly, so announce each program at most once a
|
||||
# window.
|
||||
readonly dedupe_seconds=${OMARCHY_CRASH_DEDUPE_SECONDS:-60}
|
||||
|
||||
# Extended regex of process names never worth announcing.
|
||||
readonly ignore_pattern=${OMARCHY_CRASH_IGNORE:-}
|
||||
|
||||
declare -A last_notified
|
||||
|
||||
announce() {
|
||||
local comm=$1 pid=$2 exe=$3 signal=$4 exec_command
|
||||
|
||||
exec_command=$(printf 'omarchy-agent-crash %q %q %q %q' "$pid" "$comm" "$exe" "$signal")
|
||||
|
||||
# The shell owns org.freedesktop.Notifications, so a shell crash takes the
|
||||
# notification server down with it and a toast sent into that gap is lost.
|
||||
# Wait for the restarted shell to claim the name again: the crash least
|
||||
# likely to be delivered is the one most worth reporting.
|
||||
omarchy-notification-wait || return 1
|
||||
|
||||
# --exec rather than a libnotify action: the shell runs clicks from its own
|
||||
# omarchy-exec hint and never emits ActionInvoked. Keeps the default
|
||||
# "omarchy-action" app name too, the only one shouldBypassDnd() lets through.
|
||||
omarchy-notification-send \
|
||||
--urgency critical \
|
||||
--glyph "$CRASH_GLYPH" \
|
||||
--exec "$exec_command" \
|
||||
"Process crashed: $comm" \
|
||||
"Click to diagnose with AI"
|
||||
}
|
||||
|
||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||
journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
while IFS= read -r entry; do
|
||||
IFS=$'\t' read -r uid comm pid exe signal < <(
|
||||
jq -r '[(._UID // "-"),
|
||||
(.COREDUMP_COMM // "-"),
|
||||
(.COREDUMP_PID // "-"),
|
||||
(.COREDUMP_EXE // "-"),
|
||||
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
|
||||
)
|
||||
|
||||
[[ $pid =~ ^[0-9]+$ ]] || continue
|
||||
|
||||
# The toast only offers a diagnosis, so it has nothing to offer until an
|
||||
# agent is chosen. Checked per crash, not at startup, so picking one takes
|
||||
# effect without restarting this service.
|
||||
[[ -n $(omarchy-default-agent) ]] || continue
|
||||
|
||||
# Only this user's crashes; a daemon dumping core is a sysadmin's problem.
|
||||
[[ $uid =~ ^[0-9]+$ ]] || continue
|
||||
((uid == UID)) || continue
|
||||
|
||||
# comm is truncated to 15 characters, so prefer the executable's basename.
|
||||
name=$comm
|
||||
[[ $exe == /* ]] && name=${exe##*/}
|
||||
|
||||
[[ -n $ignore_pattern && $name =~ $ignore_pattern ]] && continue
|
||||
|
||||
# Never announce our own machinery, or it notifies about itself.
|
||||
[[ $name == omarchy-crash-* || $name == omarchy-agent-* ]] && continue
|
||||
|
||||
now=$EPOCHSECONDS
|
||||
(((now - ${last_notified[$name]:-0}) < dedupe_seconds)) && continue
|
||||
|
||||
# Only a delivered toast starts the dedupe window. A failed send that
|
||||
# counted would suppress the rest of a crash loop for a minute, and
|
||||
# `journalctl -n 0` never replays what was missed.
|
||||
announce "$name" "$pid" "$exe" "$signal" && last_notified[$name]=$now
|
||||
done
|
||||
@@ -17,7 +17,9 @@ if (($# == 0)); then
|
||||
read -r agent <"$agent_file"
|
||||
fi
|
||||
|
||||
[[ -n $agent ]] && echo "$agent" || echo "opencode"
|
||||
# Silent when unset rather than defaulting: Omarchy picks no agent for you, so
|
||||
# the menu leaves every entry unchecked until one is chosen.
|
||||
[[ -n ${agent:-} ]] && echo "$agent"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -57,7 +59,7 @@ printf '%s\n' "$agent" >"$agent_file"
|
||||
|
||||
if [[ $installing == "true" ]]; then
|
||||
printf '\033[2J\033[3J\033[H'
|
||||
exec omarchy-launch-agent --inline
|
||||
exec omarchy-agent --inline
|
||||
else
|
||||
exec omarchy-launch-agent
|
||||
exec omarchy-agent
|
||||
fi
|
||||
Executable
+652
@@ -0,0 +1,652 @@
|
||||
#!/usr/bin/python3
|
||||
# omarchy:summary=Add branded glyphs to the Omarchy icon font
|
||||
# omarchy:args=[list|add] <name> <svg-file-or-url> [--codepoint U+E9xx] [--font PATH]
|
||||
# omarchy:examples=omarchy dev font list | omarchy dev font add ollama https://simpleicons.org/icons/ollama.svg
|
||||
"""Append monochrome SVG marks to default/fonts/omarchy/omarchy.ttf.
|
||||
|
||||
The font is a private-use icon font: the menu renders a mark by setting
|
||||
"iconFont":"omarchy" on an entry and using the glyph's codepoint as its
|
||||
icon. Adding a mark means appending a glyph here, then pointing a menu
|
||||
entry at the codepoint this prints.
|
||||
|
||||
Marks must be monochrome single-path SVGs so the menu can draw them in the
|
||||
active theme's foreground and selection colors. Brand icon sets like
|
||||
simpleicons.org publish exactly that shape; app favicons often do not.
|
||||
|
||||
The glyph is scaled into the same 64..960 box the existing marks use, so a
|
||||
new mark lands at the same optical size as the ones already in the font.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
UPEM = 1024
|
||||
ART_BOX = (64, 64, 960, 960) # the box every existing mark is drawn in
|
||||
TOL = 0.6 # cubic -> quadratic error tolerance, in font units
|
||||
PUA_FIRST = 0xE900
|
||||
|
||||
|
||||
# --- SVG path parsing ------------------------------------------------------
|
||||
|
||||
NUM = re.compile(r'[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?')
|
||||
CMD = re.compile(r'[MmZzLlHhVvCcSsQqTtAa]')
|
||||
|
||||
|
||||
def tokenize(d):
|
||||
out, i = [], 0
|
||||
while i < len(d):
|
||||
c = d[i]
|
||||
if CMD.match(c):
|
||||
out.append(c)
|
||||
i += 1
|
||||
elif c in ' ,\t\r\n':
|
||||
i += 1
|
||||
else:
|
||||
m = NUM.match(d, i)
|
||||
if not m:
|
||||
raise ValueError('bad path data at %d: %r' % (i, d[i:i + 20]))
|
||||
out.append(float(m.group()))
|
||||
i = m.end()
|
||||
return out
|
||||
|
||||
|
||||
def arc_to_cubics(p0, rx, ry, phi, large, sweep, p1):
|
||||
"""Endpoint-parameterized SVG arc -> cubic segments."""
|
||||
if p0 == p1:
|
||||
return []
|
||||
rx, ry = abs(rx), abs(ry)
|
||||
if rx == 0 or ry == 0:
|
||||
return [('L', p1)]
|
||||
phi = math.radians(phi % 360)
|
||||
cosp, sinp = math.cos(phi), math.sin(phi)
|
||||
dx2, dy2 = (p0[0] - p1[0]) / 2.0, (p0[1] - p1[1]) / 2.0
|
||||
x1 = cosp * dx2 + sinp * dy2
|
||||
y1 = -sinp * dx2 + cosp * dy2
|
||||
lam = x1 * x1 / (rx * rx) + y1 * y1 / (ry * ry)
|
||||
if lam > 1:
|
||||
s = math.sqrt(lam)
|
||||
rx, ry = rx * s, ry * s
|
||||
num = rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1
|
||||
den = rx * rx * y1 * y1 + ry * ry * x1 * x1
|
||||
co = math.sqrt(max(0.0, num / den)) if den else 0.0
|
||||
if large == sweep:
|
||||
co = -co
|
||||
cx1 = co * rx * y1 / ry
|
||||
cy1 = -co * ry * x1 / rx
|
||||
cx = cosp * cx1 - sinp * cy1 + (p0[0] + p1[0]) / 2.0
|
||||
cy = sinp * cx1 + cosp * cy1 + (p0[1] + p1[1]) / 2.0
|
||||
|
||||
def angle(ux, uy, vx, vy):
|
||||
dot = ux * vx + uy * vy
|
||||
n = math.hypot(ux, uy) * math.hypot(vx, vy)
|
||||
a = math.acos(max(-1.0, min(1.0, dot / n))) if n else 0.0
|
||||
return -a if ux * vy - uy * vx < 0 else a
|
||||
|
||||
theta = angle(1, 0, (x1 - cx1) / rx, (y1 - cy1) / ry)
|
||||
delta = angle((x1 - cx1) / rx, (y1 - cy1) / ry, (-x1 - cx1) / rx, (-y1 - cy1) / ry)
|
||||
if not sweep and delta > 0:
|
||||
delta -= 2 * math.pi
|
||||
elif sweep and delta < 0:
|
||||
delta += 2 * math.pi
|
||||
|
||||
segs = []
|
||||
n = max(1, int(math.ceil(abs(delta) / (math.pi / 2) - 1e-9)))
|
||||
step = delta / n
|
||||
k = 4.0 / 3.0 * math.tan(step / 4.0)
|
||||
|
||||
def at(t):
|
||||
x, y = rx * math.cos(t), ry * math.sin(t)
|
||||
return (cosp * x - sinp * y + cx, sinp * x + cosp * y + cy)
|
||||
|
||||
def deriv(t):
|
||||
x, y = -rx * math.sin(t), ry * math.cos(t)
|
||||
return (cosp * x - sinp * y, sinp * x + cosp * y)
|
||||
|
||||
for i in range(n):
|
||||
t0 = theta + i * step
|
||||
t1 = t0 + step
|
||||
a, b = at(t0), at(t1)
|
||||
da, db = deriv(t0), deriv(t1)
|
||||
segs.append(('C',
|
||||
(a[0] + k * da[0], a[1] + k * da[1]),
|
||||
(b[0] - k * db[0], b[1] - k * db[1]),
|
||||
b))
|
||||
return segs
|
||||
|
||||
|
||||
def parse_path(d):
|
||||
"""Return one list of absolute segments per subpath."""
|
||||
t = tokenize(d)
|
||||
subs, cur = [], None
|
||||
pos = start = (0.0, 0.0)
|
||||
prev_c = prev_q = None
|
||||
i, cmd = 0, None
|
||||
while i < len(t):
|
||||
if isinstance(t[i], str):
|
||||
cmd = t[i]
|
||||
i += 1
|
||||
rel = cmd.islower()
|
||||
c = cmd.upper()
|
||||
|
||||
def take(n):
|
||||
nonlocal i
|
||||
v = t[i:i + n]
|
||||
i += n
|
||||
return v
|
||||
|
||||
def abspt(x, y):
|
||||
return (pos[0] + x, pos[1] + y) if rel else (x, y)
|
||||
|
||||
if c == 'M':
|
||||
x, y = take(2)
|
||||
pos = start = abspt(x, y)
|
||||
if cur:
|
||||
subs.append(cur)
|
||||
cur = [('M', pos)]
|
||||
cmd = 'l' if rel else 'L' # implicit lineto for extra pairs
|
||||
prev_c = prev_q = None
|
||||
elif c == 'Z':
|
||||
if cur:
|
||||
subs.append(cur)
|
||||
cur = None
|
||||
pos = start
|
||||
prev_c = prev_q = None
|
||||
elif c in 'LHV':
|
||||
if c == 'L':
|
||||
x, y = take(2)
|
||||
p = abspt(x, y)
|
||||
elif c == 'H':
|
||||
x = take(1)[0]
|
||||
p = (pos[0] + x, pos[1]) if rel else (x, pos[1])
|
||||
else:
|
||||
y = take(1)[0]
|
||||
p = (pos[0], pos[1] + y) if rel else (pos[0], y)
|
||||
cur.append(('L', p))
|
||||
pos = p
|
||||
prev_c = prev_q = None
|
||||
elif c in 'CS':
|
||||
if c == 'C':
|
||||
x1, y1, x2, y2, x, y = take(6)
|
||||
c1, c2, p = abspt(x1, y1), abspt(x2, y2), abspt(x, y)
|
||||
else:
|
||||
x2, y2, x, y = take(4)
|
||||
c2, p = abspt(x2, y2), abspt(x, y)
|
||||
c1 = (2 * pos[0] - prev_c[0], 2 * pos[1] - prev_c[1]) if prev_c else pos
|
||||
cur.append(('C', c1, c2, p))
|
||||
prev_c, prev_q = c2, None
|
||||
pos = p
|
||||
elif c in 'QT':
|
||||
if c == 'Q':
|
||||
x1, y1, x, y = take(4)
|
||||
q, p = abspt(x1, y1), abspt(x, y)
|
||||
else:
|
||||
x, y = take(2)
|
||||
p = abspt(x, y)
|
||||
q = (2 * pos[0] - prev_q[0], 2 * pos[1] - prev_q[1]) if prev_q else pos
|
||||
cur.append(('C',
|
||||
(pos[0] + 2.0 / 3 * (q[0] - pos[0]), pos[1] + 2.0 / 3 * (q[1] - pos[1])),
|
||||
(p[0] + 2.0 / 3 * (q[0] - p[0]), p[1] + 2.0 / 3 * (q[1] - p[1])),
|
||||
p))
|
||||
prev_q, prev_c = q, None
|
||||
pos = p
|
||||
elif c == 'A':
|
||||
rx, ry, rot, large, sweep, x, y = take(7)
|
||||
p = abspt(x, y)
|
||||
cur.extend(arc_to_cubics(pos, rx, ry, rot, int(large), int(sweep), p))
|
||||
pos = p
|
||||
prev_c = prev_q = None
|
||||
else:
|
||||
raise ValueError('unsupported path command %r' % cmd)
|
||||
if cur:
|
||||
subs.append(cur)
|
||||
return subs
|
||||
|
||||
|
||||
# --- outline conversion ----------------------------------------------------
|
||||
|
||||
def cubic_to_quads(p0, p1, p2, p3, tol, depth=0):
|
||||
"""Approximate one cubic with quadratics: [(control, end), ...]."""
|
||||
ex = p0[0] - 3 * p1[0] + 3 * p2[0] - p3[0]
|
||||
ey = p0[1] - 3 * p1[1] + 3 * p2[1] - p3[1]
|
||||
if math.sqrt(3) / 36 * math.hypot(ex, ey) <= tol or depth >= 8:
|
||||
return [(((3 * p1[0] - p0[0] + 3 * p2[0] - p3[0]) / 4.0,
|
||||
(3 * p1[1] - p0[1] + 3 * p2[1] - p3[1]) / 4.0), p3)]
|
||||
|
||||
def mid(a, b):
|
||||
return ((a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0)
|
||||
|
||||
p01, p12, p23 = mid(p0, p1), mid(p1, p2), mid(p2, p3)
|
||||
p012, p123 = mid(p01, p12), mid(p12, p23)
|
||||
m = mid(p012, p123)
|
||||
return (cubic_to_quads(p0, p01, p012, m, tol, depth + 1) +
|
||||
cubic_to_quads(m, p123, p23, p3, tol, depth + 1))
|
||||
|
||||
|
||||
def contours_from_svg(path_d, view):
|
||||
"""SVG path -> TrueType contours [[(x, y, on_curve), ...], ...]."""
|
||||
vbx, vby, vbw, vbh = view
|
||||
ax0, ay0, ax1, ay1 = ART_BOX
|
||||
scale = min((ax1 - ax0) / vbw, (ay1 - ay0) / vbh)
|
||||
ox = ax0 + ((ax1 - ax0) - vbw * scale) / 2.0
|
||||
oy = ay0 + ((ay1 - ay0) - vbh * scale) / 2.0
|
||||
|
||||
def tf(p):
|
||||
# SVG y grows down, font y grows up.
|
||||
return (ox + (p[0] - vbx) * scale, oy + (vbh - (p[1] - vby)) * scale)
|
||||
|
||||
contours = []
|
||||
for sub in parse_path(path_d):
|
||||
pts, cursor = [], None
|
||||
for seg in sub:
|
||||
if seg[0] in ('M', 'L'):
|
||||
cursor = tf(seg[1])
|
||||
pts.append((cursor[0], cursor[1], True))
|
||||
elif seg[0] == 'C':
|
||||
c1, c2, p = tf(seg[1]), tf(seg[2]), tf(seg[3])
|
||||
for q, end in cubic_to_quads(cursor, c1, c2, p, TOL):
|
||||
pts.append((q[0], q[1], False))
|
||||
pts.append((end[0], end[1], True))
|
||||
cursor = p
|
||||
if len(pts) > 2:
|
||||
contours.append(clean(pts))
|
||||
return orient(contours)
|
||||
|
||||
|
||||
def clean(pts):
|
||||
"""Round to integers, drop duplicates and the redundant closing point."""
|
||||
out = []
|
||||
for x, y, on in pts:
|
||||
p = (int(round(x)), int(round(y)), on)
|
||||
if not out or out[-1] != p:
|
||||
out.append(p)
|
||||
while len(out) > 1 and out[-1][:2] == out[0][:2] and out[-1][2] and out[0][2]:
|
||||
out.pop()
|
||||
return out
|
||||
|
||||
|
||||
def area(pts):
|
||||
a = 0.0
|
||||
for i in range(len(pts)):
|
||||
x0, y0 = pts[i][0], pts[i][1]
|
||||
x1, y1 = pts[(i + 1) % len(pts)][0], pts[(i + 1) % len(pts)][1]
|
||||
a += x0 * y1 - x1 * y0
|
||||
return a / 2.0
|
||||
|
||||
|
||||
def orient(contours):
|
||||
"""TrueType draws outer contours clockwise (negative shoelace area).
|
||||
|
||||
Flipping the whole set preserves each hole's direction relative to its
|
||||
outer contour, which is what keeps counters (eyes, cutouts) unfilled.
|
||||
"""
|
||||
if contours and area(max(contours, key=lambda c: abs(area(c)))) > 0:
|
||||
return [list(reversed(c)) for c in contours]
|
||||
return contours
|
||||
|
||||
|
||||
# --- glyph and table encoding ----------------------------------------------
|
||||
|
||||
def encode_glyph(contours):
|
||||
if not contours:
|
||||
return b''
|
||||
xs = [p[0] for c in contours for p in c]
|
||||
ys = [p[1] for c in contours for p in c]
|
||||
out = [struct.pack('>hhhhh', len(contours), min(xs), min(ys), max(xs), max(ys))]
|
||||
ends, n = [], 0
|
||||
for c in contours:
|
||||
n += len(c)
|
||||
ends.append(n - 1)
|
||||
out.append(struct.pack('>%dH' % len(ends), *ends))
|
||||
out.append(struct.pack('>H', 0)) # no instructions
|
||||
|
||||
flags, xdel, ydel = [], [], []
|
||||
px = py = 0
|
||||
for c in contours:
|
||||
for x, y, on in c:
|
||||
dx, dy = x - px, y - py
|
||||
px, py = x, y
|
||||
f = 1 if on else 0
|
||||
if dx == 0:
|
||||
f |= 0x10
|
||||
elif -255 <= dx <= 255:
|
||||
f |= 0x02 | (0x10 if dx > 0 else 0)
|
||||
xdel.append(struct.pack('>B', abs(dx)))
|
||||
else:
|
||||
xdel.append(struct.pack('>h', dx))
|
||||
if dy == 0:
|
||||
f |= 0x20
|
||||
elif -255 <= dy <= 255:
|
||||
f |= 0x04 | (0x20 if dy > 0 else 0)
|
||||
ydel.append(struct.pack('>B', abs(dy)))
|
||||
else:
|
||||
ydel.append(struct.pack('>h', dy))
|
||||
flags.append(struct.pack('>B', f))
|
||||
data = b''.join(out + flags + xdel + ydel)
|
||||
return data + b'\0' * (-len(data) % 4)
|
||||
|
||||
|
||||
def read_tables(d):
|
||||
t = {}
|
||||
for i in range(struct.unpack('>H', d[4:6])[0]):
|
||||
off = 12 + i * 16
|
||||
tag = d[off:off + 4].decode('latin-1')
|
||||
s, l = struct.unpack('>II', d[off + 8:off + 16])
|
||||
t[tag] = (s, l)
|
||||
return t
|
||||
|
||||
|
||||
def checksum(data):
|
||||
data += b'\0' * (-len(data) % 4)
|
||||
return sum(struct.unpack('>%dI' % (len(data) // 4), data)) & 0xFFFFFFFF
|
||||
|
||||
|
||||
def read_loca(d, t, ng):
|
||||
ls = t['loca'][0]
|
||||
if struct.unpack('>h', d[t['head'][0] + 50:t['head'][0] + 52])[0] == 0:
|
||||
return [x * 2 for x in struct.unpack('>%dH' % (ng + 1), d[ls:ls + 2 * (ng + 1)])]
|
||||
return list(struct.unpack('>%dI' % (ng + 1), d[ls:ls + 4 * (ng + 1)]))
|
||||
|
||||
|
||||
def read_cmap(d, cs):
|
||||
"""Read the format 12 subtable; it carries every mapping the font has."""
|
||||
m = {}
|
||||
for i in range(struct.unpack('>H', d[cs + 2:cs + 4])[0]):
|
||||
pid, eid, off = struct.unpack('>HHI', d[cs + 4 + i * 8:cs + 12 + i * 8])
|
||||
sub = cs + off
|
||||
if struct.unpack('>H', d[sub:sub + 2])[0] != 12:
|
||||
continue
|
||||
for j in range(struct.unpack('>I', d[sub + 12:sub + 16])[0]):
|
||||
s, e, g = struct.unpack('>III', d[sub + 16 + j * 12:sub + 28 + j * 12])
|
||||
for c in range(s, e + 1):
|
||||
m[c] = g + (c - s)
|
||||
return m
|
||||
|
||||
|
||||
def read_names(d, ps, ng):
|
||||
n = struct.unpack('>H', d[ps + 32:ps + 34])[0]
|
||||
idx = list(struct.unpack('>%dH' % n, d[ps + 34:ps + 34 + n * 2]))
|
||||
p = ps + 34 + n * 2
|
||||
pool = []
|
||||
need = max([i - 258 + 1 for i in idx if i >= 258] or [0])
|
||||
while len(pool) < need:
|
||||
ln = d[p]
|
||||
pool.append(d[p + 1:p + 1 + ln].decode('latin-1'))
|
||||
p += 1 + ln
|
||||
out = [pool[i - 258] if i >= 258 else '#mac%d' % i for i in idx]
|
||||
return out + ['#mac0'] * (ng - len(out))
|
||||
|
||||
|
||||
def build_post(d, ps, names):
|
||||
idx, pool = [], []
|
||||
for nm in names:
|
||||
if nm.startswith('#mac'):
|
||||
idx.append(int(nm[4:]))
|
||||
else:
|
||||
idx.append(258 + len(pool))
|
||||
pool.append(nm)
|
||||
body = struct.pack('>H', len(idx)) + struct.pack('>%dH' % len(idx), *idx)
|
||||
for nm in pool:
|
||||
body += struct.pack('>B', len(nm)) + nm.encode('latin-1')
|
||||
return d[ps:ps + 32] + body
|
||||
|
||||
|
||||
def build_cmap(m):
|
||||
"""Emit format 4 and 12 subtables under the usual encoding records."""
|
||||
groups = []
|
||||
for c in sorted(m):
|
||||
if groups and c == groups[-1][1] + 1 and m[c] == groups[-1][2] + (c - groups[-1][0]):
|
||||
groups[-1][1] = c
|
||||
else:
|
||||
groups.append([c, c, m[c]])
|
||||
|
||||
segs = [(a, b, g) for a, b, g in groups if b <= 0xFFFF] + [(0xFFFF, 0xFFFF, 0)]
|
||||
count = len(segs)
|
||||
pow2 = 2 ** (count.bit_length() - 1) * 2
|
||||
f4 = struct.pack('>HHHHHHH', 4, 16 + count * 8, 0, count * 2,
|
||||
pow2, count.bit_length() - 1, count * 2 - pow2)
|
||||
f4 += struct.pack('>%dH' % count, *[s[1] for s in segs])
|
||||
f4 += struct.pack('>H', 0)
|
||||
f4 += struct.pack('>%dH' % count, *[s[0] for s in segs])
|
||||
deltas = [((s[2] - s[0]) & 0xFFFF) if s[0] != 0xFFFF else 1 for s in segs]
|
||||
f4 += struct.pack('>%dh' % count, *[x - 65536 if x > 32767 else x for x in deltas])
|
||||
f4 += struct.pack('>%dH' % count, *([0] * count))
|
||||
|
||||
f12 = struct.pack('>HHIII', 12, 0, 16 + len(groups) * 12, 0, len(groups))
|
||||
for a, b, g in groups:
|
||||
f12 += struct.pack('>III', a, b, g)
|
||||
|
||||
f0 = struct.pack('>HHH', 0, 262, 0) + bytes(256)
|
||||
|
||||
body, offsets = b'', {}
|
||||
for data in (f4, f12, f0):
|
||||
offsets[id(data)] = len(body)
|
||||
body += data
|
||||
records = [(0, 3, f4), (0, 4, f12), (1, 0, f0), (3, 1, f4), (3, 10, f12)]
|
||||
head = struct.pack('>HH', 0, len(records))
|
||||
base = 4 + len(records) * 8
|
||||
for pid, eid, data in records:
|
||||
head += struct.pack('>HHI', pid, eid, base + offsets[id(data)])
|
||||
return head + body
|
||||
|
||||
|
||||
def assemble(tables):
|
||||
tags = sorted(tables)
|
||||
n = len(tags)
|
||||
sr = 2 ** (n.bit_length() - 1) * 16
|
||||
font = struct.pack('>IHHHH', 0x00010000, n, sr, n.bit_length() - 1, n * 16 - sr)
|
||||
offset = 12 + n * 16
|
||||
body, records = b'', []
|
||||
for tag in tags:
|
||||
data = tables[tag]
|
||||
records.append((tag, checksum(data), offset + len(body), len(data)))
|
||||
body += data + b'\0' * (-len(data) % 4)
|
||||
font += b''.join(struct.pack('>4sIII', t.encode('latin-1'), c, o, l)
|
||||
for t, c, o, l in records)
|
||||
font += body
|
||||
adj = (0xB1B0AFBA - checksum(font)) & 0xFFFFFFFF
|
||||
hs = [o for t, c, o, l in records if t == 'head'][0]
|
||||
return font[:hs + 8] + struct.pack('>I', adj) + font[hs + 12:]
|
||||
|
||||
|
||||
# --- commands --------------------------------------------------------------
|
||||
|
||||
def load_font(path):
|
||||
d = open(path, 'rb').read()
|
||||
t = read_tables(d)
|
||||
ng = struct.unpack('>H', d[t['maxp'][0] + 4:t['maxp'][0] + 6])[0]
|
||||
return d, t, ng
|
||||
|
||||
|
||||
def glyph_list(path):
|
||||
d, t, ng = load_font(path)
|
||||
loca = read_loca(d, t, ng)
|
||||
names = read_names(d, t['post'][0], ng)
|
||||
gs = t['glyf'][0]
|
||||
rows = []
|
||||
for cp, gid in sorted(read_cmap(d, t['cmap'][0]).items()):
|
||||
if cp < PUA_FIRST:
|
||||
continue
|
||||
a, b = loca[gid], loca[gid + 1]
|
||||
box = ''
|
||||
if b > a:
|
||||
_, x0, y0, x1, y1 = struct.unpack('>hhhhh', d[gs + a:gs + a + 10])
|
||||
box = '%d x %d' % (x1 - x0, y1 - y0)
|
||||
name = names[gid]
|
||||
rows.append((cp, '' if name.startswith('#mac') else name, box))
|
||||
return rows
|
||||
|
||||
|
||||
def add_glyph(font_path, codepoint, name, contours):
|
||||
d, t, ng = load_font(font_path)
|
||||
loca = read_loca(d, t, ng)
|
||||
gs = t['glyf'][0]
|
||||
glyphs = [d[gs + loca[i]:gs + loca[i + 1]] for i in range(ng)]
|
||||
|
||||
nhm = struct.unpack('>H', d[t['hhea'][0] + 34:t['hhea'][0] + 36])[0]
|
||||
hm = t['hmtx'][0]
|
||||
adv, lsb = [], []
|
||||
for i in range(ng):
|
||||
if i < nhm:
|
||||
a, b = struct.unpack('>Hh', d[hm + i * 4:hm + i * 4 + 4])
|
||||
else:
|
||||
a = adv[-1]
|
||||
b = struct.unpack('>h', d[hm + nhm * 4 + (i - nhm) * 2:][:2])[0]
|
||||
adv.append(a)
|
||||
lsb.append(b)
|
||||
|
||||
names = read_names(d, t['post'][0], ng)
|
||||
cmap = read_cmap(d, t['cmap'][0])
|
||||
|
||||
gid = len(glyphs)
|
||||
glyphs.append(encode_glyph(contours))
|
||||
xs = [p[0] for c in contours for p in c]
|
||||
adv.append(UPEM)
|
||||
lsb.append(min(xs) if xs else 0)
|
||||
names.append(name)
|
||||
cmap[codepoint] = gid
|
||||
ng = len(glyphs)
|
||||
|
||||
offs, o = [0], 0
|
||||
for g in glyphs:
|
||||
o += len(g)
|
||||
offs.append(o)
|
||||
short = offs[-1] <= 0x1FFFE and all(x % 2 == 0 for x in offs)
|
||||
|
||||
hhea = bytearray(d[t['hhea'][0]:t['hhea'][0] + t['hhea'][1]])
|
||||
struct.pack_into('>H', hhea, 34, ng)
|
||||
|
||||
maxp = bytearray(d[t['maxp'][0]:t['maxp'][0] + t['maxp'][1]])
|
||||
struct.pack_into('>H', maxp, 4, ng)
|
||||
struct.pack_into('>H', maxp, 6, max(struct.unpack('>H', bytes(maxp[6:8]))[0],
|
||||
sum(len(c) for c in contours)))
|
||||
struct.pack_into('>H', maxp, 8, max(struct.unpack('>H', bytes(maxp[8:10]))[0],
|
||||
len(contours)))
|
||||
|
||||
boxes = [struct.unpack('>hhhhh', g[:10])[1:] for g in glyphs if len(g) >= 10]
|
||||
head = bytearray(d[t['head'][0]:t['head'][0] + t['head'][1]])
|
||||
struct.pack_into('>hhhh', head, 36,
|
||||
min(b[0] for b in boxes), min(b[1] for b in boxes),
|
||||
max(b[2] for b in boxes), max(b[3] for b in boxes))
|
||||
struct.pack_into('>h', head, 50, 0 if short else 1)
|
||||
struct.pack_into('>I', head, 8, 0) # checkSumAdjustment, recomputed below
|
||||
|
||||
tables = {tag: d[s:s + l] for tag, (s, l) in t.items()}
|
||||
tables['glyf'] = b''.join(glyphs)
|
||||
tables['loca'] = (struct.pack('>%dH' % len(offs), *[x // 2 for x in offs]) if short
|
||||
else struct.pack('>%dI' % len(offs), *offs))
|
||||
tables['hmtx'] = b''.join(struct.pack('>Hh', adv[i], lsb[i]) for i in range(ng))
|
||||
tables['hhea'] = bytes(hhea)
|
||||
tables['maxp'] = bytes(maxp)
|
||||
tables['cmap'] = build_cmap(cmap)
|
||||
tables['post'] = build_post(d, t['post'][0], names)
|
||||
tables['head'] = bytes(head)
|
||||
|
||||
open(font_path, 'wb').write(assemble(tables))
|
||||
|
||||
|
||||
def read_svg(location):
|
||||
if not location.startswith(('http://', 'https://')):
|
||||
return open(location, encoding='utf-8').read()
|
||||
# Icon sites reject the stock urllib agent, so ask like a browser would.
|
||||
req = urllib.request.Request(location, headers={'User-Agent': 'omarchy-dev-font'})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return r.read().decode('utf-8')
|
||||
except OSError as e:
|
||||
sys.exit('could not fetch %s: %s' % (location, e))
|
||||
|
||||
|
||||
def svg_contours(svg, location):
|
||||
vb = re.search(r'viewBox="([^"]+)"', svg)
|
||||
if not vb:
|
||||
sys.exit('%s: no viewBox; need an SVG with a viewBox' % location)
|
||||
view = tuple(float(x) for x in vb.group(1).replace(',', ' ').split())
|
||||
paths = re.findall(r'<path[^>]*\bd="([^"]+)"', svg)
|
||||
if len(paths) != 1:
|
||||
sys.exit('%s: expected a single <path>, found %d — flatten the mark to '
|
||||
'one monochrome path first' % (location, len(paths)))
|
||||
return contours_from_svg(paths[0], view)
|
||||
|
||||
|
||||
def default_font():
|
||||
root = os.environ.get('OMARCHY_PATH')
|
||||
if not root:
|
||||
sys.exit('OMARCHY_PATH is not set')
|
||||
return os.path.join(root, 'default/fonts/omarchy/omarchy.ttf')
|
||||
|
||||
|
||||
def readme_for(font_path):
|
||||
return os.path.join(os.path.dirname(font_path), 'README.md')
|
||||
|
||||
|
||||
def record_source(font_path, codepoint, label, location):
|
||||
"""Append the new mark to the font's README so sources stay tracked."""
|
||||
readme = readme_for(font_path)
|
||||
if not os.path.exists(readme):
|
||||
return None
|
||||
lines = open(readme, encoding='utf-8').read().split('\n')
|
||||
last = max(i for i, l in enumerate(lines) if l.startswith('- `U+'))
|
||||
entry = '- `U+%04X` — %s' % (codepoint, label)
|
||||
if location.startswith(('http://', 'https://')):
|
||||
entry += ', from <%s>' % location
|
||||
lines.insert(last + 1, entry)
|
||||
open(readme, 'w', encoding='utf-8').write('\n'.join(lines))
|
||||
return readme
|
||||
|
||||
|
||||
def main():
|
||||
common = argparse.ArgumentParser(add_help=False)
|
||||
common.add_argument('--font', help='font to edit '
|
||||
'(default: $OMARCHY_PATH/default/fonts/omarchy/omarchy.ttf)')
|
||||
|
||||
p = argparse.ArgumentParser(prog='omarchy dev font', parents=[common],
|
||||
description=__doc__.split('\n')[0])
|
||||
sub = p.add_subparsers(dest='command')
|
||||
sub.add_parser('list', parents=[common], help='show the marks the font carries')
|
||||
add = sub.add_parser('add', parents=[common],
|
||||
help='append a mark from a monochrome SVG')
|
||||
add.add_argument('name', help='glyph name, e.g. ollama')
|
||||
add.add_argument('svg', help='SVG file or URL')
|
||||
add.add_argument('--codepoint', help='private-use codepoint (default: next free)')
|
||||
add.add_argument('--label', help='README label (default: the glyph name)')
|
||||
args = p.parse_args()
|
||||
|
||||
font = args.font or default_font()
|
||||
if not os.path.exists(font):
|
||||
sys.exit('no font at %s' % font)
|
||||
|
||||
if args.command in (None, 'list'):
|
||||
for cp, name, box in glyph_list(font):
|
||||
print('U+%04X %s %-12s %s' % (cp, chr(cp), name, box))
|
||||
return
|
||||
|
||||
taken = {cp for cp, _, _ in glyph_list(font)}
|
||||
if args.codepoint:
|
||||
cp = int(args.codepoint.upper().replace('U+', ''), 16)
|
||||
if cp in taken:
|
||||
sys.exit('U+%04X is already used' % cp)
|
||||
else:
|
||||
cp = max(taken) + 1 if taken else PUA_FIRST
|
||||
|
||||
contours = svg_contours(read_svg(args.svg), args.svg)
|
||||
add_glyph(font, cp, args.name, contours)
|
||||
readme = record_source(font, cp, args.label or args.name, args.svg)
|
||||
|
||||
print('Added %s as U+%04X (%s)' % (args.name, cp, chr(cp)))
|
||||
print()
|
||||
print('Next:')
|
||||
if readme:
|
||||
print(' - check the new line in %s' % os.path.relpath(readme))
|
||||
print(' - point a menu entry at it: "icon":"%s","iconFont":"omarchy"' % chr(cp))
|
||||
print(' - bump the charset range in test/shell.d/menu-test.sh to e900-%x' % cp)
|
||||
print(' - the font is package-owned, so it reaches the desktop through an')
|
||||
print(' omarchy-settings release, not through omarchy update')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -2,8 +2,11 @@
|
||||
|
||||
# omarchy:summary=Returns true when an external monitor is physically connected.
|
||||
|
||||
for status in /sys/class/drm/card*-*/status; do
|
||||
[[ $status == *-eDP-*/status ]] && continue
|
||||
drm_path="${OMARCHY_DRM_PATH:-/sys/class/drm}"
|
||||
|
||||
for status in "$drm_path"/card*-*/status; do
|
||||
[[ -e $status ]] || continue
|
||||
[[ $status =~ -(eDP|LVDS|DSI)-[^/]+/status$ ]] && continue
|
||||
[[ $(< $status) == "connected" ]] && exit 0
|
||||
done
|
||||
exit 1
|
||||
@@ -9,6 +9,7 @@
|
||||
# also make USB touchscreens) are left out to avoid nagging laptops with no
|
||||
# reader — those still match on the product string below when present.
|
||||
fingerprint_vendors=" 27c6 138a 06cb 08ff 1c7a 147e "
|
||||
usb_devices_path="${OMARCHY_USB_DEVICES_PATH:-/sys/bus/usb/devices}"
|
||||
|
||||
# libfprint drives every reader it supports from userspace over libusb, so a
|
||||
# real reader sits there with no kernel driver bound to any of its interfaces.
|
||||
@@ -17,23 +18,32 @@ fingerprint_vendors=" 27c6 138a 06cb 08ff 1c7a 147e "
|
||||
# — all bind one. Only the vendor-ID guess needs this; a device that names
|
||||
# itself a fingerprint reader is trusted outright.
|
||||
has_kernel_driver() {
|
||||
local intf
|
||||
local intf driver
|
||||
for intf in "$1"/*:*; do
|
||||
[[ -e $intf/driver ]] && return 0
|
||||
[[ -e $intf/driver ]] || continue
|
||||
# usbfs is the exception: libusb claims an interface through it, so a reader
|
||||
# fprintd is enrolling or verifying against binds a driver for as long as it
|
||||
# holds the claim. That is userspace driving the device — what a reader is
|
||||
# supposed to look like — so it must not read as a kernel driver here.
|
||||
driver=$(readlink -f "$intf/driver")
|
||||
[[ ${driver##*/} == "usbfs" ]] || return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
for dev in /sys/bus/usb/devices/*; do
|
||||
for dev in "$usb_devices_path"/*; do
|
||||
# The device's own product descriptor usually names it, e.g. "Goodix
|
||||
# Fingerprint USB Device" — driver-independent and vendor-agnostic.
|
||||
if [[ -r $dev/product ]]; then
|
||||
product=$(<"$dev/product")
|
||||
product=${product,,}
|
||||
# Elan's match-on-chip readers report "ELAN:ARM-M4", the family name
|
||||
# rather than the function. Elan is left out of the vendor list above
|
||||
# on purpose, so without this they match nothing.
|
||||
[[ $product == *fingerprint* || $product == *biometric* || $product == *elan:arm-m4* ]] && exit 0
|
||||
# Elan's match-on-chip readers report "ELAN:ARM-M4" and Fingerprint Cards'
|
||||
# report "FPC Sensor Controller" or "FPC L:0000 FW:1425046" — the family or
|
||||
# the manufacturer rather than the function. Both vendors are left out of
|
||||
# the list above on purpose (Elan also makes touchscreens), so without these
|
||||
# they match nothing. FPC leads the string on every reader on record, and
|
||||
# three letters are little to match on, so require the prefix.
|
||||
[[ $product == *fingerprint* || $product == *biometric* || $product == *elan:arm-m4* || $product == "fpc "* ]] && exit 0
|
||||
fi
|
||||
|
||||
if [[ -r $dev/idVendor ]]; then
|
||||
|
||||
@@ -2,8 +2,23 @@
|
||||
|
||||
# omarchy:summary=Detect whether the system has an active hybrid GPU configuration
|
||||
|
||||
if omarchy-cmd-present supergfxctl; then
|
||||
supergfxctl -s 2>/dev/null | grep -qw Hybrid
|
||||
else
|
||||
multiple_gpus() {
|
||||
(($(lspci | grep -cE 'VGA|3D|Display') >= 2))
|
||||
}
|
||||
|
||||
if omarchy-cmd-present supergfxctl; then
|
||||
# A wedged supergfxd blocks its clients forever, and this gate runs while
|
||||
# the menu renders. Bound the query, and treat a daemon that cannot answer
|
||||
# like a machine without supergfxctl: count GPUs instead of hiding hardware
|
||||
# that is really there.
|
||||
modes=$(timeout --kill-after=1s 1s supergfxctl -s 2>/dev/null)
|
||||
status=$?
|
||||
|
||||
if ((status == 124 || status == 137)); then
|
||||
multiple_gpus
|
||||
else
|
||||
grep -qw Hybrid <<<"$modes"
|
||||
fi
|
||||
else
|
||||
multiple_gpus
|
||||
fi
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
# omarchy:summary=Check whether a webcam is available
|
||||
|
||||
v4l2-ctl --list-devices 2>/dev/null | grep -q '^[[:space:]]*/dev/video'
|
||||
[[ -n $(omarchy-capture-webcam-list) ]]
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Focus a Hyprland window by application class
|
||||
# omarchy:summary=Focus a Hyprland window by application identity
|
||||
# omarchy:args=<app-name>
|
||||
# omarchy:examples=omarchy hyprland focus app Slack
|
||||
|
||||
@@ -12,10 +12,17 @@ usage() {
|
||||
app=${1:-}
|
||||
[[ -n $app ]] || usage
|
||||
|
||||
# Agent terminals notify as kitty/foot/etc. while their shared window class is
|
||||
# org.omarchy.agent, leaving the terminal name only in initialTitle. So match
|
||||
# by class first, then fall back to the launch-time title of agent windows.
|
||||
address=$(
|
||||
hyprctl clients -j 2>/dev/null |
|
||||
jq -r --arg pattern "$app" \
|
||||
'[.[] | select((.class // "") | test($pattern; "i"))] | first.address // empty'
|
||||
'def matches($value): ($value // "") | test($pattern; "i");
|
||||
first(
|
||||
(.[] | select(matches(.class))),
|
||||
(.[] | select(.initialClass == "org.omarchy.agent" and matches(.initialTitle)))
|
||||
).address // empty'
|
||||
)
|
||||
|
||||
[[ -n $address ]] || exit 1
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install the ChatGPT desktop app
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing ChatGPT..."
|
||||
omarchy-pkg-add openai-codex-desktop
|
||||
|
||||
echo "Opening ChatGPT..."
|
||||
setsid uwsm-app -- /usr/bin/chatgpt >/dev/null 2>&1 &
|
||||
|
||||
echo ""
|
||||
echo "ChatGPT has been installed."
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Restore the preinstalled Omarchy applications (web apps, TUIs, and selected packages).
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
if gum confirm "Are you sure you want to restore all preinstalled web apps, TUI wrappers, and desktop applications?"; then
|
||||
echo -e "Restoring preinstalled Omarchy applications...\n"
|
||||
|
||||
# Recreates the shipped .desktop launchers (web apps and TUIs) and the mise stubs
|
||||
# that back claude, gh, opencode, and the rest of the agents
|
||||
omarchy-refresh-applications
|
||||
|
||||
# Mirrors the list in omarchy-remove-preinstalls; both track omarchy-base.packages
|
||||
if ! omarchy-pkg-add \
|
||||
aether \
|
||||
cliamp \
|
||||
libreoffice-fresh \
|
||||
xournalpp \
|
||||
pinta \
|
||||
obsidian \
|
||||
obs-studio \
|
||||
kdenlive \
|
||||
moonlight-qt \
|
||||
lazydocker \
|
||||
omacut \
|
||||
omacalc \
|
||||
omawrite; then
|
||||
echo -e "\nPreinstalls are still marked as removed. Fix the errors above and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Last, so a failure above leaves the opt-out intact rather than restoring
|
||||
# keybindings for apps that never came back
|
||||
rm -f ~/.local/state/omarchy/preinstalls-removed
|
||||
hyprctl reload
|
||||
fi
|
||||
+132
-40
@@ -2,13 +2,32 @@
|
||||
|
||||
# omarchy:summary=Launch the fastfetch TUI that gives information about the current system.
|
||||
|
||||
# The float rule only sets a starting size: the exact fit depends on the terminal
|
||||
# font and the user's About logo, so once the terminal grid has settled we measure
|
||||
# the rendered content and resize the window to hug it with even padding all around.
|
||||
# Bash defers WINCH traps while read blocks, so poll for size changes and re-render
|
||||
# fastfetch at the new width whenever the window is resized.
|
||||
# The size that hugs the About content depends on the terminal font and the user's
|
||||
# logo, so it can only be measured from inside the terminal. We remember the size
|
||||
# that fit and apply it as a window rule before launching, so the window opens at
|
||||
# it instead of resizing after the first paint. Bash defers WINCH traps while read
|
||||
# blocks, so poll for size changes and re-render fastfetch whenever it is resized.
|
||||
|
||||
LOGO_FILE="$HOME/.config/omarchy/branding/about.txt"
|
||||
FIT_FILE="$HOME/.local/state/omarchy/windows/about.fit"
|
||||
|
||||
# A user-level fastfetch config can relocate or restyle the logo in ways this
|
||||
# measurement cannot see, so leave sizing to the float rule in that case.
|
||||
custom_fastfetch_config() {
|
||||
[[ -f $HOME/.config/fastfetch/config.jsonc ]]
|
||||
}
|
||||
|
||||
# wc -L counts display columns only in a UTF-8 locale. A session that never set
|
||||
# one counts every box-drawing and Nerd Font glyph in the About layout as
|
||||
# nothing, which measures the content narrower than it renders.
|
||||
display_columns() {
|
||||
LC_ALL=C.UTF-8 wc -L
|
||||
}
|
||||
|
||||
logo_dimensions() {
|
||||
[[ -f $LOGO_FILE ]] || return 1
|
||||
printf '%s %s' "$(display_columns <"$LOGO_FILE")" "$(wc -l <"$LOGO_FILE")"
|
||||
}
|
||||
|
||||
hypr_dispatch() {
|
||||
local lua="$1"
|
||||
@@ -16,74 +35,147 @@ hypr_dispatch() {
|
||||
hyprctl dispatch "$lua" >/dev/null 2>&1 || hyprctl dispatch "$@" >/dev/null
|
||||
}
|
||||
|
||||
remember_fit() {
|
||||
local directory tmp
|
||||
directory=$(dirname "$FIT_FILE")
|
||||
mkdir -p "$directory"
|
||||
tmp=$(mktemp "$directory/.about.fit.XXXXXX")
|
||||
printf '%s %s %s %s\n' "$1" "$2" "$3" "$4" >"$tmp"
|
||||
mv "$tmp" "$FIT_FILE"
|
||||
}
|
||||
|
||||
# Replaces the rule from the last launch so a remembered size never outlives the
|
||||
# fit it came from. Called without one it only clears, leaving the float rule's
|
||||
# starting size — where a Hyprland without the Lua API stays too.
|
||||
apply_size_rule() {
|
||||
local rule=""
|
||||
(( $# == 2 )) && rule="omarchy_about_size_rule = hl.window_rule({ match = { class = \"org.omarchy.about\" }, size = { $1, $2 } })"
|
||||
|
||||
hyprctl eval "if omarchy_about_size_rule then omarchy_about_size_rule:set_enabled(false) end; omarchy_about_size_rule = nil; $rule" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Sized before the terminal is spawned, so the window maps at its final size.
|
||||
presize_window() {
|
||||
local logo_w logo_h fit_logo_w fit_logo_h fit_w fit_h
|
||||
|
||||
if ! custom_fastfetch_config && [[ -r $FIT_FILE ]]; then
|
||||
read -r logo_w logo_h <<<"$(logo_dimensions)"
|
||||
read -r fit_logo_w fit_logo_h fit_w fit_h <"$FIT_FILE"
|
||||
|
||||
# A different logo needs a different window, so leave that launch to the
|
||||
# float rule and let the fit measure the new size.
|
||||
if [[ -n ${logo_w:-} && $logo_w == "$fit_logo_w" && $logo_h == "$fit_logo_h" ]] &&
|
||||
[[ $fit_w =~ ^[0-9]+$ && $fit_h =~ ^[0-9]+$ ]]; then
|
||||
apply_size_rule "$fit_w" "$fit_h"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
apply_size_rule
|
||||
}
|
||||
|
||||
# Hyprland animates a resize and the terminal reflows to every step of it, so
|
||||
# wait for the grid to hold still before measuring it.
|
||||
settle_grid() {
|
||||
local current previous="" held=0
|
||||
|
||||
for _ in {1..20}; do
|
||||
current=$(stty size)
|
||||
if [[ $current == $previous ]]; then
|
||||
(( ++held == 3 )) && break
|
||||
else
|
||||
held=0
|
||||
previous=$current
|
||||
fi
|
||||
sleep 0.05
|
||||
done
|
||||
}
|
||||
|
||||
fit_window() {
|
||||
# A user-level fastfetch config can relocate or restyle the logo in ways this
|
||||
# measurement cannot see, so leave sizing to the float rule in that case.
|
||||
[[ -f $HOME/.config/fastfetch/config.jsonc ]] && return 0
|
||||
custom_fastfetch_config && return 0
|
||||
|
||||
local rows cols address width height
|
||||
read -r rows cols <<<"$(stty size)"
|
||||
read -r address width height <<<"$(hyprctl clients -j | jq -r '.[] | select(.class == "org.omarchy.about") | "\(.address) \(.size[0]) \(.size[1])"')"
|
||||
[[ -f $LOGO_FILE && -n ${address:-} ]] || return 1
|
||||
(( cols > 0 && rows > 0 && width > 0 && height > 0 )) || return 1
|
||||
|
||||
# Mirror the logo block in the fastfetch config: 2 columns of padding on the
|
||||
# left of the logo, 6 between the logo and the modules, 2 rows above the logo.
|
||||
# wc -L measures display columns, so double-width glyphs count correctly.
|
||||
local logo_w logo_h
|
||||
logo_w=$(wc -L <"$LOGO_FILE")
|
||||
logo_h=$(( $(wc -l <"$LOGO_FILE") + 2 ))
|
||||
read -r logo_w logo_h <<<"$(logo_dimensions)"
|
||||
[[ -n ${logo_w:-} ]] || return 1
|
||||
|
||||
# The guard character keeps command substitution from eating the trailing
|
||||
# break line, which provides the bottom padding row.
|
||||
local modules module_w module_h
|
||||
modules=$(fastfetch --logo none | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g'; printf X)
|
||||
modules=${modules%X}
|
||||
module_w=$(printf '%s' "$modules" | wc -L)
|
||||
module_w=$(printf '%s' "$modules" | display_columns)
|
||||
module_h=$(printf '%s' "$modules" | wc -l)
|
||||
|
||||
# Two columns of right padding to match the logo's left padding, and one row
|
||||
# for the cursor line so the trailing break stays visible as bottom padding.
|
||||
# Mirror the logo block in the fastfetch config: 2 columns of padding left of
|
||||
# the logo, 6 between logo and modules, 2 rows above it. Then 2 columns of
|
||||
# right padding to match, and a row for the cursor so the trailing break shows.
|
||||
local target_c=$(( 2 + logo_w + 6 + module_w + 2 ))
|
||||
local target_r=$(( (logo_h > module_h ? logo_h : module_h) + 1 ))
|
||||
local target_w=$(( (target_c * width + cols / 2) / cols ))
|
||||
local target_h=$(( (target_r * height + rows / 2) / rows ))
|
||||
local target_r=$(( (logo_h + 2 > module_h ? logo_h + 2 : module_h) + 1 ))
|
||||
|
||||
# Skip resizes within one terminal cell so the fit doesn't churn.
|
||||
(( (target_w - width) ** 2 <= (width / cols) ** 2 && (target_h - height) ** 2 <= (height / rows) ** 2 )) && return
|
||||
local nudges=0 rows cols address width height shift_w shift_h target_w target_h
|
||||
while :; do
|
||||
read -r rows cols <<<"$(stty size)"
|
||||
read -r address width height <<<"$(hyprctl clients -j | jq -r '.[] | select(.class == "org.omarchy.about") | "\(.address) \(.size[0]) \(.size[1])"')"
|
||||
[[ -n ${address:-} ]] || return 1
|
||||
(( cols > 0 && rows > 0 && width > 0 && height > 0 )) || return 1
|
||||
|
||||
hypr_dispatch "hl.dsp.window.resize({ window = \"address:$address\", x = $target_w, y = $target_h })" resizewindowpixel "exact $target_w $target_h,address:$address"
|
||||
hypr_dispatch "hl.dsp.window.center({ window = \"address:$address\" })" centerwindow
|
||||
# A window has to land on the terminal's cell boundaries, so take a cell of
|
||||
# slack over chasing an exact grid, and remember where it came to rest.
|
||||
if (( cols >= target_c && cols <= target_c + 1 && rows >= target_r && rows <= target_r + 1 )); then
|
||||
remember_fit "$logo_w" "$logo_h" "$width" "$height"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Two nudges is the budget, and each one is measured before the next is spent.
|
||||
(( ++nudges <= 2 )) || return 1
|
||||
|
||||
# Move by the cells the window is off by, rather than scaling it to the grid,
|
||||
# which would multiply up the terminal's padding along with them. Dividing a
|
||||
# window that carries that padding still leaves the cell a touch generous, so
|
||||
# round the move away from the grid that would clip.
|
||||
shift_w=$(( (target_c - cols) * width ))
|
||||
shift_h=$(( (target_r - rows) * height ))
|
||||
target_w=$(( width + (shift_w >= 0 ? (shift_w + cols - 1) / cols : shift_w / cols) ))
|
||||
target_h=$(( height + (shift_h >= 0 ? (shift_h + rows - 1) / rows : shift_h / rows) ))
|
||||
|
||||
hypr_dispatch "hl.dsp.window.resize({ window = \"address:$address\", x = $target_w, y = $target_h })" resizewindowpixel "exact $target_w $target_h,address:$address"
|
||||
hypr_dispatch "hl.dsp.window.center({ window = \"address:$address\" })" centerwindow
|
||||
settle_grid
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if [[ ${1:-} == "--render" ]]; then
|
||||
printf '\e[?25l'
|
||||
|
||||
# Give the compositor a moment to apply the float rule before measuring cells.
|
||||
previous=""
|
||||
for _ in {1..20}; do
|
||||
current=$(stty size)
|
||||
[[ $current == $previous ]] && break
|
||||
previous=$current
|
||||
sleep 0.05
|
||||
done
|
||||
# Give the compositor a moment to apply the window rules before measuring cells.
|
||||
settle_grid
|
||||
|
||||
fitted=false
|
||||
passes=0
|
||||
while :; do
|
||||
size=$(stty size)
|
||||
logo_stamp=$(stat -c %Y "$LOGO_FILE" 2>/dev/null)
|
||||
clear
|
||||
fastfetch
|
||||
if [[ $fitted == false ]] && fit_window; then
|
||||
fitted=true
|
||||
# A second pass picks up a fit that could not measure the window the first
|
||||
# time. Beyond that, a window that will not settle would be fitted again on
|
||||
# every repaint.
|
||||
if [[ $fitted == false ]] && (( ++passes <= 2 )); then
|
||||
fit_window && fitted=true
|
||||
fi
|
||||
while [[ $(stty size) == $size && $(stat -c %Y "$LOGO_FILE" 2>/dev/null) == $logo_stamp ]]; do
|
||||
read -t 0.5 -n 1 -s && exit
|
||||
(( $? > 128 )) || exit
|
||||
done
|
||||
# A rebranded logo changes the content dimensions, so measure again.
|
||||
[[ $(stat -c %Y "$LOGO_FILE" 2>/dev/null) == $logo_stamp ]] || fitted=false
|
||||
if [[ $(stat -c %Y "$LOGO_FILE" 2>/dev/null) != $logo_stamp ]]; then
|
||||
fitted=false
|
||||
passes=0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
presize_window
|
||||
exec omarchy-launch-or-focus-tui --app-id=org.omarchy.about omarchy-launch-about --render
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Launch the default coding agent in a terminal
|
||||
# omarchy:args=[--inline] [prompt...]
|
||||
# omarchy:examples=omarchy launch agent | omarchy launch agent "Review this project"
|
||||
|
||||
if [[ ${1:-} == "--inline" ]]; then
|
||||
inline=true
|
||||
shift
|
||||
else
|
||||
inline=false
|
||||
fi
|
||||
|
||||
# Agents refuse to remember trust for $HOME, so launches from the keybinding or
|
||||
# menu start in the work directory instead of re-asking on every session.
|
||||
[[ $PWD == "$HOME" && -d $HOME/Work ]] && cd "$HOME/Work"
|
||||
|
||||
agent=$(omarchy-default-agent)
|
||||
|
||||
if omarchy-cmd-missing "$agent"; then
|
||||
echo "$agent is not installed. Choose an installed agent with: omarchy default agent <name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (($# > 0)); then
|
||||
prompt="$*"
|
||||
fi
|
||||
|
||||
case "$agent" in
|
||||
opencode)
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(opencode --prompt "$prompt")
|
||||
else
|
||||
command=(opencode)
|
||||
fi
|
||||
;;
|
||||
gemini)
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(gemini --prompt-interactive "$prompt")
|
||||
else
|
||||
command=(gemini)
|
||||
fi
|
||||
;;
|
||||
copilot)
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(copilot --interactive "$prompt")
|
||||
else
|
||||
command=(copilot)
|
||||
fi
|
||||
;;
|
||||
crush)
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(crush run "$prompt")
|
||||
else
|
||||
command=(crush)
|
||||
fi
|
||||
;;
|
||||
pi | omp | claude | codex | grok)
|
||||
command=("$agent")
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported default agent: $agent" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $inline == "true" ]]; then
|
||||
exec "${command[@]}"
|
||||
else
|
||||
exec omarchy-launch-tui "${command[@]}"
|
||||
fi
|
||||
@@ -9,8 +9,14 @@
|
||||
#
|
||||
# Backgrounded because bash defers a trap until a foreground command returns but
|
||||
# interrupts wait. systemd-cat execs, so the job is Quickshell itself.
|
||||
#
|
||||
# Quickshell's own reloading is off; Omarchy restarts the shell deliberately.
|
||||
# A package upgrade rewriting $OMARCHY_PATH/shell would otherwise reload it
|
||||
# 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() {
|
||||
systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" &
|
||||
QS_DISABLE_FILE_WATCHER=1 QS_NO_RELOAD_POPUP=1 \
|
||||
systemd-cat -t omarchy-shell -- quickshell -n -p "$OMARCHY_PATH/shell" &
|
||||
shell_pid=$!
|
||||
|
||||
local status
|
||||
|
||||
+43
-22
@@ -111,8 +111,8 @@ cache_key=$(printf '%s' "$image_dirs_env" | md5sum | cut -d ' ' -f 1)
|
||||
rows_cache_file="$cache_dir/$cache_key.rows"
|
||||
rows_signature_file="$cache_dir/$cache_key.signature"
|
||||
rows_fast_signature_file="$cache_dir/$cache_key.fast-signature"
|
||||
rows_signature="v2"$'\n'
|
||||
rows_fast_signature="v1"$'\n'
|
||||
rows_signature="v3"$'\n'
|
||||
rows_fast_signature="v2"$'\n'
|
||||
rows_cacheable=true
|
||||
rows_cache_hit=false
|
||||
image_files=()
|
||||
@@ -142,23 +142,32 @@ generate_thumbnail() {
|
||||
local image="$1"
|
||||
local thumbnail="$2"
|
||||
local lock="$thumbnail.lock"
|
||||
local lock_fd
|
||||
local tmp="$thumbnail.$$.jpg"
|
||||
|
||||
if mkdir "$lock" 2>/dev/null; then
|
||||
# Callers fan out one generator per image, so keep each vips single-threaded.
|
||||
if VIPS_CONCURRENCY=1 vipsthumbnail "$image" --size 1536x864 --smartcrop=centre --path "$tmp[Q=82,strip]"; then
|
||||
mv -f "$tmp" "$thumbnail"
|
||||
else
|
||||
rm -f "$tmp" "$thumbnail"
|
||||
fi
|
||||
# Older releases used directories as locks, which could survive a killed
|
||||
# generator and block this thumbnail forever. Only reap aged ones, so a
|
||||
# legacy generator still running through an upgrade keeps its lock.
|
||||
if [[ -d $lock ]] && (( $(date +%s) - $(stat -c '%Y' "$lock" 2>/dev/null || date +%s) > 120 )); then
|
||||
rmdir "$lock" 2>/dev/null
|
||||
fi
|
||||
|
||||
rmdir "$lock" 2>/dev/null || true
|
||||
exec {lock_fd}>"$lock" || return
|
||||
flock -w 30 "$lock_fd" || return
|
||||
|
||||
# A generator killed mid-write leaves its partial $thumbnail.<pid>.jpg
|
||||
# behind. Only the lock holder writes these, so any found now are stale.
|
||||
rm -f "$thumbnail".*.jpg
|
||||
|
||||
[[ -f $thumbnail ]] && return
|
||||
|
||||
# Callers fan out one generator per image, so keep each vips single-threaded.
|
||||
# Close the lock fd for vips: an orphaned or hung vips must not keep holding
|
||||
# the lock after this shell is killed.
|
||||
if VIPS_CONCURRENCY=1 vipsthumbnail "$image" --size 1536x864 --smartcrop=centre --path "$tmp[Q=82,strip]" {lock_fd}>&-; then
|
||||
mv -f "$tmp" "$thumbnail"
|
||||
else
|
||||
for ((i = 0; i < 3000; i++)); do
|
||||
[[ -f $thumbnail ]] && return
|
||||
[[ -d $lock ]] || break
|
||||
sleep 0.01
|
||||
done
|
||||
rm -f "$tmp" "$thumbnail"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -226,7 +235,10 @@ elif [[ $rows_cache_hit != true ]]; then
|
||||
if [[ -s $pending_file ]]; then
|
||||
pruned=""
|
||||
while IFS=$'\t' read -r row_image row_thumbnail; do
|
||||
[[ -e $row_thumbnail ]] || continue
|
||||
if [[ ! -e $row_thumbnail ]]; then
|
||||
rows_cacheable=false
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -z $pruned ]]; then
|
||||
pruned="$row_image"$'\t'"$row_thumbnail"
|
||||
@@ -237,12 +249,21 @@ elif [[ $rows_cache_hit != true ]]; then
|
||||
rows="$pruned"
|
||||
fi
|
||||
|
||||
if [[ $rows_cacheable == true ]]; then
|
||||
printf '%s' "$rows" >"$rows_cache_file"
|
||||
printf '%s' "$rows_signature" >"$rows_signature_file"
|
||||
printf '%s' "$rows_fast_signature" >"$rows_fast_signature_file"
|
||||
else
|
||||
rm -f "$rows_cache_file" "$rows_signature_file" "$rows_fast_signature_file"
|
||||
# Publish the cache under a lock and via renames: a picker killed mid-write,
|
||||
# or two pickers interleaving, must never leave truncated or mismatched rows
|
||||
# behind signatures that still validate. Rows go first so a kill between
|
||||
# renames leaves signatures that are either older (a harmless miss) or
|
||||
# describe the same directory state.
|
||||
if exec {rows_lock_fd}>"$rows_cache_file.lock" && flock -w 30 "$rows_lock_fd"; then
|
||||
if [[ $rows_cacheable == true ]]; then
|
||||
rm -f "$cache_dir/$cache_key".*.tmp
|
||||
printf '%s' "$rows" >"$rows_cache_file.$$.tmp" && mv -f "$rows_cache_file.$$.tmp" "$rows_cache_file"
|
||||
printf '%s' "$rows_signature" >"$rows_signature_file.$$.tmp" && mv -f "$rows_signature_file.$$.tmp" "$rows_signature_file"
|
||||
printf '%s' "$rows_fast_signature" >"$rows_fast_signature_file.$$.tmp" && mv -f "$rows_fast_signature_file.$$.tmp" "$rows_fast_signature_file"
|
||||
else
|
||||
rm -f "$rows_cache_file" "$rows_signature_file" "$rows_fast_signature_file"
|
||||
fi
|
||||
exec {rows_lock_fd}>&-
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ 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" \
|
||||
omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-agent.hook"
|
||||
|
||||
run_first_run_step "enable user systemd units" \
|
||||
bash "$OMARCHY_PATH/install/user/first-run/enable-user-units.sh"
|
||||
|
||||
@@ -83,11 +83,16 @@ fi
|
||||
|
||||
# Dev-aware skill symlinks. Cannot live in /etc/skel because OMARCHY_PATH may
|
||||
# point at a dev checkout (omarchy dev link) where the target differs.
|
||||
# Loops every skill directory, so shipping a new one needs no edit here.
|
||||
mkdir -p ~/.agents/skills ~/.claude/skills ~/.codex/skills ~/.pi/agent/skills
|
||||
ln -sfn "$OMARCHY_PATH/default/agents/skills/omarchy" ~/.agents/skills/omarchy
|
||||
ln -sfn "$OMARCHY_PATH/default/agents/skills/omarchy" ~/.claude/skills/omarchy
|
||||
ln -sfn "$OMARCHY_PATH/default/agents/skills/omarchy" ~/.codex/skills/omarchy
|
||||
ln -sfn "$OMARCHY_PATH/default/agents/skills/omarchy" ~/.pi/agent/skills/omarchy
|
||||
for skill in "$OMARCHY_PATH"/default/agents/skills/*/; do
|
||||
skill=${skill%/}
|
||||
name=${skill##*/}
|
||||
ln -sfn "$skill" ~/.agents/skills/"$name"
|
||||
ln -sfn "$skill" ~/.claude/skills/"$name"
|
||||
ln -sfn "$skill" ~/.codex/skills/"$name"
|
||||
ln -sfn "$skill" ~/.pi/agent/skills/"$name"
|
||||
done
|
||||
|
||||
mkdir -p ~/Downloads ~/Pictures ~/Videos ~/.config/gtk-3.0
|
||||
xdg-user-dirs-update --set TEMPLATES "$HOME"
|
||||
|
||||
@@ -20,19 +20,15 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
|
||||
omarchy-pkg-drop \
|
||||
aether \
|
||||
cliamp \
|
||||
typora \
|
||||
spotify \
|
||||
libreoffice-fresh \
|
||||
1password \
|
||||
1password-cli \
|
||||
xournalpp \
|
||||
signal-desktop \
|
||||
pinta \
|
||||
obsidian \
|
||||
obs-studio \
|
||||
kdenlive \
|
||||
moonlight-qt \
|
||||
lazydocker \
|
||||
opencode \
|
||||
claude-code \
|
||||
github-cli
|
||||
omacut \
|
||||
omacalc \
|
||||
omawrite
|
||||
fi
|
||||
@@ -20,4 +20,7 @@ if pgrep -x "1password" >/dev/null && omarchy-cmd-present 1password; then
|
||||
fi
|
||||
|
||||
# Avoid running screensaver when locked
|
||||
pkill -x ttfx 2>/dev/null || true
|
||||
# ttfx handles SIGTERM asynchronously, so keep its terminal alive until it exits.
|
||||
timeout 1s pidwait -x ttfx 2>/dev/null || true
|
||||
pkill -f '[o]rg.omarchy.screensaver' 2>/dev/null || true
|
||||
@@ -6,7 +6,7 @@
|
||||
# omarchy:examples=omarchy theme install git@github.com:example/omarchy-example-theme.git
|
||||
|
||||
if [[ -z $1 ]]; then
|
||||
echo -e "\e[32mSee https://manuals.omamix.org/2/the-omarchy-manual/90/extra-themes\n\e[0m"
|
||||
echo -e "\e[32mSee https://omarchy.org/themes/\n\e[0m"
|
||||
REPO_URL=$(gum input --placeholder="Git repo URL (https or git@host:org/repo.git)" --header="")
|
||||
else
|
||||
REPO_URL="$1"
|
||||
|
||||
@@ -150,4 +150,4 @@ set_theme() {
|
||||
! omarchy-toggle-enabled skip-vscode-theme-changes && set_theme "code" "$HOME/.config/Code/User/settings.json" "$HOME/.vscode/extensions"
|
||||
! omarchy-toggle-enabled skip-vscode-insiders-theme-changes && set_theme "code-insiders" "$HOME/.config/Code - Insiders/User/settings.json" "$HOME/.vscode-insiders/extensions"
|
||||
! omarchy-toggle-enabled skip-codium-theme-changes && set_theme "codium" "$HOME/.config/VSCodium/User/settings.json" "$HOME/.vscode-oss/extensions"
|
||||
! omarchy-toggle-enabled skip-cursor-theme-changes && set_theme "cursor" "$HOME/.config/Cursor/User/settings.json" "$HOME/.cursor/extensions"
|
||||
! omarchy-toggle-enabled skip-cursor-theme-changes && set_theme "/usr/bin/cursor" "$HOME/.config/Cursor/User/settings.json" "$HOME/.cursor/extensions"
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Toggle crash capture notifications
|
||||
|
||||
# nf-md-robot_dead, escaped so this file reads without a Nerd Font.
|
||||
readonly CRASH_GLYPH=$'\U000f16a1'
|
||||
|
||||
omarchy-toggle crash-capture-off
|
||||
|
||||
# The unit checks the same flag with ConditionPathExists, so stopping and
|
||||
# starting only settles this session; the next login reads the flag itself.
|
||||
if omarchy-toggle-enabled crash-capture-off; then
|
||||
systemctl --user stop omarchy-crash-watch.service
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture disabled"
|
||||
else
|
||||
systemctl --user start omarchy-crash-watch.service
|
||||
omarchy-notification-send -g "$CRASH_GLYPH" "Crash capture enabled"
|
||||
fi
|
||||
@@ -22,7 +22,20 @@ CONF
|
||||
sudo systemctl enable --now supergfxd
|
||||
fi
|
||||
|
||||
gpu_mode=$(supergfxctl -g)
|
||||
gpu_mode=""
|
||||
for attempt in {1..3}; do
|
||||
if gpu_mode=$(timeout --kill-after=1s 3s supergfxctl -g 2>/dev/null) && [[ -n $gpu_mode ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
gpu_mode=""
|
||||
(( attempt < 3 )) && sleep 1
|
||||
done
|
||||
|
||||
if [[ -z $gpu_mode ]]; then
|
||||
echo "supergfxd is not responding. Try again, or check: systemctl status supergfxd" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$gpu_mode" in
|
||||
"Integrated")
|
||||
|
||||
@@ -21,7 +21,15 @@ trap 'omarchy-update-stay-awake stop' EXIT
|
||||
|
||||
omarchy-update-requires-free-space
|
||||
|
||||
# -y is a promise not to ask anything. Steps that would need an answer report
|
||||
# and move on instead of waiting on a prompt nobody is here to give.
|
||||
[[ ${1:-} != "-y" ]] || export OMARCHY_UPDATE_UNATTENDED=1
|
||||
|
||||
if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||
# Before the snapshot: the cache is on the snapshotted subvolume, so pruning
|
||||
# after it frees nothing until that snapshot ages out.
|
||||
omarchy-update-pkg-prune
|
||||
|
||||
# 127 means Snapper is deliberately absent. Any other failure already said
|
||||
# what went wrong, and a missing snapshot is not worth blocking an update
|
||||
# over, but it must not pass for one either.
|
||||
@@ -32,6 +40,10 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then
|
||||
|
||||
omarchy-update-dev
|
||||
omarchy-update-keyring
|
||||
|
||||
# Migrations ship with the packages installed here and are written against
|
||||
# them, so everything below waits on this finishing. An upgrade that stopped
|
||||
# takes the update with it rather than migrating against what is still on disk.
|
||||
omarchy-update-system-pkgs
|
||||
omarchy-migrate
|
||||
omarchy-hook post-update
|
||||
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Prune superseded versions from the pacman package cache
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
# The cache is the only offline downgrade path, so keep two. Run before the
|
||||
# packages update and the installed version survives with a spare. paccache
|
||||
# retains by version order, never by what is installed.
|
||||
echo -e "\e[32m\nPrune package cache\e[0m"
|
||||
sudo paccache -rk2 || echo -e "\e[33mCould not prune the package cache.\e[0m" >&2
|
||||
echo
|
||||
@@ -5,6 +5,14 @@
|
||||
|
||||
set -e
|
||||
|
||||
# The conflict handler's last resort: pacman puts its questions to the person
|
||||
# running the update instead of answering them itself. Nothing here may capture
|
||||
# a stream, because an upgrade without --noconfirm prompts on stderr. The
|
||||
# handler has already said why, so no heading here either.
|
||||
if [[ ${OMARCHY_UPDATE_CONFLICT:-} == 1 && ${OMARCHY_UPDATE_INTERACTIVE:-} == 1 ]]; then
|
||||
exec sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --overwrite '/usr/share/omarchy/*'
|
||||
fi
|
||||
|
||||
echo -e "\e[32m\nUpdate system packages\e[0m"
|
||||
|
||||
errors=$(mktemp)
|
||||
@@ -22,9 +30,9 @@ if sudo env LC_ALL=C OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm \
|
||||
fi
|
||||
cat "$errors" >&2
|
||||
|
||||
# An upgrade blocked only by files pacman doesn't own yet is the one failure
|
||||
# worth retrying: the handler clears them and runs this again. Anything else,
|
||||
# including a second failure, is for a human.
|
||||
# The handler takes it from here: it clears files pacman doesn't own yet and
|
||||
# runs this again, and puts a package conflict to whoever started the update.
|
||||
# Anything else, including a second failure, is for a human.
|
||||
[[ ${OMARCHY_UPDATE_RETRY:-} != 1 ]] || exit 1
|
||||
# exec, so the EXIT trap above does not fire and the handler can still read the
|
||||
# report. It takes over deleting it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Retry a system package update after resolving file conflicts
|
||||
# omarchy:summary=Retry a system package update that hit a conflict
|
||||
# omarchy:hidden=true
|
||||
|
||||
# Internal to omarchy-update-system-pkgs. Not a command to run by hand: it acts
|
||||
@@ -11,6 +11,10 @@
|
||||
# release's installer, an in-place edit -- belong to nobody, and pacman refuses
|
||||
# to install over a file it doesn't own. This clears them and runs the upgrade
|
||||
# again.
|
||||
#
|
||||
# Packages that conflict with each other are the other blocked upgrade, and that
|
||||
# one is a decision rather than a cleanup, so it goes back to the person
|
||||
# watching the update.
|
||||
|
||||
set -e
|
||||
|
||||
@@ -57,6 +61,26 @@ restore_moved() {
|
||||
trap 'restore_moved; rm -f "$errors"' EXIT
|
||||
trap 'exit 1' INT TERM
|
||||
|
||||
# Pacman answers its own conflict question with No under --noconfirm, so one
|
||||
# retired package can stop every upgrade after it. Which package to drop is a
|
||||
# decision -- an upstream split retires one, but so does a package the user
|
||||
# picked on purpose -- so put the question to whoever started the update rather
|
||||
# than guess at it. Pacman printed what conflicts just above.
|
||||
if grep -q 'unresolvable package conflicts detected' "$errors"; then
|
||||
# -y promised not to ask anything, and without a terminal there is nowhere to
|
||||
# ask; either way pacman would sit on a prompt nobody answers. An upgrade that
|
||||
# isn't running --noconfirm puts its questions on stderr and reads the answers
|
||||
# from stdin, so those are the two that have to be a terminal.
|
||||
if [[ ${OMARCHY_UPDATE_UNATTENDED:-} == 1 || ! -t 0 || ! -t 2 ]]; then
|
||||
echo -e "\e[33m\nThis upgrade needs an answer. Run omarchy update interactively to give it.\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "\e[33m\nA package conflict stopped this upgrade. Running it again so you can answer:\e[0m"
|
||||
OMARCHY_UPDATE_INTERACTIVE=1 omarchy-update-system-pkgs
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Paths one of these packages installs that pacman doesn't own. Moving them out
|
||||
# of the way is what lets the upgrade through, and works on a leftover directory
|
||||
# as well as a file.
|
||||
|
||||
@@ -1792,82 +1792,6 @@ CHROMIUM_FLAGS_PATCH_PY
|
||||
done
|
||||
}
|
||||
|
||||
# True if any Chromium-family browser that stores the Copy URL shortcut is running.
|
||||
browser_is_running() {
|
||||
pgrep -x 'chromium|chrome|brave|msedge|vivaldi-bin|vivaldi|opera|helium' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
repair_chromium_copy_url_shortcuts() {
|
||||
local profile_root preferences
|
||||
local -a profile_roots=(
|
||||
"$HOME/.config/chromium"
|
||||
"$HOME/.config/google-chrome"
|
||||
"$HOME/.config/google-chrome-beta"
|
||||
"$HOME/.config/BraveSoftware/Brave-Browser"
|
||||
"$HOME/.config/BraveSoftware/Brave-Browser-Beta"
|
||||
"$HOME/.config/microsoft-edge"
|
||||
"$HOME/.config/microsoft-edge-beta"
|
||||
"$HOME/.config/vivaldi"
|
||||
"$HOME/.config/opera"
|
||||
"$HOME/.config/helium"
|
||||
)
|
||||
local -a pending=()
|
||||
|
||||
for profile_root in "${profile_roots[@]}"; do
|
||||
[[ -d $profile_root ]] || continue
|
||||
|
||||
for preferences in "$profile_root"/*/Preferences; do
|
||||
[[ -f $preferences ]] || continue
|
||||
grep -qF 'bocglpkldciamkbmlphanhkfnhpmnbma' "$preferences" || continue
|
||||
pending+=("$preferences")
|
||||
done
|
||||
done
|
||||
|
||||
(( ${#pending[@]} )) || return 0
|
||||
|
||||
# A running browser holds Preferences in memory and rewrites it on exit,
|
||||
# which would revert this repair. Ask the user to close it first.
|
||||
if browser_is_running && (( ! OMARCHY_UPGRADE_YES )) && [[ -r /dev/tty ]]; then
|
||||
if ! gum confirm "Close all browser windows before the Copy URL shortcut is repaired, then continue" </dev/tty; then
|
||||
echo "Skipped Copy URL shortcut repair; re-run after closing your browser." >&2
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
for preferences in "${pending[@]}"; do
|
||||
python3 - "$preferences" "$preferences.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak" <<'CHROMIUM_SHORTCUTS_PATCH_PY'
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(sys.argv[1])
|
||||
backup_path = Path(sys.argv[2])
|
||||
old_id = "bocglpkldciamkbmlphanhkfnhpmnbma"
|
||||
new_id = "bgpiichlckmfanooecilcjemknkcpngb"
|
||||
preferences = json.loads(path.read_text())
|
||||
extensions = preferences.get("extensions", {})
|
||||
commands = extensions.get("commands", {})
|
||||
changed = False
|
||||
|
||||
for command in commands.values():
|
||||
if command.get("extension") == old_id and command.get("command_name") == "copy-url":
|
||||
command["extension"] = new_id
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
settings = extensions.get("settings", {})
|
||||
old_command = settings.get(old_id, {}).get("commands", {}).get("copy-url", {})
|
||||
new_command = settings.get(new_id, {}).get("commands", {}).get("copy-url", {})
|
||||
old_command.pop("was_assigned", None)
|
||||
if new_command:
|
||||
new_command["was_assigned"] = True
|
||||
shutil.copy2(path, backup_path)
|
||||
path.write_text(json.dumps(preferences, separators=(",", ":")))
|
||||
CHROMIUM_SHORTCUTS_PATCH_PY
|
||||
done
|
||||
}
|
||||
|
||||
repair_sleep_lock_unit_override() {
|
||||
local unit="$HOME/.config/systemd/user/omarchy-sleep-lock.service"
|
||||
local legacy_exec='ExecStart=%h/.local/share/omarchy/bin/omarchy-system-sleep-monitor'
|
||||
@@ -1964,7 +1888,6 @@ copy_missing_config_defaults "$root/config" "$HOME/.config"
|
||||
mark_removed_preinstalls_from_legacy_bindings
|
||||
copy_always_config_defaults
|
||||
repair_chromium_copy_url_extension_flags
|
||||
repair_chromium_copy_url_shortcuts
|
||||
|
||||
# Carry over user-added backgrounds from the legacy git checkout backup, then
|
||||
# remove old symlinks that pointed ~/.config/omarchy/themes/* back into the
|
||||
|
||||
@@ -5,8 +5,8 @@ profile {
|
||||
identity = true
|
||||
}
|
||||
|
||||
# To enable auto switch to nightlight, set in your .config/hypr/autostart:
|
||||
# exec-once = uwsm app -- hyprsunset
|
||||
# To enable auto switch to nightlight, add to your .config/hypr/autostart.lua:
|
||||
# o.launch_on_start("hyprsunset")
|
||||
# and use the following:
|
||||
# profile {
|
||||
# time = 20:00
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: diagnose-crash
|
||||
description: >
|
||||
Diagnose why a program crashed on this machine, from a systemd-coredump core dump.
|
||||
Use when a process has segfaulted, aborted, or otherwise dumped core, when asked
|
||||
why an application crashed or disappeared, or when a "Process crashed:" desktop
|
||||
notification is acted on. Triggers: crash, segfault, SIGSEGV, SIGABRT, core dump,
|
||||
coredumpctl, "why did X crash", "X keeps crashing", backtrace symbolization.
|
||||
Covers reporting a confirmed Omarchy bug upstream — see reporting.md.
|
||||
---
|
||||
|
||||
# Diagnosing a Crash
|
||||
|
||||
Work from evidence. The goal is an honest account of what happened, not a
|
||||
plausible-sounding story.
|
||||
|
||||
## Establish the facts
|
||||
|
||||
`coredumpctl info <pid>` is the starting point. Beyond the backtrace, note the
|
||||
**command line** the process was started with — it usually reveals what the
|
||||
program was working on when it died, which is often the whole answer.
|
||||
|
||||
`coredumpctl list` shows whether this crash is a one-off or a pattern. Repeated
|
||||
crashes of the same program, or several programs dying together, point somewhere
|
||||
different than a single failure does.
|
||||
|
||||
## Rule out the boring causes first
|
||||
|
||||
Check resource exhaustion before blaming the program: `free -h`, and the journal
|
||||
for OOM kills. A process killed by the OOM killer is not a bug in that process.
|
||||
|
||||
## Correlate against the timeline
|
||||
|
||||
The crash timestamp is the most underused piece of evidence. Compare it against:
|
||||
|
||||
- **Filesystem mtimes.** A directory or file whose mtime lands on the same second
|
||||
as the crash strongly suggests what triggered it.
|
||||
- **The journal** around that moment, for related warnings from the same or
|
||||
neighbouring processes.
|
||||
- **Recent package updates.** A crash that starts right after an update points at
|
||||
the update.
|
||||
|
||||
## Read the whole core, not just frame 0
|
||||
|
||||
Thread stacks other than the crashing one show what work was **in flight** —
|
||||
thumbnailers, image loaders, IPC readers, GPU queues. That context often explains
|
||||
the trigger even when the crashing frame itself cannot be symbolized.
|
||||
|
||||
Note any third-party code in the address space: file-manager or browser
|
||||
extensions, plugins, out-of-tree drivers. In-process third-party code is a common
|
||||
crash source and worth flagging — but do not pin blame on it without evidence
|
||||
that it is actually implicated.
|
||||
|
||||
## Symbolize when you can
|
||||
|
||||
This is Arch, which runs a public debuginfod server:
|
||||
|
||||
```bash
|
||||
core=$(mktemp -t crash-XXXXXX.core)
|
||||
trap 'rm -f "$core"' EXIT
|
||||
coredumpctl dump <pid> --output="$core"
|
||||
DEBUGINFOD_URLS="https://debuginfod.archlinux.org" \
|
||||
gdb -q <executable> "$core" \
|
||||
-batch -ex 'set debuginfod enabled on' -ex 'bt'
|
||||
```
|
||||
|
||||
A core is a verbatim copy of the process's memory and can hold passwords, tokens,
|
||||
and private documents. Write it to a fresh `mktemp` path rather than a predictable
|
||||
shared one, and delete it when you are done — never leave it lying in `/tmp`.
|
||||
|
||||
Many packages publish no debug symbols. When frames stay unresolved, say so —
|
||||
never invent function names to fill the gap. An unsymbolized stack still has
|
||||
shape: which library each frame belongs to, and whether the crash came from a
|
||||
signal handler, a main loop, or a worker thread.
|
||||
|
||||
## Report
|
||||
|
||||
1. What crashed, and what it was doing at the time.
|
||||
2. The most likely mechanism — separating clearly what the evidence **proves**
|
||||
from what you are **inferring**.
|
||||
3. Whether any user data was lost, and where it can be recovered from. Check the
|
||||
trash before concluding anything is gone.
|
||||
4. Whether it is likely to recur, and what would avoid or fix it.
|
||||
|
||||
Be straight about the limits of the evidence. If the cause is genuinely
|
||||
ambiguous, say so rather than assembling confidence out of guesswork.
|
||||
|
||||
**Leave the system as you found it.** Diagnosis reads; it does not fix, tidy, or
|
||||
reconfigure. The one thing to clean up is your own: delete the core you extracted
|
||||
above, which is a copy of the crashed process's memory.
|
||||
|
||||
## If it is an Omarchy bug
|
||||
|
||||
Most application crashes are upstream bugs in those applications, not Omarchy's
|
||||
doing. In the minority of cases where the cause really does sit within Omarchy's
|
||||
sphere of control, read [`reporting.md`](reporting.md) before offering to file
|
||||
anything.
|
||||
@@ -0,0 +1,104 @@
|
||||
# Reporting a Crash Upstream to Omarchy
|
||||
|
||||
Read this only after concluding that a crash is genuinely Omarchy's to fix.
|
||||
|
||||
## Is it even Omarchy's bug?
|
||||
|
||||
Be strict here. Omarchy is a configuration layer over Arch Linux, so a crash
|
||||
inside a third-party application — a file manager, a browser, a GNOME or Qt
|
||||
library — is almost always an upstream bug in **that** project, not in Omarchy.
|
||||
|
||||
Omarchy's sphere of control is roughly:
|
||||
|
||||
- the `omarchy-*` commands
|
||||
- the Quickshell shell and its plugins
|
||||
- the Hyprland and terminal configuration it ships
|
||||
- its themes
|
||||
- its install and migration scripts
|
||||
- how it packages and configures what it installs
|
||||
|
||||
A crash in a program Omarchy merely installs is **not** an Omarchy bug unless
|
||||
Omarchy's own packaging or configuration is implicated.
|
||||
|
||||
If it is not Omarchy's, say so and stop. Suggesting the right upstream project is
|
||||
useful; filing there yourself is not part of this.
|
||||
|
||||
## Three conditions, all required
|
||||
|
||||
1. **It is a verified bug in Omarchy's sphere**, established on evidence. Issues
|
||||
are for verified bugs only. An "is this even a bug?" belongs on the Discord at
|
||||
<https://omarchy.org/discord>; a feature idea belongs in GitHub Discussions
|
||||
under Suggestions.
|
||||
2. **The user has explicitly agreed.** Show them the exact title and body you
|
||||
propose, and wait for a yes. Never file unprompted.
|
||||
3. **The machine can file it** — `gh auth status` must succeed. If `gh` is missing
|
||||
or unauthenticated, do not install or authenticate it. Say so, and hand the
|
||||
user the finished text to submit themselves.
|
||||
|
||||
## Search before filing
|
||||
|
||||
A duplicate issue costs a maintainer more time than no report at all.
|
||||
|
||||
```bash
|
||||
gh search issues --repo basecamp/omarchy "<program> crash"
|
||||
gh issue list --repo basecamp/omarchy --state all --search "<signal> <program>"
|
||||
```
|
||||
|
||||
Search on the crashing program, the signal, and distinctive symbols from the
|
||||
backtrace — not on the wording of the title you were about to write.
|
||||
|
||||
`gh search issues` accepts only `open` or `closed` for `--state`, and errors on
|
||||
anything else. Leaving it off searches both, which is what you want here.
|
||||
|
||||
Include **closed** issues. A matching issue closed as fixed, when the crash still
|
||||
reproduces on a current system, is a regression — and reporting that is worth far
|
||||
more than another duplicate.
|
||||
|
||||
## Adding to an existing report
|
||||
|
||||
If a plausible match comes back, read it properly first:
|
||||
|
||||
```bash
|
||||
gh issue view <number> --repo basecamp/omarchy --comments
|
||||
```
|
||||
|
||||
Confirm it is genuinely the same failure. The same program crashing is not the
|
||||
same bug if the trigger or the stack differs.
|
||||
|
||||
If it is the same, add to that issue rather than opening a new one — but only
|
||||
when you have something the thread does not already contain: a different
|
||||
reproduction, a symbolized stack where it has none, a narrower trigger, a version
|
||||
where it regressed.
|
||||
|
||||
A comment that only says the bug happens to you too is noise. If that is all you
|
||||
have, tell the user so and file nothing.
|
||||
|
||||
```bash
|
||||
gh issue comment <number> --repo basecamp/omarchy --body "..."
|
||||
```
|
||||
|
||||
## Filing a new issue
|
||||
|
||||
Only when the search turns up nothing that matches:
|
||||
|
||||
```bash
|
||||
gh issue create --repo basecamp/omarchy --title "..." --body "..."
|
||||
```
|
||||
|
||||
Include what happened, what was expected, steps to reproduce, system details from
|
||||
`omarchy version`, and diagnostics from `omarchy debug --no-sudo --print` (which
|
||||
also writes `/tmp/omarchy-debug.log`; the interactive `omarchy debug` can upload
|
||||
it and print a shareable URL worth including).
|
||||
|
||||
`gh` cannot attach media. If a screenshot would help, save one and give the user
|
||||
the path to drag into the web form.
|
||||
|
||||
## Signing
|
||||
|
||||
End the issue or comment with a line naming the model and agent harness that
|
||||
produced it, so a human reader knows it was machine-authored:
|
||||
|
||||
> Filed by \<model name\> via \<agent harness\>.
|
||||
|
||||
Use your actual model and harness names. If you are not certain of them, say so
|
||||
plainly rather than inventing a version string.
|
||||
@@ -43,7 +43,7 @@ alias ...='cd ../..'
|
||||
alias ....='cd ../../..'
|
||||
|
||||
# Tools
|
||||
alias a='omarchy-launch-agent --inline'
|
||||
alias a='omarchy-agent --inline'
|
||||
alias c='opencode --auto'
|
||||
alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode bypassPermissions'
|
||||
alias cy='codex -s danger-full-access -a never'
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
"manifest_version": 3,
|
||||
"name": "WhatsApp Slim",
|
||||
"version": "1.1",
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAszLhFYxjvKqx1VAORt3M6b0H1Kz03cmQ1MrUjJZ8gj3nhRcvufk6PS17yUIjEdKKmuj+1HuFVwOaHJQsACGJXwn2TeKQqZmZx/83yd7ydgabHXTsJbhy5O+Yunv+VA1B2IX/phcXpkH9bPv2PuC/bQ13nmmAvT2b+EMyq8EPQHOoSWhE4M4kIhLDczTmxfJX5X3tTxH66952DYuQWvGpIXLAUXkvIgap0qemW68/LymPaWzcg6g3oE/nZ7C0j7JE5bnqLFxqXNlA9Jt+Ss/1Y33QL3OSD9EEsrpgueS0bbqtkmFQ9/jGlruIL+qj0xe+CAtqbJ5ty9H/sqi01kjfyQIDAQAB",
|
||||
"description": "Make WhatsApp Web follow the system theme and collapse its chat list in narrow windows, this extension is installed by Omarchy",
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://web.whatsapp.com/*"],
|
||||
"css": ["whatsapp.css"],
|
||||
"js": ["system-theme.js"],
|
||||
"matches": [
|
||||
"https://web.whatsapp.com/*"
|
||||
],
|
||||
"css": [
|
||||
"whatsapp.css"
|
||||
],
|
||||
"js": [
|
||||
"system-theme.js"
|
||||
],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,6 +8,8 @@ The private-use glyphs in `omarchy.ttf` are:
|
||||
- `U+E903` — omp, from <https://omp.sh/favicon.svg>
|
||||
- `U+E904` — Grok, from <https://grok.com/images/favicon.svg>
|
||||
- `U+E905` — Codex, from the OpenAI mark on <https://developers.openai.com/codex/>
|
||||
- `U+E906` — LM Studio, from <https://simpleicons.org/icons/lmstudio.svg>
|
||||
- `U+E907` — Ollama, from <https://simpleicons.org/icons/ollama.svg>
|
||||
|
||||
The agent marks are monochrome so the menu can render them using the active
|
||||
theme's foreground and selection colors.
|
||||
Binary file not shown.
@@ -20,8 +20,9 @@ o.window({
|
||||
}, { tag = "+floating-window" })
|
||||
|
||||
-- The About fastfetch layout needs more columns than the standard float provides.
|
||||
-- This size only positions the first paint: omarchy-launch-about then measures
|
||||
-- the rendered content and fits the window to it exactly.
|
||||
-- This size only covers the first launch: omarchy-launch-about measures the
|
||||
-- rendered content, remembers the size that hugs it, and applies that as its own
|
||||
-- rule before every later launch.
|
||||
o.window("org.omarchy.about", { float = true })
|
||||
o.window("org.omarchy.about", { center = true })
|
||||
o.window("org.omarchy.about", { size = { 920, 480 } })
|
||||
|
||||
@@ -93,7 +93,7 @@ o.bind("SUPER + CTRL + ALT + T", "Show time", "omarchy-notification-time")
|
||||
o.bind("SUPER + CTRL + ALT + B", "Show battery remaining", "omarchy-notification-battery")
|
||||
o.bind("SUPER + CTRL + ALT + W", "Toggle weather", "omarchy-notification-weather")
|
||||
|
||||
o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-launch-agent")
|
||||
o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-agent --pick")
|
||||
o.bind("SUPER + CTRL + A", "Audio", "omarchy-shell shell toggle omarchy.audio")
|
||||
o.bind("SUPER + CTRL + B", "Bluetooth", "omarchy-shell shell toggle omarchy.bluetooth")
|
||||
o.bind("SUPER + CTRL + D", "Display", "omarchy-shell shell toggle omarchy.monitor")
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
// Learn
|
||||
"learn.keybindings": {"icon":"","label":"Keybindings","action":"omarchy-menu-keybindings"},
|
||||
"learn.omarchy": {"icon":"","label":"Omarchy","action":"omarchy-launch-webapp 'https://learn.omacom.io/2/the-omarchy-manual'"},
|
||||
"learn.omarchy": {"icon":"","label":"Omarchy","action":"omarchy-launch-webapp 'https://omarchy.org/manual/'"},
|
||||
"learn.hyprland": {"icon":"","label":"Hyprland","action":"omarchy-launch-webapp 'https://wiki.hypr.land/'"},
|
||||
"learn.arch": {"icon":"","label":"Arch","action":"omarchy-launch-webapp 'https://wiki.archlinux.org/title/Main_page'"},
|
||||
"learn.neovim": {"icon":"","label":"Neovim","action":"omarchy-launch-webapp 'https://www.lazyvim.org/keymaps'"},
|
||||
@@ -84,6 +84,7 @@
|
||||
"trigger.share.receive": {"icon":"","label":"Receive","action":"uwsm-app -- localsend"},
|
||||
"trigger.toggle.idle-lock": {"icon":"","label":"Stay Awake","action":"omarchy-toggle-idle"},
|
||||
"trigger.toggle.notifications": {"icon":"","label":"Notifications","action":"omarchy-toggle-notification-silencing"},
|
||||
"trigger.toggle.crash-capture": {"icon":"","label":"Crash Capture","action":"omarchy-toggle-crash-capture"},
|
||||
"trigger.toggle.screensaver": {"icon":"","label":"Screensaver","action":"omarchy-toggle-screensaver"},
|
||||
"trigger.toggle.nightlight": {"icon":"","label":"Nightlight","action":"omarchy-toggle-nightlight"},
|
||||
"trigger.toggle.top-bar": {"icon":"","label":"Menu Bar","action":"omarchy-toggle-bar"},
|
||||
@@ -128,7 +129,7 @@
|
||||
"setup.network.dns.custom": {"icon":"","label":"Custom","checked":"[[ \"$(omarchy-dns)\" == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-dns Custom'"},
|
||||
"setup.network.qr": {"icon":"","label":"QR Code","aliases":["wifi-qr"],"when":"[[ $(omarchy-network-status) == wifi* ]]","action":"omarchy-shell shell summon omarchy.wifiqr"},
|
||||
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
|
||||
"setup.default.agent": {"icon":"","label":"Agent"},
|
||||
"setup.default.agent": {"icon":"","label":"Agent","title":"Default Agent"},
|
||||
"setup.default.agent.claude": {"icon":"","label":"Claude","checked":"[[ \"$(omarchy-default-agent)\" == \"claude\" ]]","action":"omarchy-default-agent claude"},
|
||||
"setup.default.agent.codex": {"icon":"","iconFont":"omarchy","label":"Codex","checked":"[[ \"$(omarchy-default-agent)\" == \"codex\" ]]","action":"omarchy-default-agent codex"},
|
||||
"setup.default.agent.copilot": {"icon":"","label":"Copilot","checked":"[[ \"$(omarchy-default-agent)\" == \"copilot\" ]]","action":"omarchy-default-agent copilot"},
|
||||
@@ -138,7 +139,7 @@
|
||||
"setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"},
|
||||
"setup.default.agent.opencode": {"icon":"","iconFont":"omarchy","label":"OpenCode","checked":"[[ \"$(omarchy-default-agent)\" == \"opencode\" ]]","action":"omarchy-default-agent opencode"},
|
||||
"setup.default.agent.pi": {"icon":"","iconFont":"omarchy","label":"Pi","checked":"[[ \"$(omarchy-default-agent)\" == \"pi\" ]]","action":"omarchy-default-agent pi"},
|
||||
"setup.default.browser": {"icon":"","label":"Browser"},
|
||||
"setup.default.browser": {"icon":"","label":"Browser","title":"Default Browser"},
|
||||
"setup.default.browser.chromium": {"icon":"","label":"Chromium","when":"omarchy-cmd-present chromium","checked":"[[ \"$(omarchy-default-browser)\" == \"chromium\" ]]","action":"omarchy-default-browser chromium"},
|
||||
"setup.default.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-cmd-present google-chrome-stable","checked":"[[ \"$(omarchy-default-browser)\" == \"chrome\" ]]","action":"omarchy-default-browser chrome"},
|
||||
"setup.default.browser.brave": {"icon":"","label":"Brave","when":"omarchy-cmd-present brave","checked":"[[ \"$(omarchy-default-browser)\" == \"brave\" ]]","action":"omarchy-default-browser brave"},
|
||||
@@ -146,12 +147,12 @@
|
||||
"setup.default.browser.edge": {"icon":"","label":"Edge","when":"omarchy-cmd-present microsoft-edge-stable","checked":"[[ \"$(omarchy-default-browser)\" == \"edge\" ]]","action":"omarchy-default-browser edge"},
|
||||
"setup.default.browser.firefox": {"icon":"","label":"Firefox","when":"omarchy-cmd-present firefox","checked":"[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]","action":"omarchy-default-browser firefox"},
|
||||
"setup.default.browser.zen": {"icon":"","label":"Zen","when":"omarchy-cmd-present zen-browser","checked":"[[ \"$(omarchy-default-browser)\" == \"zen\" ]]","action":"omarchy-default-browser zen"},
|
||||
"setup.default.terminal": {"icon":"","label":"Terminal"},
|
||||
"setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"},
|
||||
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","when":"omarchy-cmd-present alacritty","checked":"[[ \"$(omarchy-default-terminal)\" == \"alacritty\" ]]","action":"omarchy-default-terminal alacritty"},
|
||||
"setup.default.terminal.foot": {"icon":"","label":"Foot","when":"omarchy-cmd-present foot","checked":"[[ \"$(omarchy-default-terminal)\" == \"foot\" ]]","action":"omarchy-default-terminal foot"},
|
||||
"setup.default.terminal.ghostty": {"icon":"","label":"Ghostty","when":"omarchy-cmd-present ghostty","checked":"[[ \"$(omarchy-default-terminal)\" == \"ghostty\" ]]","action":"omarchy-default-terminal ghostty"},
|
||||
"setup.default.terminal.kitty": {"icon":"","label":"Kitty","when":"omarchy-cmd-present kitty","checked":"[[ \"$(omarchy-default-terminal)\" == \"kitty\" ]]","action":"omarchy-default-terminal kitty"},
|
||||
"setup.default.editor": {"icon":"","label":"Editor"},
|
||||
"setup.default.editor": {"icon":"","label":"Editor","title":"Default Editor"},
|
||||
"setup.default.editor.neovim": {"icon":"","label":"Neovim","when":"omarchy-cmd-present nvim","checked":"[[ \"$(omarchy-default-editor)\" == \"nvim\" ]]","action":"omarchy-default-editor nvim"},
|
||||
"setup.default.editor.vscode": {"icon":"","label":"VSCode","when":"omarchy-cmd-present code","checked":"[[ \"$(omarchy-default-editor)\" == \"code\" ]]","action":"omarchy-default-editor code"},
|
||||
"setup.default.editor.cursor": {"icon":"","label":"Cursor","when":"omarchy-cmd-present cursor","checked":"[[ \"$(omarchy-default-editor)\" == \"cursor\" ]]","action":"omarchy-default-editor cursor"},
|
||||
@@ -201,6 +202,7 @@
|
||||
"install.ai": {"icon":"","label":"AI"},
|
||||
"install.gaming": {"icon":"","label":"Gaming"},
|
||||
"install.windows": {"icon":"","label":"Windows","when":"[[ ! -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
||||
"install.preinstalls": {"icon":"","label":"Preinstalls","when":"[[ -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
||||
"install.browser.chrome": {"icon":"","label":"Chrome","when":"! omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
||||
"install.browser.edge": {"icon":"","label":"Edge","when":"! omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
||||
"install.browser.brave": {"icon":"","label":"Brave","when":"! omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave'"},
|
||||
@@ -227,9 +229,11 @@
|
||||
"install.terminal.foot": {"icon":"","label":"Foot","when":"! omarchy-pkg-present foot","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal foot'"},
|
||||
"install.terminal.ghostty": {"icon":"","label":"Ghostty","when":"! omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
|
||||
"install.terminal.kitty": {"icon":"","label":"Kitty","when":"! omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
||||
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"! omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
||||
"install.ai.dictation": {"icon":"","label":"Dictation","when":"! omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||
"install.ai.lm-studio": {"icon":"","label":"LM Studio","when":"! omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
"install.ai.ollama": {"icon":"","label":"Ollama","when":"! omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"! omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"! omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"! omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
||||
"install.gaming.steam": {"icon":"","label":"Steam","when":"! omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-steam"},
|
||||
"install.gaming.retroarch": {"icon":"","label":"RetroArch","when":"! omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-retroarch"},
|
||||
"install.gaming.minecraft": {"icon":"","label":"Minecraft","when":"! omarchy-pkg-present minecraft-launcher","action":"omarchy-install-and-launch Minecraft minecraft-launcher minecraft-launcher"},
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Announce process crashes and offer an AI diagnosis
|
||||
# Needs the session bus to notify, and uwsm-app to open the diagnosis terminal.
|
||||
# Both are up only after graphical-session.target.
|
||||
After=graphical-session.target
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
# Set by omarchy-toggle-crash-capture. Checked here so a disabled watcher stays
|
||||
# disabled across logins without the unit having to be disabled.
|
||||
ConditionPathExists=!%h/.local/state/omarchy/toggles/crash-capture-off
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/omarchy-crash-watch
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -1,5 +1,45 @@
|
||||
HOOKS=(base udev plymouth keyboard autodetect microcode modconf kms keymap consolefont block encrypt filesystems fsck btrfs-overlayfs)
|
||||
|
||||
# The proprietary NVIDIA driver does early KMS itself: nvidia.conf (written by
|
||||
# install/hardware/nvidia.sh, sourced before this file) early-loads nvidia_drm
|
||||
# with modeset=1. Keeping the kms hook on such a system makes autodetect pull
|
||||
# in nouveau — and ~100 MB of its GSP firmware — for a driver that never runs.
|
||||
# Drop kms only when nvidia_drm is early-loaded and NVIDIA owns every display
|
||||
# controller. Hybrid systems keep kms: their iGPU still needs it for early
|
||||
# KMS at the LUKS prompt. So does anything unexpected, like a PCI tree that
|
||||
# cannot be read.
|
||||
#
|
||||
# This reads MODULES midway through mkinitcpio's drop-in sourcing, so a
|
||||
# later-sorting drop-in that resets MODULES outright — surface_device_modules.conf
|
||||
# does — would strip nvidia_drm after kms was already dropped. Every machine
|
||||
# Omarchy writes such a file for carries an Intel iGPU, which keeps kms here
|
||||
# through the scan below; keep it that way.
|
||||
if [[ " ${MODULES[*]:-} " == *" nvidia_drm "* ]]; then
|
||||
_omarchy_nvidia_gpu=0
|
||||
_omarchy_other_gpu=0
|
||||
for _omarchy_pci in "${OMARCHY_PCI_DEVICES_PATH:-/sys/bus/pci/devices}"/*; do
|
||||
if [[ ! -r $_omarchy_pci/class || ! -r $_omarchy_pci/vendor ]]; then
|
||||
# An unreadable device could be another GPU. Inconclusive keeps kms.
|
||||
_omarchy_other_gpu=1
|
||||
continue
|
||||
fi
|
||||
[[ $(<"$_omarchy_pci/class") == "0x03"* ]] || continue
|
||||
if [[ $(<"$_omarchy_pci/vendor") == "0x10de" ]]; then
|
||||
_omarchy_nvidia_gpu=1
|
||||
else
|
||||
_omarchy_other_gpu=1
|
||||
fi
|
||||
done
|
||||
if ((_omarchy_nvidia_gpu && !_omarchy_other_gpu)); then
|
||||
_omarchy_hooks=()
|
||||
for _omarchy_hook in "${HOOKS[@]}"; do
|
||||
[[ $_omarchy_hook == "kms" ]] || _omarchy_hooks+=("$_omarchy_hook")
|
||||
done
|
||||
HOOKS=("${_omarchy_hooks[@]}")
|
||||
fi
|
||||
unset _omarchy_nvidia_gpu _omarchy_other_gpu _omarchy_pci _omarchy_hooks _omarchy_hook
|
||||
fi
|
||||
|
||||
# Bundle vconsole.conf so Plymouth uses the configured keyboard layout at the
|
||||
# LUKS prompt, but only when that layout types Latin letters. Passphrases are
|
||||
# Latin characters, so bundling a Hebrew/Greek/Cyrillic/Arabic layout would
|
||||
|
||||
@@ -17,4 +17,5 @@ systemctl --user enable --now \
|
||||
omarchy-recover-internal-monitor.service \
|
||||
omarchy-sleep-lock.service \
|
||||
omarchy-migrate-notify.service \
|
||||
omarchy-fcitx5.service
|
||||
omarchy-fcitx5.service \
|
||||
omarchy-crash-watch.service
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Omarchy ships no default agent, so invite once rather than picking one. An
|
||||
# agent already chosen means the invitation has nothing to offer.
|
||||
if [[ -z $(omarchy-default-agent) ]] && omarchy-done ensure agent-setup-invitation; then
|
||||
omarchy-notification-send -u critical -g "Set your default agent" \
|
||||
"Let your favorite agent help with Omarchy." \
|
||||
--exec "omarchy menu summon setup.default.agent"
|
||||
fi
|
||||
@@ -0,0 +1,11 @@
|
||||
# Welcome to Omarchy!
|
||||
|
||||
Omarchy is an [omakase](https://manuals.omamix.org/3/omacom/76/omakase-computing) Linux distribution based on [Arch](https://archlinux.org/), the tiling window manager [Hyprland](https://hypr.land/), and the desktop construction-kit [Quickshell](https://quickshell.org/). It ships with everything a modern, savvy computer user needs to be productive immediately. From [Neovim](https://neovim.io/) (btw) to Chromium, [Obsidian](https://obsidian.md/) to LibreOffice, and Kdenlive to OBS Studio. Hell, even a retro Winamp-style music player is there!
|
||||
|
||||
This isn't just a grab bag of preinstalled packages, though. It's a complete system designed with both aesthetics and productivity in mind. Because a _beautiful_ system is a _motivating_ system, and productivity has always been [downstream from motivation](https://world.hey.com/dhh/beautiful-motivations-6fef7c73). There's zero bloat here: Just everything I use.
|
||||
|
||||
It's true that developing an eye for the beauty of a TUI-heavy, theme-delighted, tiling-window-managed system like Omarchy can be an acquired taste. But that's why you're here, isn't it? To experience something a little outside of your comfort zone? To embark on a little bit of an adventure into a new way of working with computers? I hope so.
|
||||
|
||||
Omarchy isn't like Windows and it's not like macOS either. It's not trying to be as familiar as possible. It's trying to be beautiful and _better_. Embrace the Linux-ness of it all. Manually editing some config files, sure. Heavy on the terminal, definitely.
|
||||
|
||||
Let's get started with the basics.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Getting Started
|
||||
|
||||
Omarchy is installed using an ISO. You can choose between a full-disk install, which takes over the entire drive, or a free-space install, which puts Omarchy in the unallocated space on a drive — that's how you dual boot alongside Windows or another OS (see [dual-boot install](50-dual-boot-install.md) — note that you'll need to turn off BitLocker in Windows first). Either way, the installation defaults to full encryption, and the full-disk option will wipe the selected drive, so be sure to take a backup before using an existing one!
|
||||
|
||||
[Download the Omarchy ISO](https://omarchy.org/) first, put it on a USB stick (use [balenaEtcher](https://etcher.balena.io/) on Mac/Windows or [caligula](https://github.com/ifd3f/caligula) on Linux), and boot off the stick.
|
||||
|
||||
_You must turn off Secure Boot and/or TPM in the BIOS. You have to turn these off to be able to install Omarchy. They're Microsoft security schemes meant for Windows and Microsoft-affiliated Linux distributions._
|
||||
|
||||
Then answer the configuration questions, and confirm them like this:
|
||||
|
||||

|
||||
|
||||
Then select a drive for your installation, and sit back and watch the installation show go. It can be done in under a minute on the fastest modern machines, but it shouldn't take more than 5 minutes even on an older computer.
|
||||
|
||||

|
||||
|
||||
Now you're ready to Omarchy!
|
||||
|
||||
### Use a wired or 2.4ghz keyboard!
|
||||
|
||||
The full-disk encryption won't allow you to enter the password from a Bluetooth keyboard at startup. Just like you can't use a Bluetooth keyboard to enter the BIOS on a PC. You'll need a keyboard that either uses a 2.4ghz dongle or a cable (which is much nicer for latency anyway!). I personally love the [Lofree Flow84](https://www.lofree.co/products/lofree-flow-the-smoothest-mechanical-keyboard)!
|
||||
|
||||
### Installing for another owner
|
||||
|
||||
If you're setting up a machine for someone else — a family member, a new employee, a buyer — you shouldn't be answering the personal questions on their behalf. Hit `Ctrl + C` on the very first screen of the installer (the keyboard selection), and Omarchy will offer to prepare the machine for another owner instead. The system installs right away, but all the personal setup — keyboard layout, username, password — is deferred until the machine boots for the first time. The drive is still encrypted by default, and the password the new owner picks on that first boot becomes the encryption password too. (A machine you've already been using can be handed over without a reinstall too — see [resetting the computer](48-security.md).)
|
||||
|
||||
### Unattended installs
|
||||
|
||||
The ISO can also install completely on its own — no keyboard, no wizard — when it's handed its configuration on a second drive. That's the way to treat Omarchy as a base image for VMs and fleet machines. See [unattended installs](51-unattended-installs.md).
|
||||
|
||||
### No-encryption installations
|
||||
|
||||
Omarchy is installed with encryption by default. It's the safe, responsible choice for any computer that can possibly be lost or stolen. You don't want anyone with access to your hardware to be able to get your data!
|
||||
|
||||
But in special circumstances, like remote Omarchy installs on protected computers or for throw-away installations without sensitive data, you may want to install without encryption. You can hit `Ctrl + C` on the disk formatting confirmation to switch to an encryption-less installation.
|
||||
|
||||
### Help if you're stuck
|
||||
|
||||
If you get stuck, you can usually find someone willing to help in the _#omarchy-help_ channel on [the community Discord](https://omarchy.org/discord).
|
||||
@@ -0,0 +1,57 @@
|
||||
# Coming From Mac or Windows
|
||||
|
||||
If you've spent years on macOS or Windows, your fingers know a hundred things your brain has forgotten it ever learned. This chapter is the translation layer: where those instincts go in Omarchy. The features themselves are covered in depth elsewhere — this is just the map.
|
||||
|
||||
### Super is the center of everything
|
||||
|
||||
All the muscle memory you've built around Cmd or the Windows key transfers to one key: Super. That's the Windows key on a PC keyboard, and it's the anchor for nearly every hotkey in Omarchy.
|
||||
|
||||
Your Spotlight or Raycast or Start-menu reflex becomes `Super + Space`. That opens the Omarchy menu, which launches apps, changes settings, installs software, captures the screen — just about everything. Start typing to filter. There's also a dedicated apps-only menu on `Super + Alt + Space`. See [navigation](04-navigation.md).
|
||||
|
||||
### There's no dock and no desktop icons
|
||||
|
||||
Nothing to click to launch things, no icons to arrange on the desktop. Apps start from a hotkey (`Super + Return` for the terminal, `Super + Shift + Return` for the browser, and `Super + K` for a list of everything that's mapped) or from the menu. The one persistent piece of UI is [the top bar](05-the-top-bar.md), which covers what the menu bar, system tray, and Notification Center did for you before — and nearly every widget on it does something on left, right, and middle click.
|
||||
|
||||
### Windows place themselves
|
||||
|
||||
The biggest mindset shift: you don't drag windows around or snap them to screen halves. Open a window and it takes the whole screen. Open a second and they split it. You never fish a window out from under another one, because windows don't overlap.
|
||||
|
||||
When you genuinely need a floating window, `Super + T` toggles the active one out of the tiling (and back). But give the tiling a real chance first — it's the heart of the whole thing. [Navigation](04-navigation.md) walks you through it.
|
||||
|
||||
Workspaces will feel familiar: they're macOS Spaces or Windows virtual desktops, except you'll actually use them, because `Super + 1/2/3/4` jumps straight to one and `Super + Shift + 1/2/3/4` sends the active window there. No animation delay, just instant jumps. This means that you might not even need multiple monitors, if you were used to that before.
|
||||
|
||||
### Copy and paste just work
|
||||
|
||||
On the Mac you had Cmd + C everywhere. On Windows you had Ctrl + C everywhere — except the terminal, where it kills your program. Omarchy gives you `Super + C`, `Super + X`, and `Super + V`, and they work everywhere, including the terminal. No separate reflex to learn for the shell.
|
||||
|
||||
Windows folks: your Win + V clipboard history lives on `Super + Ctrl + V`, and it holds images as well as text. See [unified clipboard & history](08-unified-clipboard-history.md).
|
||||
|
||||
### The translation table
|
||||
|
||||
| You reach for | In Omarchy |
|
||||
| ------------- | ---------- |
|
||||
| Spotlight / Raycast / Start menu | `Super + Space` — the Omarchy menu |
|
||||
| AirDrop | LocalSend, via `Super + Ctrl + S` — see [GUIs](22-guis.md) |
|
||||
| Cmd + Shift + 4 / Win + Shift + S | `Print Screen` — see [screenshots & recording](12-screenshots-recording.md) |
|
||||
| Notification Center | Notification history on `Super + Shift + Alt + ,` |
|
||||
| Time Machine (for the system) | Automatic [system snapshots](47-system-snapshots.md) on every update |
|
||||
| App Store / downloading an installer | _Install_ in the menu, or `omarchy pkg add` — see [other packages](29-other-packages.md) |
|
||||
| System Settings / Control Panel | _Setup_ in the menu, which edits plain config files — see [dotfiles](31-dotfiles.md) |
|
||||
|
||||
### Some things really are different
|
||||
|
||||
A lot of settings live in text files you edit, not panels you click through. That sounds primitive until you realize it means every tweak can be seen, copied to your next machine, and put in version control. The _Setup_ menu drops you straight into the right file and restarts whatever needs restarting when you're done.
|
||||
|
||||
Updates come through one command — _Update > Omarchy_ — that updates Omarchy itself and every package on the system, taking a snapshot first. No per-app updaters nagging you at random. See [updates](30-updates.md).
|
||||
|
||||
Software comes from a package manager, not from downloaded installers.
|
||||
|
||||
And when you close a window, the app actually quits. There's no macOS limbo where the program keeps running with no windows. `Super + W` means gone.
|
||||
|
||||
### On Mac hardware
|
||||
|
||||
Omarchy runs well on Intel Macs — see [Mac support](44-mac-support.md). And the keyboard is kind to you: Omarchy doesn't remap anything, and Linux treats the Command key as Super, so Super sits right where Cmd always was. Your thumb won't notice the move.
|
||||
|
||||
### Give it two weeks
|
||||
|
||||
The instincts transfer faster than you'd think. Skim the [hotkeys](07-hotkeys.md) chapter once, and whenever you blank on a binding, hit `Super + K` — it shows you all of them. That's the only hotkey you actually have to memorize.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Navigation
|
||||
|
||||
Everything in Omarchy happens via the keyboard — _EVERYTHING!_ When the system first starts, you literally can't do a thing with the mouse alone. But you can hit `Super + Space` to reveal the Omarchy Menu and from here you to do just about everything.
|
||||
|
||||
But the Omarchy menu is not even intended to be the main way to operate the system most of the time. We can get faster than that! All the most important applications are bound directly to individual hotkeys. You start the terminal with `Super + Return` and a browser with `Super + Shift + Return`. Try doing one after the other, and you'll see the magic of Hyprland's tiling in action:
|
||||
|
||||

|
||||
|
||||
You can then hit `Super + J` to stack them on top of each other instead of side by side:
|
||||
|
||||

|
||||
|
||||
Hit `Super + J` again to return them to their side-by-side positions. Then try `Super + Shift + Arrow Right` while on the browser to swap the windows.
|
||||
|
||||
Now try `Super + Ctrl + T` to start the Activity monitor. That'll appears as a floating window. You can tile it using `Super + T` (and hit that again to make it floating again). Now press `Super + Shift + F` to open the files manager. You'll have a neat four-way setup:
|
||||
|
||||

|
||||
|
||||
You navigate between the window you want to be active with `Super + Arrow`. This will switch focus and move the cursor to the center of the new application.
|
||||
|
||||
If you hit `Super + Shift + 2`, you'll move the current focused application onto the second workspace. `Super + Shift + 1` moves it back. (And `Super + Shift + Alt + 2` will move the current focused application onto the second workspace without switching to it).
|
||||
|
||||
If you hold down `Super` and use the mouse to click on a window, you'll be able to rearrange where it sits. If you hold `Super` and use the right button on the mouse, you can freely resize the window.
|
||||
|
||||
You close a window on `Super + W` (and close all windows on `Ctrl + Alt + Delete`).
|
||||
|
||||
You can also go full screen with `Super + F` or even just full-width (keeping the top bar) with `Super + Alt + F` or full-screen within a window with `Super + Ctrl + F` (good for YouTube!).
|
||||
|
||||
### Dwindle vs scrolling layout
|
||||
|
||||
Omarchy's default layout is called dwindle. It keeps all the windows you open on a single workspace visible at all time, even if it has to shrink them down.
|
||||
|
||||

|
||||
|
||||
But you can also choose to turn a workspace into the scrolling layout where windows are lined up side-by-side, beyond the visible edge of the display. You turn a single workspace into this layout via `Super + L`.
|
||||
|
||||

|
||||
|
||||
The choice is per workspace, and it sticks. So you can keep workspace 1 on dwindle for browsing and workspace 2 on scrolling for code, and they'll come back that way after a restart. (The same toggle is under _Trigger > Toggle > Workspace Layout_ in the Omarchy menu).
|
||||
|
||||
If you wish to use the scrolling layout as the default, you can set that in `~/.config/hypr/looknfeel.lua`:
|
||||
|
||||
```lua
|
||||
hl.config({
|
||||
general = {
|
||||
layout = "scrolling",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Grouping windows
|
||||
|
||||
Windows can be grouped using `Super + G`. Once you're in a group, every window you start while that's active will belong to the group. You can move between these grouped windows using `Super + Ctrl + Arrow Left/Right` or `Super + Alt + 1/2/3/4` to go directly to grouped window in order.
|
||||
|
||||
You can move a window out of the grouping with `Super + Alt + G` or disassemble the entire group by hitting `Super + G` again. Finally, you can move windows outside the group into it with `Super + Alt + Arrows`.
|
||||
|
||||
### Popping windows
|
||||
|
||||
You can pop a window out of its workspace allocation with `Super + O`. That'll pin it as a floating window that follows you on whatever workspace you go to. Great for video players and the like.
|
||||
|
||||

|
||||
|
||||
### Scratchpad workspace
|
||||
|
||||
Finally, there's a special scratchpad workspace that overlays on whatever workspace you're currently on. You access what's on that using `Super + S` and you place windows there using `Super + Alt + S`.
|
||||
|
||||
It works well for controls or perhaps a terminal that you quickly want to interact with in an overlay without leaving the current workspace. If you want to move a window off the scratchpad, you just move it directly to another workspace with something like `Super + Shift + 1` to move it to workspace 1.
|
||||
|
||||
### It takes some getting used to!
|
||||
|
||||
It takes a little while to get used to navigating your desktop like this, but once you do, it'll be hard to go back to a traditional mouse-driven desktop experience!
|
||||
@@ -0,0 +1,134 @@
|
||||
# The Top Bar
|
||||
|
||||
The strip along the top of your screen is the Omarchy bar. It's not a bolted-on status bar but part of the Omarchy shell, the single long-running Quickshell process that also draws the menu, the notifications, the OSD popups, and the lock screen. That's why it themes perfectly with everything else and why a panel opens instantly instead of spawning a new app.
|
||||
|
||||
It's also the one piece of the desktop that's always on screen, so it's worth knowing what all those little glyphs do.
|
||||
|
||||
## What's on it by default
|
||||
|
||||
The bar has three sections. On the left sits the Omarchy logo (the menu launcher) and the workspace indicators. In the center you get the status indicators, the clock, the keyboard layout, the weather, and an Omarchy update badge. On the right: the system tray, agents, bluetooth, network, audio, display, and power.
|
||||
|
||||
A few of those only show up when they have something to say. The keyboard layout appears only if you've configured more than one layout. The update badge appears only when there's an Omarchy update waiting. And the agents icon appears the first time Omarchy finds AI coding usage on the machine (see [AI](17-ai.md)).
|
||||
|
||||
## Clicking around
|
||||
|
||||
Nearly every widget does something on left, right, and middle click, and several respond to scrolling. This is the part people miss — the right and middle buttons are where a lot of the good stuff hides.
|
||||
|
||||
| Widget | Left | Right | Middle / scroll |
|
||||
| --- | --- | --- | --- |
|
||||
| Menu | Omarchy menu | New terminal | — |
|
||||
| Workspaces | Focus that workspace | — | — |
|
||||
| Clock | Calendar popup | Cycle the label format | Middle: timezone picker |
|
||||
| Weather | Forecast popup | Full weather as a notification | Middle: refresh |
|
||||
| Audio | Audio panel | Mute | Middle: panel · scroll: volume |
|
||||
| Microphone | Mute the mic | — | Middle: audio panel · scroll: input volume |
|
||||
| Network | Network panel | — | — |
|
||||
| Bluetooth | Bluetooth panel | Toggle the radio | — |
|
||||
| Display | Display panel | — | Scroll: brightness |
|
||||
| Power | Power panel | Toggle the battery percentage | — |
|
||||
| Media | Play/pause | Cover-art popup | Middle: next · scroll: prev/next |
|
||||
| Agents | Agents panel | Launch your agent | Middle: next subscription |
|
||||
| Tray | Hover to reveal the drawer | Right on the chevron to manage | — |
|
||||
| Omarchy update | Run the update | — | — |
|
||||
|
||||
Not everything in that table is on your bar out of the box. The media widget (MPRIS now-playing, with a scrolling track and artist) and the microphone widget are both built in but off by default — add them if you want them, as described below.
|
||||
|
||||
## The panels
|
||||
|
||||
Clicking a bar icon opens a panel, which is a proper popup with sliders, lists, and keyboard navigation rather than a tooltip. Each one also has a hotkey, so you never have to aim at a 16-pixel glyph:
|
||||
|
||||
| Hotkey | Panel |
|
||||
| --- | --- |
|
||||
| `Super + Ctrl + A` | Audio |
|
||||
| `Super + Ctrl + W` | Network |
|
||||
| `Super + Ctrl + B` | Bluetooth |
|
||||
| `Super + Ctrl + D` | Display |
|
||||
| `Super + Ctrl + P` | Power |
|
||||
| `Super + Ctrl + Alt + D` | Calendar |
|
||||
| `Super + Ctrl + 1-9` | Toggle the nth panel in the right section |
|
||||
|
||||
The panels aren't read-outs. They're where you actually do the thing:
|
||||
|
||||
- **Audio** has a master volume slider, an output-device picker, and a per-app mixer, so you can turn down that one browser tab without touching everything else.
|
||||
- **Network** scans for Wi-Fi, shows signal strength, connects, and lets you pick a DNS provider.
|
||||
- **Bluetooth** lists your devices with connect/disconnect and battery levels.
|
||||
- **Power** shows battery stats, switches power profiles (it remembers a separate choice for battery and AC), and prints some system info.
|
||||
- **Display** carries a brightness slider, text size, monitor scaling presets, and — when you have more than one screen — per-monitor controls. See [monitors](33-monitors.md) for the deeper story.
|
||||
- **Clock** opens a month grid with ISO week numbers and month stepping.
|
||||
|
||||
Every panel takes the keyboard as well as the mouse: arrows move, Return activates, Tab steps to the neighbouring panel, and Escape closes.
|
||||
|
||||
`Super + Ctrl + 1-9` counts panels left to right in the right section, skipping the tray since it has no panel of its own. So the number matches the icon you'd point at.
|
||||
|
||||
### Tailscale and Dropbox
|
||||
|
||||
Two more widgets appear on the bar only once you install the matching service from **Install → Service**, and both are worth knowing about because they do more than report status.
|
||||
|
||||
The **Tailscale** panel connects and disconnects the tailnet, switches between accounts, and picks an exit node (your own machines and Mullvad regions both show up in that list). It also browses your machines — and with one selected, `s` sends files to it over Taildrop, which is the fastest way to move a file to your phone or another laptop. `c` copies the machine's IP, `n` its name, and `d` its full DNS name. There's a send button on each machine row too, if you'd rather click. The same thing from the terminal is `omarchy tailscale send <machine> [file...]`.
|
||||
|
||||
The **Dropbox** panel handles login, shows how much storage you've used, and lists recently synced files.
|
||||
|
||||
Removing either service takes its widget back off the bar.
|
||||
|
||||
## Indicators
|
||||
|
||||
The little cluster in the center is the indicators widget. These are status glyphs for modes you've turned on: do not disturb, night light, a queued [reminder](09-reminders.md), an active screen recording, stay awake, and [dictation](11-text-extraction-dictation.md). They light up when the mode is active and otherwise stay out of the way — hover the center of the bar to peek at the inactive ones. Clicking an indicator toggles that mode.
|
||||
|
||||
If you'd rather they were always visible, set `alwaysShow` to `true` on the widget. And if you only care about some of them, list the ones you want in `items`: `["Dnd", "Reminder", "NightLight"]`. You can have more than one indicators widget, so different sections can show different subsets.
|
||||
|
||||
## Rearranging the bar
|
||||
|
||||
The bar configures itself. You don't have to open a config file to move things.
|
||||
|
||||
Grab an empty patch of the bar around the center and drag it toward another screen edge, and the bar moves there — left, right, top, or bottom all work, and every widget adapts (vertical bars fall back to compact icon-only forms). A click-and-hold starts the same drag. Double-left-click that same empty space to toggle transparency. And drag any widget to reorder it or throw it into another section.
|
||||
|
||||
If you'd rather pick from a menu, **Style → Menu Bar** has both position and transparency.
|
||||
|
||||
The same things have commands, which is what you want for a [dotfiles](31-dotfiles.md) setup:
|
||||
|
||||
```bash
|
||||
omarchy bar position bottom
|
||||
omarchy bar transparent toggle
|
||||
omarchy bar move omarchy.clock --section center --index 0
|
||||
omarchy bar set omarchy.clock format "HH:mm"
|
||||
omarchy bar defaults # back to the shipped layout
|
||||
```
|
||||
|
||||
To add or remove a widget entirely, use the plugin commands. `omarchy plugin list` prints every widget the shell knows about with its id, and then:
|
||||
|
||||
```bash
|
||||
omarchy plugin enable omarchy.media --section center
|
||||
omarchy plugin disable omarchy.weather
|
||||
```
|
||||
|
||||
## Hiding the bar
|
||||
|
||||
`Super + Shift + Space` toggles the bar off and back on without killing the shell — panels and hotkeys keep working, you just get the pixels back. It's also in the menu under **Trigger → Toggle → Menu Bar**.
|
||||
|
||||
## The config file
|
||||
|
||||
All of it is stored in `~/.config/omarchy/shell.json`, under the `bar` key. Here's a trimmed version:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"bar": {
|
||||
"position": "top",
|
||||
"transparent": false,
|
||||
"centerAnchor": "omarchy.clock",
|
||||
"layout": {
|
||||
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
|
||||
"center": [{ "id": "omarchy.clock", "format": "HH:mm" }],
|
||||
"right": [{ "id": "omarchy.audio" }, { "id": "omarchy.power" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Every widget is one entry in one of the three layout arrays, and its settings sit inline on that entry — there's no separate settings file and no `config` sub-object. The clock's `format`, `formatAlt` (what right-click cycles to), and `verticalFormat` all live right there on `{ "id": "omarchy.clock" }`.
|
||||
|
||||
`centerAnchor` names the one center widget that gets pinned to the exact center of the screen, with the others flanking it. That's how the clock stays dead center even as the weather and update badge come and go. Set it to an empty string and the center list is just centered as a group instead.
|
||||
|
||||
One rule worth internalizing: **once you have your own `shell.json`, it's canonical**. Until you customize anything, the shell reads Omarchy's default file. The moment you drag a widget, run `omarchy bar`, or edit the file yourself, you own it — there's no deep merge, so new default widgets in future Omarchy releases won't appear on your bar automatically. `omarchy bar defaults` puts the shipped layout back whenever you want a clean slate.
|
||||
|
||||
The same file also holds your idle timings at the top level, outside the `bar` key: `idle.screensaver` and `idle.lock`, both in seconds since you went idle. So the default screensaver kicks in at 150 seconds and the lock at 300.
|
||||
@@ -0,0 +1,127 @@
|
||||
# Themes
|
||||
|
||||
Omarchy comes with twenty-two beautiful themes. You can select between them via _Style > Theme_ in the Omarchy Menu (`Super + Space`) or hop directly to the theme selector using `Super + Ctrl + Shift + Space`.
|
||||
|
||||
Each theme styles the desktop, terminal, neovim, activity screen (btop), Chromium, and the entire Omarchy shell: top bar, menu, notifications, OSD, and the lock screen. (For Obsidian, you must manually select the Omarchy theme via _Appearance > Themes_ inside the app).
|
||||
|
||||
Themes have a set of background images that you can pick between using `Super + Ctrl + Space`.
|
||||
|
||||
You can find even more themes on [the extra themes page](https://omarchy.org/themes/) or even [make your own theme](43-making-your-own-theme.md).
|
||||
|
||||

|
||||
_Tokyo Night_
|
||||
|
||||

|
||||
_Catppuccin_
|
||||
|
||||

|
||||
_Lumon_
|
||||
|
||||

|
||||
_Ethereal_
|
||||
|
||||

|
||||
_Everforest_
|
||||
|
||||

|
||||
_Gruvbox_
|
||||
|
||||

|
||||
_Miasma_
|
||||
|
||||

|
||||
_Hackerman_
|
||||
|
||||

|
||||
_Osaka Jade_
|
||||
|
||||

|
||||
_Kanagawa_
|
||||
|
||||

|
||||
_Nord_
|
||||
|
||||

|
||||
_Matte Black_
|
||||
|
||||

|
||||
_Vantablack_
|
||||
|
||||

|
||||
_Ristretto_
|
||||
|
||||

|
||||
_Retro 82_
|
||||
|
||||

|
||||
_Flexoki Light_
|
||||
|
||||

|
||||
_Rose Pine_
|
||||
|
||||

|
||||
_Catppuccin Latte_
|
||||
|
||||

|
||||
_White_
|
||||
|
||||
### Unlocks
|
||||
|
||||
Themes can also have a custom unlock design, which is used for the boot decryption process. You can select one of these under _Style > Unlock_. They look like this:
|
||||
|
||||

|
||||
_Catppuccin_
|
||||
|
||||

|
||||
_Catppuccin Latte_
|
||||
|
||||

|
||||
_Ethereal_
|
||||
|
||||

|
||||
_Everforest_
|
||||
|
||||

|
||||
_Flexoki Light_
|
||||
|
||||

|
||||
_Gruvbox_
|
||||
|
||||

|
||||
_Hackerman_
|
||||
|
||||

|
||||
_Kanagawa_
|
||||
|
||||

|
||||
_Lumon_
|
||||
|
||||

|
||||
_Matte Black_
|
||||
|
||||

|
||||
_Miasma_
|
||||
|
||||

|
||||
_Nord_
|
||||
|
||||

|
||||
_Osaka Jade_
|
||||
|
||||

|
||||
_Retro 82_
|
||||
|
||||

|
||||
_Ristretto_
|
||||
|
||||

|
||||
_Rose Pine_
|
||||
|
||||

|
||||
_Tokyo Night_
|
||||
|
||||

|
||||
_Vantablack_
|
||||
|
||||

|
||||
_White_
|
||||
@@ -0,0 +1,373 @@
|
||||
# Hotkeys
|
||||
|
||||
You can see all the main keyboard bindings with `Super + K` (Tmux bindings with `Super + Alt + K` and Herdr bindings with `Super + Ctrl + K`).
|
||||
|
||||
## Navigating
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Space` | Omarchy menu (apps and everything else) |
|
||||
| `Super + Alt + Space` | Apps menu |
|
||||
| `Super + Escape` | System menu (suspend, restart, etc) |
|
||||
| `Super + Ctrl + L` | Lock computer |
|
||||
| `Super + W` | Close window |
|
||||
| `Ctrl + Alt + Del` | Close all windows |
|
||||
| `Super + T` | Toggle window between tiling/floating |
|
||||
| `Super + J` | Toggle window position (horizontal/vertical) |
|
||||
| `Super + O` | Toggle popping window into sticky'n'floating |
|
||||
| `Super + L` | Toggle between dwindle and scrolling layout |
|
||||
| `Super + P` | Toggle pseudo window style (natural v stretch) |
|
||||
| `Super + F` | Go full screen |
|
||||
| `Super + Alt + F` | Go full width |
|
||||
| `Super + Ctrl + F` | Go full screen inside window |
|
||||
| `Super + 1/2/3/4` | Jump to specific workspace |
|
||||
| `Super + Tab` | Jump to next workspace |
|
||||
| `Super + Shift + Tab` | Jump to previous workspace |
|
||||
| `Super + Ctrl + Tab` | Jump to former workspace |
|
||||
| `Super + Shift + 1/2/3/4` | Move window to workspace |
|
||||
| `Super + Shift + Alt + 1/2/3/4` | Move window to workspace without following |
|
||||
| `Super + Shift + Alt + Arrows` | Move workspaces to directional monitor |
|
||||
| `Super + Arrow` | Move focus to window in direction of arrow |
|
||||
| `Super + Shift + Arrow` | Swap window with another in direction of arrow |
|
||||
| `Super + Minus` | Expand window left |
|
||||
| `Super + Equal` | Shrink window left |
|
||||
| `Super + Shift + Minus` | Shrink window up |
|
||||
| `Super + Shift + Equal` | Expand window down |
|
||||
| `Super + Alt + Minus/Equal` | Same resizing in smaller steps |
|
||||
| `Super + Ctrl + Minus/Equal` | Same resizing in bigger steps |
|
||||
| `Super + Alt + Home` | Save window width |
|
||||
| `Super + Home` | Restore saved window width |
|
||||
| `Super + Left Mouse` | Drag window around |
|
||||
| `Super + Right Mouse` | Resize window |
|
||||
| `Super + Scroll Wheel` | Scroll through workspaces |
|
||||
| `Super + G` | Toggle window grouping |
|
||||
| `Super + Alt + G` | Move window out of grouping |
|
||||
| `Super + Alt + Tab` | Cycle between windows in grouping |
|
||||
| `Super + Alt + Shift + Tab` | Cycle backward between windows in grouping |
|
||||
| `Super + Alt + 1/2/3/4/5` | Jump to specific window in grouping |
|
||||
| `Super + Alt + Arrow` | Move window into grouping in direction of arrow |
|
||||
| `Super + Ctrl + Left/Right` | Move between windows inside a tiling group |
|
||||
| `Super + S` | Show scratchpad workspace overlay |
|
||||
| `Super + Alt + S` | Move window to scratchpad workspace |
|
||||
| `Super + Ctrl + Z` | Zoom in on screen (repeat for more zoom) |
|
||||
| `Super + Ctrl + Alt + Z` | Zoom fully out from screen |
|
||||
| `Super + /` | Step forward through monitor scaling options |
|
||||
| `Super + Alt + /` | Step backward through monitor scaling options |
|
||||
| `Alt + Tab` | Cycle forward through windows on the active workspace |
|
||||
| `Alt + Shift + Tab` | Cycle backward through windows on the active workspace |
|
||||
| `Ctrl + Alt + Tab`| Cycle focus forward through monitors |
|
||||
| `Ctrl + Alt + Shift + Tab`| Cycle focus backwards through monitors |
|
||||
|
||||
## System controls
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Ctrl + A` | Audio panel |
|
||||
| `Super + Ctrl + B` | Bluetooth panel |
|
||||
| `Super + Ctrl + W` | Wifi/network panel |
|
||||
| `Super + Ctrl + D` | Display panel |
|
||||
| `Super + Ctrl + P` | Power panel |
|
||||
| `Super + Ctrl + Alt + D` | Calendar panel |
|
||||
| `Super + Ctrl + 1-9` | Toggle bar panel by position |
|
||||
| `Super + Ctrl + S` | Share menu (via LocalSend) |
|
||||
| `Super + Ctrl + T` | Activity (btop) |
|
||||
| `Super + Ctrl + C` | Capture controls (screenshot/-recording/picker) |
|
||||
| `Super + Ctrl + O` | Toggle menu |
|
||||
| `Super + Ctrl + H` | Hardware menu |
|
||||
| `Super + Ctrl + Q` | Calculator |
|
||||
| `Super + Ctrl + E` | Emoji picker |
|
||||
| `Super + Ctrl + .` | Transcode media |
|
||||
| `Super + Shift + Ctrl + A` | Pick an AI agent |
|
||||
|
||||
## Adjustments
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Shift + Brightness Up` | Maximum screen brightness |
|
||||
| `Shift + Brightness Down` | Minimum screen brightness |
|
||||
| `Alt + Brightness Up/Down` | Precise 1% brightness changes |
|
||||
| `Alt + Volume Up/Down` | Precise 1% volume changes |
|
||||
| `Keyboard Brightness Up/Down` | Keyboard backlight brighter/dimmer |
|
||||
| `Keyboard Backlight` | Cycle the keyboard backlight levels |
|
||||
| `Alt + Play` | Next track |
|
||||
| `Alt + Shift + Play` | Previous track |
|
||||
|
||||
## Launching apps
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Return` | Terminal |
|
||||
| `Super + Alt + Return` | Tmux terminal |
|
||||
| `Super + Ctrl + Return` | Herdr (agent manager) |
|
||||
| `Super + Shift + Return` | Browser |
|
||||
| `Super + Shift + Alt + B` | Browser (private/incognito) |
|
||||
| `Super + Shift + F` | File manager |
|
||||
| `Super + Shift + Alt + F` | File manager in cwd of terminal |
|
||||
| `Super + Shift + M` | Music (Spotify) |
|
||||
| `Super + Shift + Alt + M` | Music (cliamp) |
|
||||
| `Super + Shift + /` | Password manager (1password) |
|
||||
| `Super + Shift + N` | Editor (Neovim) |
|
||||
| `Super + Shift + C` | Calendar ([HEY](https://hey.com/)) |
|
||||
| `Super + Shift + E` | Email ([HEY](https://hey.com/)) |
|
||||
| `Super + Shift + Alt + E` | New email ([HEY](https://hey.com/)) |
|
||||
| `Super + Shift + A` | AI (ChatGPT) |
|
||||
| `Super + Shift + Alt + A` | AI (Grok) |
|
||||
| `Super + Shift + G` | Messenger (Signal) |
|
||||
| `Super + Shift + P` | Google Photos |
|
||||
| `Super + Shift + S` | Google Maps |
|
||||
| `Super + Shift + Alt + G` | Messenger (WhatsApp) |
|
||||
| `Super + Shift + Ctrl + G` | Messenger (Google) |
|
||||
| `Super + Shift + D` | Docker (LazyDocker) |
|
||||
| `Super + Shift + O` | Obsidian |
|
||||
| `Super + Shift + W` | Writing (Omawrite) |
|
||||
| `Super + Shift + X` | X |
|
||||
| `Super + Shift + Alt + X` | X Compose |
|
||||
| `Super + Shift + Y` | YouTube |
|
||||
|
||||
Change/add bindings in `~/.config/hypr/bindings.lua`.
|
||||
|
||||
## Universal clipboard
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + C` | Copy |
|
||||
| `Super + X` | Cut (not in terminal) |
|
||||
| `Super + V` | Paste |
|
||||
| `Super + Ctrl + V` | Clipboard manager |
|
||||
|
||||
Usually on Linux, you need `Ctrl + Shift + C/V` to copy'n'paste in the terminal and `Ctrl + C/V` to do it everywhere else. These Omarchy unified clipboard hotkeys work everywhere.
|
||||
|
||||
## Capture
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------------------- | ------------------------------ |
|
||||
| `Super + Ctrl + C` | Capture menu (for keyboards w/o PrintScr button) |
|
||||
| `Print Screen` | Screenshot |
|
||||
| `Alt + Print Screen` | Screenrecord |
|
||||
| `Super + Print Screen` | Color picker |
|
||||
| `Super + Ctrl + Print Screen` | Text extraction to clipboard |
|
||||
| `Super + Alt + [` | Make webcam overlay smaller while recording |
|
||||
| `Super + Alt + ]` | Make webcam overlay larger while recording |
|
||||
| `Alt + Shift + L` | Copy current URL from webapp or Chromium |
|
||||
| `Alt + Shift + D` | Download the video on the current page to `~/Videos` |
|
||||
| `Super + Ctrl + X` | Start/stop dictation (requires _Install > AI > Dictation_) |
|
||||
| `F9` | Push-to-talk dictation (requires _Install > AI > Dictation_) |
|
||||
|
||||
With screenrecordings, the hotkey first asks which audio you want, then starts recording. Hit it again to stop. See [screenshots and recording](12-screenshots-recording.md) for the details.
|
||||
|
||||
All capture options are also accessible under _Trigger > Capture_ in the Omarchy menu (`Super + Space`).
|
||||
|
||||
## Notifications
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + ,` | Dismiss latest notification |
|
||||
| `Super + Shift + ,` | Dismiss all notifications |
|
||||
| `Super + Ctrl + ,` | Toggle silencing notifications |
|
||||
| `Super + Alt + ,` | Invoke most recent notification |
|
||||
| `Super + Shift + Alt + ,` | Open notification history |
|
||||
|
||||
## Style
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Ctrl + Shift + Space` | Pick a new theme |
|
||||
| `Super + Ctrl + Space` | Pick theme background |
|
||||
| `Super + Backspace` | Toggle transparency on a window |
|
||||
| `Super + Ctrl + Backspace` | Toggle single-window square aspect |
|
||||
|
||||
Extra background images live in `~/.config/omarchy/backgrounds/<theme name>`. Also available via _Install > Style > Background_ in the Omarchy menu.
|
||||
|
||||
All style options are also accessible under _Style_ in the Omarchy menu (`Super + Space`).
|
||||
|
||||
## Toggles
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Ctrl + I` | Toggle locking on idle |
|
||||
| `Super + Ctrl + N` | Toggle nightlight display temperature |
|
||||
| `Super + Ctrl + Delete` | Toggle laptop display on/off |
|
||||
| `Super + Ctrl + Alt + Delete` | Toggle laptop display mirroring |
|
||||
| `Super + Shift + Space` | Toggle the top bar |
|
||||
| `Shift + Mute` | Switch to next audio output |
|
||||
| `Shift + Play` | Switch to next media source |
|
||||
| `Super + Shift + Backspace` | Toggle window gaps |
|
||||
|
||||
## Reminders
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Ctrl + R` | Set a reminder |
|
||||
| `Super + Ctrl + Alt + R` | See all reminders |
|
||||
| `Super + Ctrl + Shift + R` | Clear all reminders |
|
||||
|
||||
## Notices
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Super + Ctrl + Alt + T` | Show time as notification |
|
||||
| `Super + Ctrl + Alt + B` | Show battery as notification |
|
||||
| `Super + Ctrl + Alt + W` | Toggle weather as notification |
|
||||
|
||||
## Tmux
|
||||
|
||||
The prefix key is `Ctrl + Space` (`Ctrl + B` also works). You can change these bindings in `~/.config/tmux/tmux.conf`.
|
||||
|
||||
### Panes
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Prefix + v` | Split pane beside (vertical) |
|
||||
| `Prefix + h` | Split pane below (horizontal) |
|
||||
| `Prefix + x` | Kill pane |
|
||||
| `Prefix + z` | Toggle pane zoom (fullscreen) |
|
||||
| `Alt + Enter` | Split pane below (no prefix) |
|
||||
| `Alt + Shift + Enter` | Split pane beside (no prefix) |
|
||||
| `Alt + Escape` | Kill pane (no prefix) |
|
||||
| `Ctrl + Alt + Arrows` | Move between panes |
|
||||
| `Ctrl + Alt + Shift + Arrows` | Resize panes |
|
||||
|
||||
### Windows
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Prefix + c` | New window |
|
||||
| `Prefix + k` | Kill window |
|
||||
| `Prefix + r` | Rename window |
|
||||
| `Alt + 1-9` | Go to specific window |
|
||||
| `Alt + Arrow Left/Right` | Move between windows |
|
||||
| `Alt + Shift + Arrow Left/Right` | Move window left/right |
|
||||
|
||||
### Sessions
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Prefix + C` | New session |
|
||||
| `Prefix + K` | Kill session |
|
||||
| `Prefix + R` | Rename session |
|
||||
| `Prefix + N` | Next session |
|
||||
| `Prefix + P` | Previous session |
|
||||
| `Alt + Arrow Up/Down` | Move between sessions |
|
||||
| `Prefix + s` | List sessions |
|
||||
| `Prefix + d` | Detach from session |
|
||||
|
||||
### Copy mode (vi-style)
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Prefix + [` | Enter copy mode |
|
||||
| `v` | Begin selection (in copy mode) |
|
||||
| `y` | Copy selection (in copy mode) |
|
||||
|
||||
### General
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Prefix + q` | Reload config |
|
||||
| `Prefix + ?` | Show Tmux keybindings |
|
||||
| `Prefix + :` | Command prompt |
|
||||
|
||||
### Tmux layout functions
|
||||
|
||||
These functions must be run inside a Tmux session.
|
||||
|
||||
| Command | Function |
|
||||
| ------- | -------- |
|
||||
| `tdl <ai> [<second_ai>]` | Create dev layout with editor, AI, and terminal |
|
||||
| `tdlm <ai> [<second_ai>]` | Create dev layout per subdirectory |
|
||||
| `tsl <count> <command>` | Create multi-pane swarm layout |
|
||||
|
||||
## Ghostty Terminal
|
||||
|
||||
Ghostty terminal is installed using _Install > Terminal_ via the Omarchy menu.
|
||||
|
||||
| Hotkey | Function |
|
||||
| ----------------------- | --------------------- |
|
||||
| `Ctrl + Shift + E` | New split below |
|
||||
| `Ctrl + Shift + O` | New split besides |
|
||||
| `Ctrl + Alt + Arrows` | Move between splits |
|
||||
| `Super + Ctrl + Shift + Arrows` | Resize split by 10 lines |
|
||||
| `Super + Ctrl + Shift + Alt + Arrows` | Resize split by 100 lines |
|
||||
| `Ctrl + Shift + T` | New tab |
|
||||
| `Ctrl + Shift + Arrows` | Move between tabs |
|
||||
| `Alt + Numbers` | Go to specific tab |
|
||||
| `Shift + Pg Up/Down` | Scroll the history |
|
||||
| `Ctrl + Left mouse` | Open link in browser |
|
||||
|
||||
## File Manager
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------------------- | ------------------------------ |
|
||||
| `Ctrl + L` | Go to path |
|
||||
| `Space` | Preview file (arrows navigate) |
|
||||
| `Backspace` | Go back one folder |
|
||||
|
||||
## Neovim (w/ lazyvim)
|
||||
|
||||
### Navigation
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------------------------ | ------------------------------- |
|
||||
| `Space` | Show command options |
|
||||
| `Space Space` | Open file via fuzzy search |
|
||||
| `Space E` | Toggle sidebar |
|
||||
| `Space G G` | Show git controls |
|
||||
| `Space S G` | Search file content |
|
||||
| `Ctrl + W W` | Jump between sidebar and editor |
|
||||
| `Ctrl + Left/right arrow` | Change size of sidebar |
|
||||
| `Shift + H` | Go to left file tab |
|
||||
| `Shift + L` | Go to right file tab |
|
||||
| `Space B D` | Close file tab |
|
||||
|
||||
### While in sidebar
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------------------------ | ------------------------------- |
|
||||
| `A` | Add new file in parent dir |
|
||||
| `Shift + A` | Add new subdir in parent dir |
|
||||
| `D` | Delete highlighted file/dir |
|
||||
| `M` | Move highlighted file/dir |
|
||||
| `R` | Rename highlighted file/dir |
|
||||
| `?` | Show help for all commands |
|
||||
|
||||
[See all the Neovim hotkeys configured by LazyVim](https://www.lazyvim.org/keymaps).
|
||||
|
||||
## Quick Emojis
|
||||
|
||||
You can use `Super + Ctrl + E` to show a complete emoji picker that'll put the selection on the clipboard or you can use these quick access options.
|
||||
|
||||
| Hotkey | EM | Clue |
|
||||
| ------------ | -- | ---------- |
|
||||
| `CapsLock M S` | 😄 | smile |
|
||||
| `CapsLock M C` | 😂 | cry |
|
||||
| `CapsLock M L` | 😍 | love |
|
||||
| `CapsLock M V` | ✌️ | victory |
|
||||
| `CapsLock M H` | ❤️ | heart |
|
||||
| `CapsLock M Y` | 👍 | yes |
|
||||
| `CapsLock M N` | 👎 | no |
|
||||
| `CapsLock M F` | 🖕 | fuck |
|
||||
| `CapsLock M W` | 🤞 | wish |
|
||||
| `CapsLock M R` | 🤘 | rock |
|
||||
| `CapsLock M K` | 😘 | kiss |
|
||||
| `CapsLock M E` | 🙄 | eyeroll |
|
||||
| `CapsLock M I` | 😉 | wink |
|
||||
| `CapsLock M P` | 🙏 | pray |
|
||||
| `CapsLock M D` | 🤤 | drool |
|
||||
| `CapsLock M M` | 💰 | money |
|
||||
| `CapsLock M X` | 🎉 | xellebrate |
|
||||
| `CapsLock M 1` | 💯 | 100% |
|
||||
| `CapsLock M T` | 🥂 | toast |
|
||||
| `CapsLock M O` |👌 | ok |
|
||||
| `CapsLock M G` |👋 | greeting |
|
||||
| `CapsLock M A` |💪 | arm |
|
||||
| `CapsLock M B` |🤯 | blowing |
|
||||
|
||||
## Quick Completions
|
||||
|
||||
| Hotkey | Completion |
|
||||
| ------------ | ---------- |
|
||||
| `CapsLock Space Space` | — (mdash) |
|
||||
| `CapsLock Space N` | Your name (as entered on setup) |
|
||||
| `CapsLock Space E` | Your email (as entered on setup) |
|
||||
|
||||
You can add more of your own by editing `~/.XCompose`, then running `omarchy-restart-xcompose` in the terminal to get the changes picked up.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Unified Clipboard & History
|
||||
|
||||
Usually on Linux, you need `Ctrl + Shift + C/V` to copy'n'paste in the terminal and `Ctrl + C/V` to do it everywhere else. That's hard to get used to for anyone who hasn't been born and bred on Linux! So too is the switch from super to ctrl, if you're coming from the Mac.
|
||||
|
||||
Omarchy tackles both problems with unified clipboard hotkeys that work (almost) everywhere. They are:
|
||||
|
||||
| Hotkey | Command |
|
||||
| ------- | ----------- |
|
||||
| Super + C | Copy |
|
||||
| Super + X | Cut |
|
||||
| Super + V | Paste |
|
||||
| Super + Ctrl + V | Clipboard history |
|
||||
|
||||
_Note that most agent harnesses will use `Ctrl + V` for pasting images, but `Super + V` for pasting text._
|
||||
|
||||
### Clipboard history
|
||||
|
||||
The clipboard history is provided by the Omarchy shell and works for both text and images. You trigger it by `Super + Ctrl + V`, select your entry with return, and then that'll be placed on the clipboard ready to paste on `Super + V`.
|
||||
|
||||

|
||||
|
||||
You can also search the history just by starting to type:
|
||||
|
||||

|
||||
@@ -0,0 +1,5 @@
|
||||
# Reminders
|
||||
|
||||
Omarchy has a built-in way to set simple reminders based on a countdown timer and with a message. You can do this via `Super + Ctrl + R`, seeing all the ones set via `Super + Ctrl + Alt + R`, and clearing all via `Super + Ctrl + Shift + R`. Everything also accessible via _Trigger > Reminder_. You can also use the cli with `omarchy reminder 7 'Tea ready'`.
|
||||
|
||||

|
||||
@@ -0,0 +1,23 @@
|
||||
# Notices
|
||||
|
||||
You can quickly access the date and time, battery status, and current weather using the hotkey notices.
|
||||
|
||||
### Date & Time
|
||||
|
||||
`Super + Ctrl + Alt + T`
|
||||
|
||||

|
||||
|
||||
### Weather
|
||||
|
||||
`Super + Ctrl + Alt + W`
|
||||
|
||||

|
||||
|
||||
The location is detected from your IP address, which is usually close enough, but not always. You can pin it down with `omarchy weather location --set Malibu`, or be exact about it by adding coordinates: `omarchy weather location --set Malibu 34.0259,-118.7798`. Run `omarchy weather location` on its own to see where it thinks you are, and `--clear` to go back to auto-detection.
|
||||
|
||||
### Battery
|
||||
|
||||
`Super + Ctrl + Alt + B`
|
||||
|
||||

|
||||
@@ -0,0 +1,15 @@
|
||||
# Text Extraction & Dictation
|
||||
|
||||
### Text Extraction
|
||||
|
||||
Hit `Super + Ctrl + PrtScr` to select a region on the screen for text extraction. The tesseract open source OCR model will then quickly convert that selection into text and place it on the clipboard. Then you just hit `Super + V` to paste.
|
||||
|
||||
This is very helpful for grabbing addresses out of image footers or phone numbers embedded in website headlines.
|
||||
|
||||

|
||||
|
||||
### Dictation
|
||||
|
||||
Omarchy offers AI dictation via [Voxtype](https://voxtype.io/). You install it via _Install > AI > Dictation_ through the Omarchy menu. By default, it'll load a base English model that takes up 150MB. But you can tweak which model you'd like to use by running `voxtype setup model` in the terminal. And you can tweak all the settings via `~/.config/voxtype/config.toml`.
|
||||
|
||||
Once installed, you dictate by holding down `F9` or by toggling with `Super + Ctrl + X`, and the dictated text will appear in the focused input area.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Screenshots & Recording
|
||||
|
||||
Everything you can grab off the screen hangs off the Print Screen key. One key on its own takes a picture, and the modifiers take a recording, a colour, or the text inside a region. If your keyboard doesn't have a Print Screen key at all, `Super + Ctrl + C` opens the same set as a menu.
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------ | -------- |
|
||||
| `Print Screen` | Screenshot |
|
||||
| `Alt + Print Screen` | Screenrecord (or stop the one that's running) |
|
||||
| `Super + Print Screen` | Colour picker |
|
||||
| `Super + Ctrl + Print Screen` | Extract text from a region |
|
||||
| `Super + Ctrl + C` | Capture menu |
|
||||
| `Super + Ctrl + .` | Transcode a picture or video |
|
||||
|
||||
## Screenshots
|
||||
|
||||
Hit `Print Screen` and the screen freezes so nothing shifts under you while you aim. Drag a box for a freeform region, or just click once and the shot snaps to whatever rectangle you clicked in — a window if you landed on one, the whole monitor if you landed on the bar or in a gap. Changed your mind? Hit `Print Screen` again to dismiss the picker.
|
||||
|
||||
The result goes two places at once: a PNG in your pictures directory, and the clipboard, so you can paste it straight into a chat window with `Super + V`. A notification pops up with a thumbnail. Click it (or hit `Super + Alt + ,` to invoke the last notification) and the shot opens in Tensaku, the annotation editor, where you can draw arrows and boxes on it before you send it.
|
||||
|
||||
Files land in `~/Pictures` by default, named `screenshot-2026-08-13_14-22-05.png`. If you'd rather keep them in their own folder, set `OMARCHY_SCREENSHOT_DIR` — see [the FAQ](46-faq.md) for where to put session environment variables. Omarchy creates the directory for you if it isn't there. You can swap the editor too with `OMARCHY_SCREENSHOT_EDITOR`.
|
||||
|
||||
From the terminal, `omarchy screenshot` takes the same shot, and you can be explicit about it: `omarchy capture screenshot region` for freeform only, `windows` to snap to window and monitor rectangles, or `fullscreen` to skip the picker entirely and grab the focused monitor. A second argument of `copy` puts the shot only on the clipboard, and `save` only on disk.
|
||||
|
||||
### Driving the picker from the keyboard
|
||||
|
||||
While the selection is up, you don't have to use the mouse at all:
|
||||
|
||||
| Key | Function |
|
||||
| --- | -------- |
|
||||
| `Return` | Capture the highlighted window |
|
||||
| `Ctrl + Return` | Capture the whole screen |
|
||||
| `Tab` / `Ctrl + Tab` | Highlight the next / previous window |
|
||||
| Arrow keys | Highlight the window in that direction |
|
||||
|
||||
The arrows and Tab move the cursor to the window they pick, so the highlight follows along and you can see what you're about to capture. These bindings only exist while a selection is on screen, so they can't collide with anything in your own config.
|
||||
|
||||
## Screen recording
|
||||
|
||||
`Alt + Print Screen` opens _Trigger > Capture > Screenrecord_, which asks what you want on the soundtrack: no audio, desktop audio, desktop plus microphone, or desktop plus microphone plus webcam. That last one only shows up if you actually have a camera plugged in. Pick one and you get the same picker as a screenshot: drag a region, or click a window or monitor.
|
||||
|
||||
Recording runs on gpu-screen-recorder, which encodes on the GPU at 60fps and falls back to the CPU if it has to. The result is an MP4 in `~/Videos`, named `screenrecording-2026-08-13_14-22-05.mp4`. Set `OMARCHY_SCREENRECORD_DIR` to change that — but note that unlike the screenshot directory, this one has to exist already, or the recording refuses to start.
|
||||
|
||||
While you're recording, a little indicator shows up in the bar. Click it to stop. You can also stop with `Alt + Print Screen` again, or with the _Stop Screenrecording_ entry under _Trigger > Capture > Screenrecord_, which only appears while something is actually recording.
|
||||
|
||||
Stopping does a bit of tidying before it hands you the file: the first frame gets trimmed, and if there's audio it's normalized to -14 LUFS with the PipeWire capture pop at the very start muted out. Then a notification appears with a thumbnail from the recording. Click it to play the file in mpv.
|
||||
|
||||
### The webcam overlay
|
||||
|
||||
When you record with a webcam, the camera appears as a pinned, cropped portrait window in the bottom-right corner of whatever you're recording. If it's sitting on top of something you need, resize it on the fly:
|
||||
|
||||
| Hotkey | Function |
|
||||
| ------ | -------- |
|
||||
| `Super + Alt + [` | Make the webcam overlay smaller |
|
||||
| `Super + Alt + ]` | Make the webcam overlay larger |
|
||||
|
||||
There are three sizes — small, medium, and large — and the hotkeys step between them. Medium is the default. They're proportional to the recording, so the camera takes up the same share of the frame whether you're recording a 1080p monitor or a 6K one. And if you recorded a region rather than a whole display, the overlay anchors to that region's corner rather than the monitor's, so it stays inside the shot.
|
||||
|
||||
You can also call it directly with `omarchy-capture-webcam-resize small`, or `reset` to go back to medium.
|
||||
|
||||
## Text, QR codes, and colours
|
||||
|
||||
`Super + Ctrl + Print Screen` selects a region and OCRs it to the clipboard. That's covered properly in [Text Extraction & Dictation](11-text-extraction-dictation.md).
|
||||
|
||||
_Trigger > Capture > QR Code_ does the same trick for QR codes. Select the region with the code in it, and the decoded value goes to the clipboard. It only looks for QR codes — dense screen content has a habit of false-positiving as a barcode otherwise. Worth knowing: the decoded value goes to the clipboard and nowhere else. It isn't printed, it isn't in the notification, and it's marked sensitive so it doesn't stick around in [clipboard history](08-unified-clipboard-history.md). QR codes routinely carry secrets — the `otpauth://` URI behind a 2FA setup code, for one — and you don't want that in a log. Pasting still works fine.
|
||||
|
||||
`Super + Print Screen` (or _Trigger > Capture > Color_) turns the cursor into an eyedropper. Click anything on screen and the colour lands on the clipboard. Press the hotkey again to back out without picking.
|
||||
|
||||
## Transcoding before you share
|
||||
|
||||
A 4K screen recording or a raw HEIC off your phone is often too big to just send. `Super + Ctrl + .` (or _Trigger > Transcode_) fixes that. It offers you a fuzzy file picker over `~/Pictures` and `~/Videos`, then asks for a format and a size.
|
||||
|
||||
Pictures go to jpg or png at high, medium, or low, which cap the width at 3160, 2160, and 1080 pixels. Videos go to mp4 or an animated gif at 4k, 1080p, or 720p. The converted file is written next to the original with the resolution in the name — `demo-1080p.mp4` — and the path is copied to the clipboard as a file URI, so you can paste it directly into an app that takes file drops.
|
||||
|
||||
It works from the terminal too, if you already know what you want: `omarchy transcode ~/Videos/demo.mov mp4 1080p`. There's also `omarchy transcode ascii`, which turns an image into ASCII art — that one's mostly for [branding](41-branding.md).
|
||||
|
||||
## Sending it somewhere
|
||||
|
||||
Once you've got the file, `Super + Ctrl + S` opens the Share menu and sends it to another device on your network via LocalSend. See [GUIs](22-guis.md) for that.
|
||||
@@ -0,0 +1,101 @@
|
||||
# Toggles, Idle & the Screensaver
|
||||
|
||||
A lot of what you change day to day isn't really a setting. It's a mode you flip on for an hour and off again: night light while you're working late, do not disturb while you're presenting, stay awake while you're watching something. Omarchy calls those toggles, and they all work the same way — a hotkey, a menu entry, and a command, all hitting the same switch.
|
||||
|
||||
### The toggle menu
|
||||
|
||||
`Super + Ctrl + O` opens _Trigger > Toggle_ directly, or you can walk there from the Omarchy menu (`Super + Space`). Everything in that list is a switch you can flip without thinking about where the state lives.
|
||||
|
||||
From the terminal, the same switches are `omarchy toggle <thing>`. Run `omarchy toggle` on its own to see the whole group.
|
||||
|
||||
| Toggle | Hotkey | Command |
|
||||
| ------ | ------ | ------- |
|
||||
| Night light | `Super + Ctrl + N` | `omarchy toggle nightlight` |
|
||||
| Silence notifications | `Super + Ctrl + ,` | `omarchy toggle notification silencing` |
|
||||
| Stay awake (no idle lock) | `Super + Ctrl + I` | `omarchy toggle idle` |
|
||||
| Crash capture | — | `omarchy toggle crash-capture` |
|
||||
| Screensaver | — | `omarchy toggle screensaver` |
|
||||
| Menu bar | `Super + Shift + Space` | `omarchy toggle bar` |
|
||||
| Touchpad | `XF86TouchpadToggle` | `omarchy toggle touchpad` |
|
||||
| Touchscreen | — | `omarchy toggle touchscreen` |
|
||||
| Suspend | — | `omarchy toggle suspend` |
|
||||
| Hybrid GPU | — | `omarchy toggle hybrid gpu` |
|
||||
|
||||
The touchpad, touchscreen, and hybrid GPU switches live under _Trigger > Hardware_ (`Super + Ctrl + H`) rather than under Toggle, since they only show up when you actually have that hardware. The touchpad and touchscreen ones survive a Hyprland reload — the disabled state is written back out as a small Lua file that Hyprland sources on startup.
|
||||
|
||||
The Toggle menu also carries a few things that aren't `omarchy toggle` commands but behave the same: battery percentage in the bar, workspace layout (`Super + L`), window gaps (`Super + Shift + Backspace`), and the 1-window square aspect (`Super + Ctrl + Backspace`).
|
||||
|
||||
Most of these are just a flag file under `~/.local/state/omarchy/toggles/`. If you want to branch on one in a script, `omarchy-toggle-enabled` gives you an exit code instead of making you go looking:
|
||||
|
||||
```bash
|
||||
omarchy-toggle-enabled screensaver-off && echo "screensaver is off"
|
||||
```
|
||||
|
||||
The flags are named for the off state — `screensaver-off`, `suspend-off`, `bar-off` — so their presence means the feature is disabled.
|
||||
|
||||
### Indicators in the bar
|
||||
|
||||
When a mode is on, you get a small glyph in the middle of the top bar next to the clock. That's the indicators widget, and it carries dictation, screen recording, pending reminders, night light, do not disturb, and stay awake.
|
||||
|
||||
Inactive indicators are hidden. Hover the area around them and they fade in dimmed, so you can click one to turn it on without knowing its hotkey. Clicking an active one turns it back off. If you'd rather see all of them all the time, set `alwaysShow` to `true` on the `omarchy.indicators` entry in `~/.config/omarchy/shell.json` — see [the top bar](05-the-top-bar.md) for how bar widgets are configured.
|
||||
|
||||
### Night light
|
||||
|
||||
`Super + Ctrl + N` warms the screen to 4000K, and hitting it again puts it back to 6500K. It's driven by hyprsunset, which the toggle starts for you if it isn't already running.
|
||||
|
||||
By default hyprsunset does nothing to your screen at all. `~/.config/hypr/hyprsunset.conf` ships with an identity profile precisely so the display stays untouched until you ask for warmth. If you'd rather have it switch by the clock, replace that with a time profile:
|
||||
|
||||
```
|
||||
profile {
|
||||
time = 20:00
|
||||
temperature = 4000
|
||||
}
|
||||
```
|
||||
|
||||
Then start hyprsunset at login by adding `o.launch_on_start("hyprsunset")` to `~/.config/hypr/autostart.lua`. The 4000K/6500K pair used by the toggle is fixed, so the config file is where you go if you want a different temperature.
|
||||
|
||||
### Do not disturb
|
||||
|
||||
`Super + Ctrl + ,` silences notifications. No toasts pop up while it's on, and the crossed-out bell indicator sits in the bar to remind you why the desktop has gone quiet.
|
||||
|
||||
Nothing is lost, though. A silenced notification is written straight into your notification history, which is exactly the record you want when you come back and wonder what you missed. Open it with `Super + Shift + Alt + ,`. See [notices](10-notices.md) for the rest of the notification story.
|
||||
|
||||
Two kinds of message still get through: Omarchy's own confirmation toasts for something you just did ("Theme changed", "Screenshot saved"), and critical alerts sent from the command line. Chat apps that mark everything critical to force their way in front of you don't qualify.
|
||||
|
||||
### Idle
|
||||
|
||||
The Omarchy shell owns idle behavior, and the timings are a top-level `idle` block in `~/.config/omarchy/shell.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"idle": {
|
||||
"screensaver": 150,
|
||||
"lock": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Both numbers are seconds counted from the moment you went idle — not from each other. So with the defaults, the screensaver comes up after two and a half minutes and the lock screen takes over at five minutes, whether or not the screensaver ran. Save the file and the shell picks up the new timings right away.
|
||||
|
||||
If you dismiss the screensaver before the lock deadline, that counts as activity and the pending lock is cancelled. You don't get locked out for glancing at your machine.
|
||||
|
||||
To stop locking on idle entirely, `Super + Ctrl + I` — or `omarchy toggle idle` — flips stay awake on, and the coffee cup indicator appears in the bar. That's the one to hit before a long presentation or a build you want to watch. Hit it again to go back to normal. `omarchy toggle idle status` prints the current state as JSON if you need it from a script.
|
||||
|
||||
This is about locking and the screensaver, not power. Suspend and hibernation have their own setup in [system sleep](36-system-sleep.md).
|
||||
|
||||
### The screensaver
|
||||
|
||||
Omarchy's screensaver is ASCII art running through random text effects, one instance per monitor. Any key or mouse movement exits it.
|
||||
|
||||
You can start it on demand from _System > Screensaver_ (`Super + Esc`), which forces it up even if you've turned the idle screensaver off. There's no hotkey bound to it by default.
|
||||
|
||||
`omarchy toggle screensaver` is what turns the idle one off, if you'd rather go straight from working to locked. It needs a terminal it knows how to configure — Alacritty, Foot, Ghostty, or Kitty — and will tell you so if your default terminal is something else.
|
||||
|
||||
The logo it draws is yours to change, under _Style > Screensaver_. Upload a png or svg and Omarchy converts it to ASCII. See [branding](41-branding.md).
|
||||
|
||||
### The lock screen
|
||||
|
||||
`Super + Ctrl + L` locks the machine. That runs the lock screen from the Omarchy shell, blanks the display, resets your keyboard layout to the first one so you're not typing your password in the wrong alphabet, and — if you have it running — locks 1Password on the way out.
|
||||
|
||||
The lock screen takes a password, and it'll take a fingerprint too once you've set one up. That, and the other ways to authenticate, are covered in [hardware authentication](37-hardware-authentication.md).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Omarchy CLI
|
||||
|
||||
Omarchy is usually controlled through the hotkeys and the Omarchy menu (`Super + Space`). But you can also control it through the `omarchy` CLI. This is particularly helpful when you're having an AI agent work with you on customization or configuration.
|
||||
|
||||
The CLI has access to all the internal tooling that is used both via the menu and otherwise. You can see everything that's available by running `omarchy` in the terminal.
|
||||
|
||||
It looks something like this:
|
||||
|
||||
```
|
||||
~ ❯ omarchy
|
||||
Omarchy command center
|
||||
|
||||
Usage:
|
||||
omarchy <command> [args...]
|
||||
omarchy commands [--all] [--json] [--check]
|
||||
omarchy <group> --help
|
||||
omarchy <group> <command> --help
|
||||
|
||||
Common commands:
|
||||
omarchy update Update Omarchy and system packages
|
||||
omarchy theme list List available themes
|
||||
omarchy theme set <name> Apply a theme
|
||||
omarchy font list List available fonts
|
||||
omarchy screenshot Take a screenshot
|
||||
omarchy debug Print debugging information
|
||||
|
||||
Groups:
|
||||
agent AI coding agent usage data
|
||||
audio Audio input and output controls
|
||||
bar Omarchy shell bar layout and settings
|
||||
battery Battery status helpers
|
||||
bluetooth Bluetooth device controls
|
||||
branch Omarchy git branch management
|
||||
branding About and screensaver branding
|
||||
brightness Display and keyboard brightness
|
||||
capture Screenshots and screen recording
|
||||
channel Omarchy release channel management
|
||||
clipboard Clipboard helpers
|
||||
cmd Command and shortcut helpers
|
||||
config System configuration helpers
|
||||
debug Diagnostics and support logs
|
||||
...
|
||||
```
|
||||
|
||||
And you can dive deeper on every group:
|
||||
|
||||
```
|
||||
~ ❯ omarchy capture
|
||||
Capture commands — Screenshots and screen recording:
|
||||
omarchy capture qr Decode a QR code from a screenshot region
|
||||
omarchy capture screenrecording [--fullscreen] [--with-desktop-audio] [--with-microphone-audio] [--with-webcam] [--webcam-device=<device>] [--webcam-size=<small|medium|large>] [--resolution=<size>] [--stop-recording] Start or stop screen recording
|
||||
omarchy capture screenrecording with webcam Pick a webcam and start a screen recording with it
|
||||
omarchy capture screenshot [smart|region|windows|fullscreen] [slurp|copy|save] [--editor=<name>] Take a screenshot
|
||||
omarchy capture text Extract text from a screenshot region with OCR
|
||||
omarchy capture webcam resize <smaller|larger|reset|small|medium|large> Resize the active webcam recording overlay
|
||||
```
|
||||
|
||||
Every command takes `--help` too, whether you ask a whole group (`omarchy capture --help`) or a single command (`omarchy capture screenshot --help`).
|
||||
|
||||
### Opening the menu from the terminal
|
||||
|
||||
The Omarchy menu is scriptable as well, which is handy for your own keybindings. `omarchy menu` opens it at the root, and you can jump straight to any point in the tree by naming it: `omarchy menu summon style.theme` goes right to the theme picker, `omarchy menu toggle system` opens the system menu and closes it again if it's already up, and `omarchy menu close` puts it away.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Terminal
|
||||
|
||||
[Foot](https://codeberg.org/dnkl/foot) is the default terminal for Omarchy. It's fast, lightweight, and compatible with even old computers. It does not, however, support native tabs or splits.
|
||||
|
||||
If you use Tmux, you may not mind, but if not, we fully support _Alacritty_, _Ghostty_, and _Kitty_ as options as well. Pick your preference under _Install > Terminal_ in the Omarchy menu.
|
||||
|
||||
You start a new terminal using `Super + Return`. (This binding will automatically point to whichever Terminal you've installed via _Install > Terminal_, and you can switch between installed terminals under _Setup > Defaults > Terminal_.)
|
||||
|
||||
## Tmux
|
||||
|
||||
Tmux provides a consistent, programmable interface for panes, windows (aka tabs), and resumable sessions regardless of your terminal. It even works on remote hosts, so when you're SSH'ing into a server, you can use the same approach.
|
||||
|
||||
You start a new Tmux session in a fresh terminal using `Super + Alt + Return`, and because Tmux is a persistent process, you can resume your session even if you close that terminal. Just hit `Ctrl + Space` (called the prefix key) then `s` to see all your active sessions.
|
||||
|
||||
Omarchy ships with an ergonomically-optimized Tmux configuration, which has a lot of keybindings to learn, so keep [the cheatsheet handy](07-hotkeys.md#tmux).
|
||||
|
||||
## Tmux layout functions
|
||||
|
||||
Because Tmux is programmable, we can use functions to create layouts. Omarchy ships with four different functions for common developer layouts.
|
||||
|
||||
`tdl [agent]` starts a three-way split IDE-like interface with the `$EDITOR` on the left, your chosen AI agent on the right (like `c` for opencode or `cx` for Claude or `codex` for OpenAI), and then a terminal at the bottom.
|
||||
|
||||
So `tdl c` would start this (or just `ic`):
|
||||
|
||||

|
||||
|
||||
You can also start a second agent with `tdl c cx` (opencode + claude) (or just `icx`):
|
||||
|
||||

|
||||
|
||||
There's also `tds`, which starts a four-way square with the editor top left, a live diff watcher top right, a terminal bottom left, and opencode bottom right.
|
||||
|
||||
You can also start this layout configuration for every subdirectory in the current directory using `tdlm [agent]`, then navigate using `alt + 1/2/3/4/5/...`:
|
||||
|
||||

|
||||
|
||||
Finally, you can start a swarm of agents using `tsl [panes] [command]`. So `tsl 4 c` will give you a four-way grid of opencode agents:
|
||||
|
||||

|
||||
@@ -0,0 +1,42 @@
|
||||
# Neovim
|
||||
|
||||
[Neovim](https://neovim.io/) is a modern implementation of [the vi editor](<https://en.wikipedia.org/wiki/Vi_(text_editor)>) created by Bill Joy all the way back in 1976. It's a modal editor where insert mode and command mode are separated, and it's a bit of a superpower once you learn even just a subset of the incredibly deep key command set. But it's also quite the learning curve!
|
||||
|
||||
If you're totally new to vim-style editing, I recommend you checkout [ThePrimeagen's Vim As Your Editor series](https://www.youtube.com/watch?v=X6AR2RMB5tE&list=PLm323Lc7iSW_wuxqmKx_xxNtJC_hJbQ7R) on YouTube. That'll teach you the basics. Just know that unlike more similar mainstream editors, it's going to take you longer to get basic proficiency with vim. But once you do, the payoff is also larger.
|
||||
|
||||
Now Neovim is basically infinitely configurable. If you really want to go nuts, you can create your own Neovim configuration from scratch. There's a great course from [Typecraft on setting up Neovim from scratch](https://www.youtube.com/watch?v=zHTeCSVAFNY). And [ThePrimeagen has one as well](https://www.youtube.com/watch?v=w7i4amO_zaE).
|
||||
|
||||
But Omarchy ships with a complete Neovim setup — the `omarchy-nvim` package — that's been lovingly tuned to showcase the best of what's possible out of the box. Without you having to write a single line of configuration! It's built on [LazyVim](https://www.lazyvim.org/), a distribution of Neovim plugins and configurations. It's awesome.
|
||||
|
||||
## LazyVim Basics
|
||||
|
||||
As mentioned, I'm not going to teach you vim in this short introduction, but I can show you a few basics of LazyVim, and how to get around.
|
||||
|
||||
First, Neovim has the idea of the leader key. That's basically the gateway to all the commands. LazyVim has set that to `Space`. So just press that, wait a second, and you'll see a bunch of options explained inline like this:
|
||||
|
||||
Here are some basic commands I use all the time:
|
||||
|
||||
- `Space Space` - Fuzzy-find any file in the current directory.
|
||||
- `Space S G` - Search all files using grep with a preview.
|
||||
- `Space E` - Toggle the file tree on/off.
|
||||
- `Ctrl + W W` - Hop from the file tree to the editor and back.
|
||||
- `Shift + H` - Move left between the open tabs (vim calls them buffers).
|
||||
- `Shift + L` - Move right between the open tabs.
|
||||
- `Space B D` - Close a tab.
|
||||
- `Space B O` - Close all other tabs but the current.
|
||||
- `Space G G` - Launch LazyGit in a floating pane from the current directory.
|
||||
- `Space U W` - Toggle soft wrap.
|
||||
|
||||
While you're in the file tree (`Space E` to reveal, `Ctrl + W W` to hop over there), you can add a new file with `a` or a new directory with `A`. Press `?` while in the tree to see all commands.
|
||||
|
||||
If you want to pickup the basic vim language, I've written about [the three-part syntax](https://world.hey.com/dhh/wonderful-vi-a1d034d3) and how to pull off those sick combo moves!
|
||||
|
||||
You can see all the possible commands on [the LazyVim Keymaps page](https://www.lazyvim.org/keymaps).
|
||||
|
||||
## Starting Neovim
|
||||
|
||||
You can start Neovim using `Super + Shift + N` (the binding launches your default editor, which is Neovim out of the box), but it's usually easier to drive it from the terminal by navigating to the directory you wish to work in and typing `n`. The `n` is the alias for `nvim`, which will use the the present directory to open by default. You can open a single file with `n myfile.txt`.
|
||||
|
||||
## Using Neovim for sudo edits
|
||||
|
||||
If you need to edit files that you can only change as a super user, you can use neovim with all your plugins setup by running `sudoedit /etc/sudoers.d/00-sudo-only-file`.
|
||||
@@ -0,0 +1,51 @@
|
||||
# AI
|
||||
|
||||
Omarchy treats AI coding agents as first-class citizens, but it doesn't pick a favorite for you. Instead, every major coding-agent CLI comes pre-wired as a lazy-loaded launcher. The launchers are tiny [mise](https://mise.jdx.dev/)-managed stubs in `~/.local/bin/`, so nothing is downloaded until the first time you actually run one. Invoke any of these and authenticate when prompted:
|
||||
|
||||
| Command | Agent |
|
||||
|------------|------------------------------------------------------------------|
|
||||
| `claude` | [Claude Code](https://code.claude.com/docs/en/overview) |
|
||||
| `codex` | [OpenAI Codex](https://github.com/openai/codex) |
|
||||
| `opencode` | [OpenCode](https://opencode.ai/) |
|
||||
| `gemini` | [Google Gemini CLI](https://github.com/google-gemini/gemini-cli) |
|
||||
| `copilot` | [GitHub Copilot CLI](https://github.com/github/copilot-cli) |
|
||||
| `crush` | [Crush](https://github.com/charmbracelet/crush) |
|
||||
| `grok` | Grok CLI from xAI |
|
||||
| `pi` | [Mario Zechner's Pi](https://github.com/badlogic/pi-mono) |
|
||||
| `omp` | [Oh My Pi](https://github.com/can1357/oh-my-pi) |
|
||||
|
||||
To wrap an additional CLI the same way, run `omarchy-mise-install <package> [command-name]`. The stubs are kept current along with everything else mise manages when you run `omarchy update` (or the `mup` alias).
|
||||
|
||||
### The default agent
|
||||
|
||||
Pick your default agent with `omarchy default agent <name>` or under _Setup > Defaults > Agent_ in the Omarchy Menu (`Super + Space`). If the agent isn't installed yet, picking it installs it first. A fresh Omarchy will invite you to make this choice with a one-time notification.
|
||||
|
||||
Once you've chosen, `Super + Shift + Ctrl + A` launches the default agent in a dedicated terminal window (or brings up the picker if you haven't chosen yet). You can also launch it straight into a task with `omarchy agent prompt "Review this project"`. Agents launched this way run unattended in their respective don't-stop-to-ask modes, so be ready for them to actually do things! And since agents refuse to remember trust for your home directory, launches from `$HOME` start in `~/Work` instead.
|
||||
|
||||
There are terminal shortcuts too: `a` runs the default agent inline in the current terminal, while `c`, `cx`, and `cy` start OpenCode, Claude Code, and Codex directly (again in their auto-approving modes). Theme changes sync to the agents as well: Claude Code, Pi, and OpenCode all follow along when you switch the Omarchy theme.
|
||||
|
||||
### The agents panel
|
||||
|
||||
The top bar grows an agents icon the first time Omarchy finds AI coding usage on the machine (and stays out of the way until then). The panel behind it tracks every subscription in one place: your plan, the percentage used of the 5-hour session and weekly limits (or the remaining prepaid balance), and token usage by day and by model. Claude Code, Codex, and Fireworks are covered out of the box.
|
||||
|
||||
Left-click the bar icon for the panel, right-click to launch your default agent. The usage records behind it are regenerated every 15 minutes by `omarchy agent usage-update`, and the panel can even merge usage from your other machines via a synced folder. See the README under `$OMARCHY_PATH/shell/plugins/agents/` for the full settings.
|
||||
|
||||
### Crash diagnosis
|
||||
|
||||
Omarchy watches systemd-coredump for process crashes. When something segfaults, you'll get a "Process crashed" notification — click it, and the crash is handed to your default agent along with Omarchy's diagnose-crash skill, which walks the agent through establishing the facts from the core dump and deciding whether the crash is worth reporting upstream. You can also run it by hand against any PID from `coredumpctl list` with `omarchy agent crash <pid>`.
|
||||
|
||||
The watching is on by default. Turn it off under _Trigger > Toggle > Crash Capture_ (or with `omarchy toggle crash-capture`) and the notifications stop; `omarchy agent crash <pid>` still works by hand.
|
||||
|
||||
### Desktop apps
|
||||
|
||||
The _Install > AI_ menu also carries a couple of graphical AI apps: the ChatGPT desktop app, and Grok Bot for chatting with xAI's models.
|
||||
|
||||
### Local LLMs
|
||||
|
||||
Omarchy recommends two ways of running local LLM models: LM Studio and Ollama. LM Studio provides a GUI interface for finding open-weight models, installing them, and running them. It's a great way to get going easily. Ollama offers a CLI for doing so similarly. But if you're new to local models, I'd start with LM Studio. You can install either under _Install > AI_ in the Omarchy Menu.
|
||||
|
||||
### The Omarchy Skill
|
||||
|
||||
Agent skills help AI use specific tools in a specific way, and Omarchy ships with a default skill for tailoring the system. Like tweaking your Hyprland config, adjusting the bar, or even creating a new theme from scratch. It's symlinked into the skill directories for Claude Code (`~/.claude/skills`), Codex (`~/.codex/skills`), Pi (`~/.pi/agent/skills`), and the generic `~/.agents/skills` location, so most harnesses pick it up automatically.
|
||||
|
||||
But you should treat this skill as experimental. Different models will use it to different effect. It's best to run in plan mode first, so you have an idea of what the agent would like to change. And then be ready to rollback changes or even invoking `omarchy reinstall configs`, if the agent makes a mess of everything.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Development Tools
|
||||
|
||||
## Alternative Editors
|
||||
|
||||
Omarchy ships with [Neovim](https://neovim.io/) by default, but if you'd like something a bit more mainstream and familiar, you can run the Omarchy Menu (`Super + Space`) and see the options under _Install > Editor_. We have VSCode, Cursor, Zed, Sublime Text, Helix, Vim, and Emacs listed there. If you don't find what you're looking for, checkout _Install > Package_, and see if it isn't in an Arch package (and if not, try _Install > AUR_ to check the AUR).
|
||||
|
||||
Theme matching is offered for `VSCode`, `Cursor`, `VSCodium`, and `Helix`.
|
||||
|
||||
You can set the system-wide default editor under `Setup > Defaults > Editor`.
|
||||
|
||||
## Environment
|
||||
|
||||
Omarchy supports setting up a whole host of development environments through the _Install > Development_ section of the Omarchy Menu (`Super + Space`). You'll of course find _Ruby on Rails_, but also all three major runtimes for JavaScript (Node.js, Bun, Deno), as well as popular PHP frameworks like Laravel and Symfony. Oh, and there's Go, Rust, Python, Java, Elixir (with Phoenix), .NET, OCaml, Zig, Clojure, and Scala too. It's a very broad selection!
|
||||
|
||||
The majority of these environments are managed by [Mise](https://mise.jdx.dev/). It's a tool that lets you install and run multiple versions of a programming language on the same machine. It's like rbenv or rvm for Ruby or virtualenv for Python, but it works for a bunch of different environments.
|
||||
|
||||
To install, say, Ruby, you'd run `mise use -g ruby`, which will both install Ruby and set it as the global default. Or, if your project has a .ruby-version file, you can just run `mise i` in the root of that project.
|
||||
|
||||
## Docker
|
||||
|
||||
[Docker](https://www.docker.com/) hardly needs any introduction. It allows you to run isolated containers, and Omarchy installs everything needed to run it well. This includes Docker itself, [Docker Compose](https://docs.docker.com/compose/), and the user group changes needed for you to run Docker as the normal user and not as root.
|
||||
|
||||
Remember to checkout the Lazydocker command to manage your containers in a cool TUI using `Super + Shift + D`.
|
||||
|
||||
You can setup the common databases for local development in Docker using _Install > Development > Docker DB_ in the Omarchy menu.
|
||||
|
||||
## GitHub CLI
|
||||
|
||||
[The GitHub CLI](https://cli.github.com/) let's you authenticate with your GitHub account and clone private repositories using it. It's wired up as one of the lazy-loading mise stubs, so the first time you run `gh`, it installs itself. To authenticate, run `gh auth login`. Then you can checkout private repositories using `gh repo clone org/repo`.
|
||||
|
||||
You can also perform a bunch of other GitHub operations using this command. Just run `gh` to see everything that's possible.
|
||||
|
||||
There's a lazy-installing stub for `ghui` for managing your pull requests in a TUI too. And [lazygit](https://github.com/jesseduffield/lazygit) is preinstalled, if you'd like to drive git itself from a TUI as well.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Shell Tools
|
||||
|
||||
In addition to the standard Linux tools, Omarchy also ships with a bunch of enhanced shell tools. Here are the key ones.
|
||||
|
||||
## fzf
|
||||
|
||||
[fzf](https://junegunn.github.io/fzf/) gives you fuzzy finding of files via the `ff` alias. Go to any directory, type `ff`, and you'll be able to fuzzy find your way to any file in that tree, while seeing a preview of the files you're narrowing down on the right-hand side.
|
||||
|
||||
You can use `Ctrl + R` to use fzf to fuzzy find through your command history.
|
||||
|
||||
This tool is also used by Neovim when you type `Space Space`.
|
||||
|
||||
The full manual can be found via `man fzf`.
|
||||
|
||||
## Zoxide
|
||||
|
||||
[Zoxide](https://github.com/ajeetdsouza/zoxide) is a replacement for cd. It remembers the directories you've been in, so you can more easily jump to them next time. Say you do `cd ~/.config/omarchy` once. Next time, you can just do `cd omarchy` (or even just `cd oma`), and Zoxide will take you directly there.
|
||||
|
||||
The full manual can be found via `man zoxide`.
|
||||
|
||||
## ripgrep
|
||||
|
||||
[ripgrep](https://github.com/BurntSushi/ripgrep) searches the contents of files by using `rg <pattern> <path>`, like `rg Controller app/` to find all mentions of `Controller` in the directory app.
|
||||
|
||||
This tool is also used by Neovim when you type `Space S G`.
|
||||
|
||||
The full manual can be found via `man rg`.
|
||||
|
||||
## eza
|
||||
|
||||
[eza](https://eza.rocks/) is a replacement for ls. It gives you directory listings with more information, color, and icons. By default, eza has been aliased as ls. You can also use `lt` to get a listing of two-deep levels of nesting. `lsa` gives you a listing including hidden files. And `lta` a nested listing with hidden files.
|
||||
|
||||
The full manual can be found via `man eza`.
|
||||
|
||||
## fd
|
||||
|
||||
[fd](https://github.com/sharkdp/fd) is an easier to use replacement for `find`. Use `fd person.rb` to find a file called `person.rb` within the current tree. `fd person.rb /` will search the entire file system. `fd person.rb / -H` searches the entire file system, including hidden directories.
|
||||
|
||||
The full manual can be found via `man fd`.
|
||||
|
||||
## bat
|
||||
|
||||
[bat](https://github.com/sharkdp/bat) is `cat` with syntax highlighting, line numbers, and paging. Run `bat somefile.rb` and you'll see why it's hard to go back. It's also quietly doing work for you elsewhere: it's what colors your man pages, and what renders the previews in `ff`.
|
||||
|
||||
The full manual can be found via `man bat`.
|
||||
|
||||
## tldr
|
||||
|
||||
[tldr](https://tldr.sh/) is the antidote to man pages that open with three screens of history before showing you a single example. `tldr tar` gives you the handful of invocations you actually wanted.
|
||||
|
||||
## yt-dlp
|
||||
|
||||
[yt-dlp](https://github.com/yt-dlp/yt-dlp) downloads video from YouTube and hundreds of other sites. `yt-dlp <url>` grabs the best quality available into the current directory.
|
||||
|
||||
The full manual can be found via `man yt-dlp`.
|
||||
|
||||
## try
|
||||
|
||||
[try](https://github.com/tobi/try) makes it easy to manage programming experiments with date-stamped directories. All experiments live in `~/Work/tries` and you can access them via `try`.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Shell Functions
|
||||
|
||||
Omarchy comes with a set of shell functions to simplify common tasks and encapsulate convoluted parameter calls.
|
||||
|
||||
## Compression
|
||||
|
||||
- `compress [file/dir]`: Create a tar.gz archive from the file/dir.
|
||||
- `decompress [file.tar.gz]`: Expand a tar.gz file.
|
||||
|
||||
## Drives
|
||||
|
||||
- `iso2sd [image.iso]`: Create a bootable drive on an SD card using the referenced iso file and picking the drive interactively.
|
||||
- `format-drive [device] [name]`: Format an entire disk with a single exFAT partition (which works on Windows and macOS too). Run it without arguments to see the available drives. Be careful!
|
||||
|
||||
## Dev layouts
|
||||
|
||||
Instant multi-pane development layouts for tmux:
|
||||
|
||||
- `tdl [ai]`: Create a Tmux Dev Layout with your editor, an AI agent, and a terminal. Use the agent aliases, like `tdl c` for opencode or `tdl cx` for Claude Code, or pass a second agent to run both, like `tdl c cx`.
|
||||
- `tds`: Create a Tmux Dev Square with editor, diff watching (via `hunk diff --watch`), terminal, and opencode.
|
||||
- `tdlm [ai]`: Create a `tdl` window for every subdirectory in the current directory.
|
||||
- `tsl [count] [command]`: Create a swarm of panes tiled in a grid, all running the same command (great for AI agents).
|
||||
|
||||
The same layouts are available for Herdr as `hdl`, `hds`, `hdlm`, and `hsl`.
|
||||
|
||||
## Git worktrees
|
||||
|
||||
- `ga [branch]`: Create a new worktree and branch next to the current repository and jump into it.
|
||||
- `gd`: Remove the current worktree and its branch (asks for confirmation first).
|
||||
|
||||
## Rsync watchers
|
||||
|
||||
- `rsw [source] [destination]`: Start a background watcher that rsyncs source to destination whenever anything changes. The destination can be a remote host, like `rsw ~/Work/app nyc-dev:Work/app`.
|
||||
- `lsw`: List all active watchers.
|
||||
- `dsw`: Stop all active watchers.
|
||||
|
||||
## SSH Portforwarding
|
||||
|
||||
Ideal for doing web development with localhost secure-context privileges against a remote box.
|
||||
|
||||
- `fip`: Forward one or more ports from a remote host to localhost via SSH.
|
||||
- `dip`: Disconnect one or more forwarded ports.
|
||||
- `lip`: List all active SSH port forwards.
|
||||
|
||||
Say you start a dev server on port `3000` on a machine accessible as `nyc-dev`, then you can run `fip nyc-dev 3000` to forward that port, so `localhost:3000` actually reaches `nyc-dev:3000`, but without the need for SSL certificates to establish the secure context needed for testing web sockets or the like.
|
||||
|
||||
## SSH reconnection
|
||||
|
||||
`ssh` itself is wrapped in a function that cleans up the terminal if a connection dies while a remote tmux, Herdr, or editor has claimed it, and then automatically reconnects when an interactive session drops (Ctrl-C stops the retry loop).
|
||||
@@ -0,0 +1,51 @@
|
||||
# TUIs
|
||||
|
||||
## Lazygit
|
||||
|
||||
[Lazygit](https://github.com/jesseduffield/lazygit) is a delightful alternative to something like the GitHub Desktop application, and it runs inside the terminal.
|
||||
|
||||
You can run it directly, by going to any directory managed by git and running `lazygit`. Or you can run it inside Neovim where it can be started with `Space G G`.
|
||||
|
||||
You hop between the different panes using `Tab`. In the Files pane, you select files for staging using `Space`, and then you can create a new commit using `c`. You can see all the commands available using `?`.
|
||||
|
||||
## Lazydocker
|
||||
|
||||
[Lazydocker](https://github.com/jesseduffield/lazydocker) is made in the same spirit like Lazygit, and also gives you a terminal interface for managing your containers and images.
|
||||
|
||||
You can start it with `Super + Shift + D`.
|
||||
|
||||
You stop a container using `s` or start/restart it using `r`. See all commands using `?`.
|
||||
|
||||
## Btop
|
||||
|
||||
[Btop](https://github.com/aristocratos/btop) is a beautiful resource manager that shows memory, CPU, disk, and network usage. It also lists all active processes, and allows you to manage them.
|
||||
|
||||
Omarchy calls it Activity, and you start it by hitting `Super + Ctrl + T`. It opens as a floating window, which you can tile with `Super + T`.
|
||||
|
||||
## Herdr
|
||||
|
||||
[Herdr](https://github.com/omacom-io/herdr) is a terminal workspace manager that gives you workspaces, tabs, and panes, and keeps them all running in a persistent session you can detach from and come back to later.
|
||||
|
||||
You start it (or reattach to your existing session) with `Super + Ctrl + Return`. Omarchy ships a Herdr configuration that mirrors its Tmux config, so the prefix key is `Ctrl + Space` here too. You can browse all the keybindings with `Super + Ctrl + K`.
|
||||
|
||||
## Fastfetch
|
||||
|
||||
[Fastfetch](https://github.com/fastfetch-cli/fastfetch) shows system information, like kernel version, uptime, theme, CPU, memory, and more. It's a successor to the popular neofetch tool.
|
||||
|
||||
Omarchy has packaged this as _About_ in the Omarchy menu (`Super + Space`).
|
||||
|
||||
## Disk Usage
|
||||
|
||||
When the drive fills up and you have no idea what's eating it, launch _Disk Usage_ from the app launcher (`Super + Space`). It's [dua](https://github.com/Byron/dua-cli) in interactive mode pointed at the whole file system, so you can walk down into whatever directory is the culprit, sorted biggest first, and delete from right inside it.
|
||||
|
||||
## Cliamp
|
||||
|
||||
[Cliamp](https://www.cliamp.stream/) is a retro terminal music player inspired by Winamp 2.x, complete with built-in radio stations for lo-fi beats. Launch it with `Super + Shift + Alt + M`, or from the Omarchy menu under _Apps_. Press `?` for the full keybinding list.
|
||||
|
||||
## What about Wi-Fi and Bluetooth?
|
||||
|
||||
You won't find TUIs for Wi-Fi and Bluetooth — those jobs belong to the Omarchy shell. Click the Wi-Fi icon in the top bar (or hit `Super + Ctrl + W`) to see networks and connect, and click the Bluetooth icon (or hit `Super + Ctrl + B`) to pair and connect devices. See [networking](35-networking.md) for the full story.
|
||||
|
||||
## Adding your own
|
||||
|
||||
Any terminal program can get the full app treatment. Go to _Install > TUI_ in the Omarchy menu (`Super + Space`), give it a name, a launch command, a window style, and an icon, and it'll show up in the app launcher like any other application. You can remove it again under _Remove > TUI_.
|
||||
@@ -0,0 +1,96 @@
|
||||
# GUIs
|
||||
|
||||
## Files
|
||||
|
||||
Files (Nautilus) is the graphical file manager. `Super + Shift + F` opens it, and `Super + Shift + Alt + F` opens it in the directory your terminal is sitting in, which saves a lot of clicking. `Ctrl + L` lets you type a path, and hitting `Space` on any file gives you a quick preview without opening anything.
|
||||
|
||||
Plug in a USB stick or an SD card and it's mounted automatically, so it just shows up in the sidebar. For anything more involved — formatting a drive, checking SMART health, creating partitions — launch _Disks_ from the app launcher (`Super + Space`).
|
||||
|
||||
Double-clicking follows sensible defaults: images open in imv, video in mpv, PDFs in Document Viewer, and plain text in Neovim.
|
||||
|
||||
## Obsidian
|
||||
|
||||
[Obsidian](https://obsidian.md/) is a free and highly extensible note taking application that uses simple Markdown files for storage.
|
||||
|
||||
Obsidian is free for all purposes, including personal, commercial, and non-profit use.
|
||||
|
||||
Obsidian also offers a [commercial add-on for syncing](https://obsidian.md/sync) with mobile apps on iOS and Android. (https://obsidian.md/pricing).
|
||||
|
||||
You start Obsidian with `Super + Shift + O`. To use theme syncing, you must select the `Omarchy` theme under settings.
|
||||
|
||||
## Omawrite
|
||||
|
||||
[Omawrite](https://github.com/omacom-io/omawrite) is Omarchy's own dead-simple Markdown writing app. No vaults, no plugins, just you and the words.
|
||||
|
||||
You start Omawrite with `Super + Shift + W`.
|
||||
|
||||
## Pinta
|
||||
|
||||
[Pinta](https://www.pinta-project.com/) is a basic image editing tool that's great for cropping, resizing, and other basic manipulations. Just don't expect a Photoshop alternative. But it's still got a Magic Wand and layers!
|
||||
|
||||
You start Pinta via the application launcher (`Super + Space`).
|
||||
|
||||
## Aether
|
||||
|
||||
[Aether](https://github.com/bjarneo/aether) is a theming application that can extract colors from a background image and turn them into a complete, cohesive theme. It's the easiest way to [make your own theme](43-making-your-own-theme.md).
|
||||
|
||||
You start Aether via the application launcher (`Super + Space`).
|
||||
|
||||
## LocalSend
|
||||
|
||||
[LocalSend](https://localsend.org/) lets you send files to other devices on the same network running the app, like Apple's AirDrop. It's cross-platform, though, so you can send files to and from Windows, macOS, Android, iOS, and of course Linux.
|
||||
|
||||
You can open the Share menu on `Super + Ctrl + S` or under _Trigger > Share_ in the Omarchy menu. It gives you four options:
|
||||
|
||||
- **Clipboard** sends whatever you've copied as a text file. Great for getting a link or a snippet onto your phone without emailing yourself.
|
||||
- **File** opens a file picker where you can select several at once.
|
||||
- **Folder** sends an entire directory.
|
||||
- **Receive** opens LocalSend proper so another device can send something to you.
|
||||
|
||||
The same thing works from the terminal with `omarchy share clipboard`, `omarchy share file [path]`, and `omarchy share folder [path]`. Leave the path off and you get the picker.
|
||||
|
||||
You can also send straight from the file manager: right-click any selection in Nautilus and pick _Send via LocalSend_.
|
||||
|
||||
Omarchy's firewall is closed by default except for LocalSend's port, so this works out of the box on a fresh install. See [security](48-security.md).
|
||||
|
||||
## LibreOffice
|
||||
|
||||
[LibreOffice](https://www.libreoffice.org/) is a complete office package with word processor, spreadsheet, presentations, drawing application, and more. It's compatible with files from Microsoft Office, so this is a great way to be able to open those Word documents.
|
||||
|
||||
You start LibreOffice via the application launcher (`Super + Space`).
|
||||
|
||||
## Omacalc
|
||||
|
||||
[Omacalc](https://github.com/omacom-io/omacalc) is Omarchy's own dead-simple calculator, which opens in a floating window.
|
||||
|
||||
You start Omacalc with `Super + Ctrl + Q` (or the calculator key, if your keyboard has one).
|
||||
|
||||
## Signal
|
||||
|
||||
[Signal](https://signal.org/) is the pioneer of E2E encrypted messaging, and a great communication option for anyone who'd prefer not to go through one of the big tech conglomerates.
|
||||
|
||||
You start Signal with `Super + Shift + G`. It's not part of the base install, so the first time you hit that, Omarchy will offer to install it for you (it's also under _Install > Service_ in the Omarchy menu).
|
||||
|
||||
## mpv
|
||||
|
||||
[mpv](https://mpv.io/) is a simple, fast media player that'll play almost anything from any source. Great for watching videos.
|
||||
|
||||
You start mpv via the application launcher (`Super + Space`) or just double-click on a video in the file manager.
|
||||
|
||||
## OBS Studio
|
||||
|
||||
[OBS Studio](https://obsproject.com/) lets you record or stream video from multiple inputs. You can mix a screencast with a webcam with a microphone input. It's what was used to record the Omarchy screencasts.
|
||||
|
||||
You start OBS Studio via the application launcher (`Super + Space`).
|
||||
|
||||
## Kdenlive
|
||||
|
||||
[Kdenlive](https://kdenlive.org/) is an excellent video editor. Perfect for working on video that comes out of OBS Studio before sharing it.
|
||||
|
||||
You start Kdenlive via the application launcher (`Super + Space`).
|
||||
|
||||
## Omacut
|
||||
|
||||
[Omacut](https://github.com/omacom-io/omacut) is Omarchy's own dead-simple video trimmer. When all you need is to cut the start and end off a clip, it beats firing up a full video editor.
|
||||
|
||||
You start Omacut via the application launcher (`Super + Space`).
|
||||
@@ -0,0 +1,39 @@
|
||||
# Browsers
|
||||
|
||||
Omarchy ships with [Chromium](https://www.chromium.org/) as the default browser. It's the plain open-source build, themed to match the rest of your system, and it's what `Super + Shift + Return` opens and what every [web app](25-web-apps.md) runs inside.
|
||||
|
||||
If Chromium isn't your taste, you're not stuck with it. Under _Install > Browser_ in the Omarchy menu you'll find Chrome, Edge, Brave, Brave Origin, Firefox, and [Zen](https://zen-browser.app/). Pick one and Omarchy installs it, sets up its policy directory, and applies your current theme to it.
|
||||
|
||||
## Making one the default
|
||||
|
||||
Installing a browser doesn't promote it. Once it's on the machine, go to _Setup > Defaults > Browser_ and pick it — the menu only lists browsers you actually have installed, and marks the current default with a check.
|
||||
|
||||
From the terminal it's:
|
||||
|
||||
```bash
|
||||
omarchy default browser firefox
|
||||
```
|
||||
|
||||
Run it with no argument and it tells you the current default. This sets the XDG handler, so it's not just Omarchy's hotkeys that follow along — anything that opens a link, from a chat app to a terminal command, goes to the browser you picked.
|
||||
|
||||
## Copy URL and Download Video
|
||||
|
||||
The Chromium-family browsers (Chromium itself, Chrome, Edge, and Brave) come with two Omarchy extensions that reach out of the browser and into the rest of your system.
|
||||
|
||||
**Copy URL** puts the current tab's address on your clipboard with `Alt + Shift + L`. That's faster than clicking into the address bar and copying, and because it goes through the system clipboard rather than the browser's, you get an Omarchy notification confirming it and the URL is immediately available in [clipboard history](08-unified-clipboard-history.md) and every other app. There's a toolbar button too, if you prefer clicking.
|
||||
|
||||
**Download Video** grabs the video playing on the page you're looking at with `Alt + Shift + D`. It hands the URL to [yt-dlp](https://github.com/yt-dlp/yt-dlp), so it works on far more than YouTube, and the download lands in `~/Videos`. Progress shows up in the same on-screen display that volume and brightness use, updating in place rather than stacking up notifications. Set `OMARCHY_YTDLP_DIR` if you'd rather the files went somewhere else — see the [FAQ](46-faq.md) for where to put environment variables.
|
||||
|
||||
Both extensions talk to Omarchy through a small native messaging host, which gets installed for you along with the browser. That's the piece that lets a web page's video end up in your home directory and a URL end up in your clipboard manager, which a normal extension can't do on its own.
|
||||
|
||||
These are Chromium-family only. Firefox and Zen don't get them.
|
||||
|
||||
## Firefox and Zen
|
||||
|
||||
Firefox and Zen are a different family, so they get different treatment: Omarchy installs a policies file for sensible defaults and switches them into native Wayland mode, which you want for fractional scaling and smooth trackpad scrolling.
|
||||
|
||||
They don't get the Chromium extensions above, and they're not themed by Omarchy, so those parts of the experience are yours to set up.
|
||||
|
||||
## Removing one again
|
||||
|
||||
Anything you installed here can be taken back off under _Remove > Browser_. Chromium isn't in that list — it's part of the base system.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Commercial apps/services
|
||||
|
||||
Omarchy is mostly focused on providing free, open source software, but it's not religious about it. Sometimes the best solution is a commercial offering, and that's just fine. Here are some of the options we provide an easy installation for.
|
||||
|
||||
## 1Password
|
||||
|
||||
Keeping your passwords in a password manager is a best practice. Doubly so if you're working with a team. And [1password](https://1password.com/) is a great solution, which also comes with a command line tool for integrating key lookups in scripts.
|
||||
|
||||
You start 1Password with `Super + Shift + /`. If it isn't installed yet, that hotkey kicks off the installation first (you can also use _Install > Service > 1Password_ from the Omarchy menu). The installer sets up the 1Password extension for Chromium as well.
|
||||
|
||||
## Bitwarden
|
||||
|
||||
[Bitwarden](https://bitwarden.com/) is the open source alternative in the password manager space, with a free tier that covers most personal use. Install it with _Install > Service > Bitwarden_ from the Omarchy menu, which brings along the Bitwarden command line tool as well.
|
||||
|
||||
## Spotify
|
||||
|
||||
[Spotify](https://spotify.com/) is the world's most popular streaming music service. And the Linux application provides everything you'd expect, including offline playing.
|
||||
|
||||
You start Spotify using `Super + Shift + M`. Like 1Password, the hotkey kicks off the installation first if Spotify isn't installed yet (or use _Install > Service > Spotify_ from the Omarchy menu).
|
||||
|
||||
## Dropbox
|
||||
|
||||
[Dropbox](https://www.dropbox.com/) is a great way to sync files between machines while keeping a backup in the cloud. To set it up, select _Install > Service > Dropbox_ from the Omarchy menu. Once it's running, hover the tray in the top right of the bar and right-click the Dropbox icon to finish the setup.
|
||||
|
||||
## Tailscale
|
||||
|
||||
[Tailscale](https://tailscale.com/) is a mesh VPN that makes getting access to all your computers and servers over the internet securely super simple. To set it up, select _Install > Service > Tailscale_ from the Omarchy menu.
|
||||
|
||||
It gets a panel in the bar, a web app for the admin console, and Taildrop for sending files between your machines — see [networking](35-networking.md).
|
||||
|
||||
## ONCE
|
||||
|
||||
[ONCE](https://once.com/) is 37signals' line of software you buy once and run on your own server, like the Campfire chat system. Select _Install > Service > ONCE_ from the Omarchy menu to install it, which enables its background service and drops you into the ONCE terminal interface to take it from there.
|
||||
|
||||
## NordVPN
|
||||
|
||||
[NordVPN](https://nordvpn.com/) is a standard VPN service that lets you exit your traffic from most regions around the world. To set it up, select _Install > Service > NordVPN_ from the Omarchy menu. After the reboot it asks for, run `nordvpn login` to authenticate.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Web Apps
|
||||
|
||||
You can add your own web apps using _Install > Web App_ in the Omarchy menu (`Super + Space`). It'll ask you for the app name, app URL, and the icon URL, if it can't retrieve it via favicon. You can get great PNG icons for many popular web apps on [Dashboard Icons](https://dashboardicons.com).
|
||||
|
||||
They'll then be accessible through the app launcher (`Super + Space`), and use the beautiful frameless web-app window.
|
||||
|
||||
If you wish to remove a web app, just go to _Remove > Web App_ in the Omarchy menu.
|
||||
|
||||
It's best if you log into all your accounts using a regular browser before using the web app shortcuts. The thin wrapper frame doesn't work well with 1password, so just easier to be logged in directly first.
|
||||
|
||||
All the keyboard hotkeys for these web apps can be changed in `~/.config/hypr/bindings.lua`.
|
||||
|
||||
When you're in a web app, you can copy the current URL to the clipboard using `Shift + Alt + L`.
|
||||
|
||||
By default, Omarchy already ships with an assortment of default apps:
|
||||
|
||||
## HEY
|
||||
|
||||
[HEY](https://www.hey.com/) is an email and calendar service that serves as a great alternative to people tired of Gmail, Outlook, or Apple Mail. It's made by [37signals](https://37signals.com/) where Omarchy originated.
|
||||
|
||||
You can start HEY Email using `Super + Shift + E`, jump straight to composing a new email using `Super + Shift + Alt + E`, and start HEY Calendar using `Super + Shift + C`.
|
||||
|
||||
## Basecamp
|
||||
|
||||
[Basecamp](https://basecamp.com/) is a project management service that helps small teams move faster and make more progress. Instead of patching together a mishmash of Trello, Slack, Asana, Notion, or whatever, you can have it all in one place with Basecamp. It's made by [37signals](https://37signals.com/) where Omarchy originated.
|
||||
|
||||
You can start Basecamp using the application launcher (`Super + Space`)
|
||||
|
||||
## ChatGPT
|
||||
|
||||
[ChatGPT](https://chatgpt.com) is the most popular AI chat bot in the world.
|
||||
|
||||
You can start ChatGPT using `Super + Shift + A`.
|
||||
|
||||
## Grok
|
||||
|
||||
[Grok](https://grok.com) is xAI's chat bot.
|
||||
|
||||
You can start Grok using `Super + Shift + Alt + A`.
|
||||
|
||||
## WhatsApp
|
||||
|
||||
[WhatsApp](https://www.whatsapp.com/) is one of the most popular messaging services in the world, and the web version is a great option for Linux.
|
||||
|
||||
You can start WhatsApp using `Super + Shift + Alt + G`.
|
||||
|
||||
## Google apps
|
||||
|
||||
Google Messages, Google Photos, Google Maps, and Google Contacts are all included as web apps too.
|
||||
|
||||
You can start Google Messages using `Super + Shift + Ctrl + G`, Google Photos using `Super + Shift + P`, and Google Maps using `Super + Shift + S`. Google Contacts is available through the app launcher (`Super + Space`).
|
||||
|
||||
## X
|
||||
|
||||
X is where news break.
|
||||
|
||||
You can start X using `Super + Shift + X` and go straight to writing a new post with `Super + Shift + Alt + X`.
|
||||
|
||||
## YouTube
|
||||
|
||||
[YouTube](https://youtube.com/) is the most popular video platform in the world.
|
||||
|
||||
You can start YouTube using `Super + Shift + Y`.
|
||||
|
||||
## Zoom
|
||||
|
||||
[Zoom](https://zoom.us/) is the most popular video chat system used in the US. Great connections across the world. And 40-minute meetings can be held without a paying account. Omarchy wraps Zoom's web client, and zoom meeting links will open straight into it.
|
||||
|
||||
You start Zoom using the application launcher (`Super + Space`).
|
||||
|
||||
## Discord
|
||||
|
||||
[Discord](https://discord.com/) is where most gaming and open source communities hang out, including [Omarchy's own](https://discord.gg/tXFUdasqhY).
|
||||
|
||||
You start Discord using the application launcher (`Super + Space`).
|
||||
@@ -0,0 +1,85 @@
|
||||
# Gaming
|
||||
|
||||
Omarchy isn't just for _pRoDUcTiVItY_, it's also for having fun, and what's more fun than gaming? Omarchy ships with a whole suite of gaming options — Steam and RetroArch for native and retro play, Battle.net, Lutris, and Heroic for non-Steam stores, Moonlight for PC streaming, Xbox Cloud Gaming + NVIDIA GeForce NOW for cloud, plus the evergreen Minecraft.
|
||||
|
||||
Thanks to Valve's incredible work on [the proton compatibility layer](https://en.wikipedia.org/wiki/Proton_(software)), there are now tens of thousands of playable modern games on Linux. Oh, and did you know that the [Steam Deck](https://store.steampowered.com/steamdeck/) actually runs Arch!
|
||||
|
||||
All gaming installers live under _Install > Gaming_ in the Omarchy menu (`Super + Space`). If you ever want to undo one, use _Remove > Gaming_.
|
||||
|
||||
## Steam
|
||||
|
||||
Install [Steam](https://store.steampowered.com/) by selecting _Install > Gaming > Steam_ from the Omarchy menu (`Super + Space`).
|
||||
|
||||
After you've installed it, you'll be able to launch Steam with `Super + Space`.
|
||||
|
||||
Note that Steam can take 10-20 seconds to start up, and it's not going to provide any visual feedback that it's loading.
|
||||
|
||||

|
||||
|
||||
## RetroArch
|
||||
|
||||
Install [RetroArch](https://www.retroarch.com/) by selecting _Install > Gaming > RetroArch_ from the Omarchy menu (`Super + Space`). It comes with the full set of libretro cores, so all the classic systems are covered.
|
||||
|
||||
RetroArch is fully preconfigured with the beautiful CRT Royale shader for that perfect retro look.
|
||||
|
||||
To get going:
|
||||
|
||||
1. Drop your BIOS files into `~/Games/bios` and your ROMs into `~/Games/roms`.
|
||||
2. Launch RetroArch with `Super + Space` and typing `retro`.
|
||||
3. Scan the `~/Games/roms` directory and you're ready to play.
|
||||
|
||||
You can also give a favorite game its own entry in the app launcher with _Install > Gaming > RetroArch Game Launcher_, which lets you pick a core and a ROM, and jump straight into the game from `Super + Space`.
|
||||
|
||||

|
||||
|
||||
## Xbox Cloud Gaming
|
||||
|
||||
Install the Xbox Cloud Gaming web app by selecting _Install > Gaming > Xbox Cloud Gaming_ from the Omarchy menu (`Super + Space`). It's "just" a web app for the service, but it's quick to start and runs great at 1080p.
|
||||
|
||||
If you already have Xbox Game Pass, this is a solid way to play Fortnite and other titles you can't run natively on Linux.
|
||||
|
||||

|
||||
|
||||
## NVIDIA GeForce Now
|
||||
|
||||
Install the cloud-gaming service [NVIDIA GeForce NOW](https://www.nvidia.com/en-us/geforce-now/) by selecting _Install > Gaming > NVIDIA GeForce NOW_ from the Omarchy menu (`Super + Space`). Another great way to play titles that aren't available natively on Linux.
|
||||
|
||||

|
||||
|
||||
## Minecraft
|
||||
|
||||
Install Minecraft by selecting _Install > Gaming > Minecraft_ from the Omarchy menu (`Super + Space`).
|
||||
|
||||
Like Steam, note that it can take a while after logging in or starting up for the next screen to appear, and you're not going to get any feedback while you're waiting.
|
||||
|
||||

|
||||
|
||||
## Xbox Controllers
|
||||
|
||||
Install support for Bluetooth Xbox controllers by selecting _Install > Gaming > Xbox Controllers_ from the Omarchy menu (`Super + Space`). Pair the controllers via Bluetooth (`Super + Ctrl + B`) and they'll work in all your games. You don't need this if you're just hardwiring your controller with a USB-C cable.
|
||||
|
||||
## Moonlight (Game streaming from a PC)
|
||||
|
||||
The [Moonlight client](https://github.com/moonlight-stream/moonlight-qt) comes preinstalled with Omarchy, so you can stream games from a Windows PC running [Sunshine](https://app.lizardbyte.dev/Sunshine/) right away. Launch Moonlight via `Super + Space`.
|
||||
|
||||
If both your Omarchy machine and the remote gaming PC are hardwired, the experience is indistinguishable from playing locally. Crank the resolution up to native, set the refresh to 120Hz, and max the bitrate — this is the best way to play competitive shooters like Fortnite on Linux.
|
||||
|
||||
You can also turn an Omarchy machine into the host by running `omarchy install service sunshine`, which installs Sunshine and opens the Moonlight streaming ports for your LAN and Tailscale.
|
||||
|
||||
## Battle.net
|
||||
|
||||
Install [Battle.net](https://eu.shop.battle.net/en-us) by selecting _Install > Gaming > Battle.net_ from the Omarchy menu (`Super + Space`). This gives you titles like Diablo, Starcraft, and World of Warcraft as a standalone install running under GE-Proton — no Steam, Lutris, or Heroic needed.
|
||||
|
||||

|
||||
|
||||
## Lutris (Windows games)
|
||||
|
||||
Install [Lutris](https://lutris.net/) by selecting _Install > Gaming > Lutris_ from the Omarchy menu (`Super + Space`). Lutris is the way to play Windows games from stores like EA and Ubisoft Connect that don't have their own installer above.
|
||||
|
||||
Installation is a little janky and looks like nothing is happening at times — just be patient, it's working in the background.
|
||||
|
||||
## Heroic Launcher (Epic Games)
|
||||
|
||||
Install the [Heroic Launcher](https://heroicgameslauncher.com/) by selecting _Install > Gaming > Heroic (Epic Games)_ from the Omarchy menu (`Super + Space`). Heroic lets you run Epic Games titles, like OddSparks, that don't rely on anti-cheat — plus games from GOG and Amazon Prime Gaming. Sadly, that means no Fortnite and no Rocket League — until Tim Sweeney comes to Linux, this is as close as it gets.
|
||||
|
||||
Like Lutris, it can feel slow and janky while installing games. Give it time.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Filling out PDFs
|
||||
|
||||
Omarchy ships with a nice, basic PDF viewer called Document Viewer. This is the program that'll open any PDFs you just double click on.
|
||||
|
||||
But you can only use Document Viewer to fill out PDFs that have been setup as forms. If you need to fill out PDFs that haven't, or you need to sign a PDF, you'll have to right-click the file, select _Open With..._, and pick Xournal++.
|
||||
|
||||
Xournal++ will let you write anywhere on a PDF using the T tool. If you need to sign a document, you'll need an image of your signature, and you can use the Image tool to insert this signature and resize it.
|
||||
|
||||
When you're done filling out the PDF, use _File > Export as PDF_ to save the final version.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Windows VM
|
||||
|
||||
Omarchy offers an easy way to run Windows through a Docker VM. You can install it using _Install > Windows_ from the Omarchy menu (`Super + Space`).
|
||||
|
||||
Your machine needs KVM virtualization for this, which most do — but it's sometimes switched off in the BIOS, and the installer will tell you if that's the case. You'll also want the disk space: whatever you give Windows, plus about 10GB for the image itself.
|
||||
|
||||
The installer asks how much RAM, how many CPU cores, and how much disk to hand over (64GB or more is the sensible floor), then for a Windows username and password. Leave those blank and you get `docker` / `admin`. The download takes a while — 10-15 minutes is normal — and you can follow the progress in the browser at `http://127.0.0.1:8006`.
|
||||
|
||||

|
||||
|
||||
## Using it
|
||||
|
||||
Once it's installed, launch _Windows_ from the app launcher. That starts the VM if it isn't already running and connects you over RDP, full screen. Give it 15-30 seconds on a cold start.
|
||||
|
||||
The RDP session carries sound, your microphone, and a shared clipboard, so copying text between Linux and Windows just works. The resolution follows your window, and Omarchy passes your display scaling through, so it's not a blurry mess on a HiDPI screen.
|
||||
|
||||
When you close the RDP window, the VM shuts down automatically. If you'd rather leave it running — say you've got something working away in there — launch it with `omarchy windows vm launch --keep-alive` instead.
|
||||
|
||||
The rest of the controls are on the same command:
|
||||
|
||||
```bash
|
||||
omarchy windows vm status # is it running?
|
||||
omarchy windows vm stop # shut it down
|
||||
omarchy windows vm launch # start and connect
|
||||
```
|
||||
|
||||
## Sharing files
|
||||
|
||||
The directory `~/Windows` in your home directory is automatically shared with the VM. Put files there if you want them accessible to Windows. The VM has no access to any other part of your file system, so you're safe from anything nasty on the Windows side. Its own virtual disk lives in `~/.windows`.
|
||||
|
||||
The VM's ports are bound to localhost only, so nothing on your network can reach the Windows machine.
|
||||
|
||||
## Limits and licensing
|
||||
|
||||
There's no GPU passthrough with this setup, so it's not suitable for gaming or video editing. It's a great way to run Microsoft Office or whatever else you absolutely must have.
|
||||
|
||||
The version installed is Windows 11 Pro, unactivated. You'll need your own license key to use the gated features.
|
||||
|
||||
You can change the resource allocation later by editing `~/.config/windows/docker-compose.yml`, which is also where you'd mount a USB device. See all the options on [the Dockur Windows project](https://github.com/dockur/windows).
|
||||
|
||||
To get rid of the whole thing, use _Remove > Windows_ from the Omarchy menu. That deletes the VM's disk and all its data, so make sure anything you care about is out of `~/Windows` first.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Other Packages
|
||||
|
||||
Arch has an amazing wealth of packages available for almost any type of software between the official repository and the Arch User Repository (AUR).
|
||||
|
||||
It couldn't be easier to use either. You install a new Arch package by going to _Install > Package_ in the Omarchy menu (`Super + Space`) and typing the package you want. It'll automatically fuzzy filter the list of all packages. (You can also do it manually using `omarchy pkg add [package]` in the terminal).
|
||||
|
||||
You can do the same with AUR, just use _Install > AUR_. Just remember that the AUR isn't vetted by the Arch team. It's like RubyGems or npm. Anyone can upload.
|
||||
|
||||
If you want to remove a package, you can use _Remove > Package_ from the Omarchy menu. It'll remove package, config files, and dependencies. (You can also do it manually using `omarchy pkg drop [package]`).
|
||||
@@ -0,0 +1,37 @@
|
||||
# Updates
|
||||
|
||||
Omarchy and your packages are kept up to date via _Update > Omarchy_ in the Omarchy menu (`Super + Space`).
|
||||
|
||||
Omarchy itself is installed as regular pacman packages from the [Omarchy Package Repository](https://github.com/omacom-io/omarchy-pkgs), so an update installs [the latest Omarchy release](https://github.com/basecamp/omarchy/releases), runs any pending migrations to get your system in sync with the latest, and updates all system packages from the [Omarchy Arch Mirror](https://github.com/omacom-io/omarchy-mirror) and [AUR](https://aur.archlinux.org/) (if you have installed any AUR packages).
|
||||
|
||||
When new releases are made, a circle arrow icon will appear to the right of your clock. Click it and the update process will start.
|
||||
|
||||

|
||||
|
||||
### Four channels
|
||||
|
||||
Omarchy is updated along four channels: stable, RC, edge, and dev. New installations start on the stable channel, which tracks the [official releases](https://github.com/basecamp/omarchy/releases/), as well as the [stable Omarchy Arch mirror](https://github.com/omacom-io/omarchy-mirror) that's running one month behind the latest, so we can catch any new incompatibilities that require config changes before they cause problems for people.
|
||||
|
||||
But if you'd like to help spot those potential issues, you can run on the edge channel. That'll keep your Omarchy packages tracking the latest development builds, and lets you update to the latest Arch packages as soon as they're available. You should only do this if you're experienced with Linux, and know how to recover a system that has problems.
|
||||
|
||||
Before any new major release, we'll be doing final validation using the RC channel. If you're interested in helping with final polishing, come hang out in #omarchy-release-candidates on the Discord.
|
||||
|
||||
Finally, there's the dev channel, which links Omarchy directly to a git checkout of the source code in `~/omarchy`, combined with the edge packages. You should only use this channel if you're an experienced Linux user, working directly on Omarchy, and willing to tolerate breakage.
|
||||
|
||||
You can switch between channels using _Update > Channel_ from the Omarchy menu (or `omarchy-channel-set` in the terminal).
|
||||
|
||||
### Firmware updates
|
||||
|
||||
Your packages aren't the only thing that goes stale. Many laptops and peripherals ship BIOS, SSD, and dock firmware through the Linux Vendor Firmware Service, and _Update > Firmware_ in the Omarchy menu will fetch and install whatever your hardware has waiting. It installs `fwupd` the first time you run it. Plenty of firmware can only be written during a reboot, so don't be surprised to be asked for one.
|
||||
|
||||
### Warning about direct pacman/yay updates
|
||||
|
||||
If you're already familiar with Arch, you might be tempted to just run `pacman -Syu` or `yay -Syu` yourself, but if you do that, you'll miss the snapshot, migrations, and configuration updates that Omarchy runs together with new packages. That's why Omarchy will actually stop a direct system upgrade and point you to `omarchy update` instead. (If you really know what you're doing, the guard will tell you how to bypass it for a single transaction.)
|
||||
|
||||
### Rolling back bad updates
|
||||
|
||||
If you ever have a problem after doing an update, you can rollback your system to the snapshot taken before the update. Just restart and pick the snapshot in the boot loading menu from before you started the update.
|
||||
|
||||

|
||||
|
||||
If somehow your configuration files have been corrupted, you can also perform an Omarchy reinstall using `omarchy reinstall` in the terminal. This will reinstall all the default Omarchy packages, put you on stable and downgrade any packages that are too new, and reset all the configuration files. Note that all your user config changes to the Omarchy defaults will be overwritten doing this!
|
||||
@@ -0,0 +1,78 @@
|
||||
# Dotfiles
|
||||
|
||||
Omarchy is primarily configured through the so-called dotfiles that live in `~/.config`. Those are considered your files for your changes. The files that live in `/usr/share/omarchy` belong to Omarchy itself, and you shouldn't be messing with those. If you need to change anything in `/usr/share/omarchy`, you should be overwriting the value in `~/.config` instead.
|
||||
|
||||
The key configs can be edited straight from the Omarchy menu (`Super + Space`), like _Setup > Monitors_, _Setup > Keybindings_, _Setup > Input_, and _Setup > Config > [file]_. When you do it this way, any process that needs restarting after config edits automatically will be after you quit the editor (Neovim by default — `:wq`, remember! — but you can change that via _Setup > Defaults > Editor_).
|
||||
|
||||
Here's a list of the key files in `~/.config` and what they control:
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | --------------------- |
|
||||
| `~/.config/hypr/hyprland.lua` | The main Hyprland config. Loads the Omarchy defaults plus your override files below. [Learn more about Hyprland configs](https://wiki.hypr.land/Configuring/). |
|
||||
| `~/.config/hypr/bindings.lua` | Your own keybindings and overrides of the defaults. |
|
||||
| `~/.config/hypr/monitors.lua` | Controls your monitors, resolution, and position. |
|
||||
| `~/.config/hypr/input.lua` | Controls your keyboard layout, mouse, and trackpad settings. |
|
||||
| `~/.config/hypr/looknfeel.lua` | Controls gaps, borders, animations, and the rest of the look. |
|
||||
| `~/.config/hypr/autostart.lua` | Controls extra processes started with the session. |
|
||||
| `~/.config/omarchy/shell.json` | Controls the Omarchy shell: bar position, layout, and widgets, plus screensaver, lock, and idle timings. |
|
||||
| `~/.config/foot/foot.ini` | Controls your terminal (foot is the default). |
|
||||
| `~/.XCompose` | Defines your quick-access emoji and name/email autocomplete. Make sure to run `omarchy-restart-xcompose` after making changes. |
|
||||
|
||||
If you end up making a lot of changes to tweak your own setup, it's a good idea to backup all these dotfiles. [Stow is a great way to do that](https://www.youtube.com/watch?v=NoFiYOqnC4o).
|
||||
|
||||
### Starting your own apps with the session
|
||||
|
||||
If you want something to run every time you log in — a sync daemon, a chat app, your own script — put it in `~/.config/hypr/autostart.lua`:
|
||||
|
||||
```lua
|
||||
o.launch_on_start("my-service")
|
||||
```
|
||||
|
||||
That starts the command as part of the session, so it's properly cleaned up when you log out again.
|
||||
|
||||
### Running scripts on system events
|
||||
|
||||
Omarchy fires hooks at a handful of moments, and you can hang your own scripts off them. They live in `~/.config/omarchy/hooks/<event>.d/`, one directory per event, and every executable file in there runs when the event happens:
|
||||
|
||||
| Event | When it runs |
|
||||
| ----- | ------------ |
|
||||
| `post-boot` | Right after the desktop has started |
|
||||
| `post-update` | During `omarchy update`, after packages and migrations |
|
||||
| `pre-refresh-pacman` | Before `omarchy refresh pacman` re-syncs the package config |
|
||||
| `theme-set` | After a theme change (theme name in `$1`) |
|
||||
| `font-set` | After a font change (font name in `$1`) |
|
||||
| `battery-low` | When the battery gets low (percentage in `$1`) |
|
||||
|
||||
Each of those directories already holds a `.sample` file showing the shape of a hook — drop the `.sample` from the name to put it to work. To install a script you've written elsewhere, use `omarchy hook install post-boot ~/my-hook`, which copies it in and makes it executable.
|
||||
|
||||
### Adding your own menu entries
|
||||
|
||||
The Omarchy menu (`Super + Space`) can be extended with your own rows by editing `~/.config/omarchy/extensions/omarchy-menu.jsonc`. Entries are keyed by a dotted id, and the id is what places them in the tree, so `personal` shows up on the root menu and `personal.notes` shows up inside it:
|
||||
|
||||
```jsonc
|
||||
"personal": {"icon":"","label":"Personal"},
|
||||
"personal.notes": {"icon":"","label":"Notes","action":"omarchy-launch-editor ~/notes"},
|
||||
```
|
||||
|
||||
Reuse an existing id and you override that row instead of adding a new one. The file ships with all the available fields documented as comments.
|
||||
|
||||
### Adding your own shell exports, functions, and aliases
|
||||
|
||||
Omarchy ships with a bunch of ergonomic aliases and helpful functions, but it's very common to want to add your own. You should add both aliases, functions, and exports in `~/.bashrc`. This file will not be overwritten on updates. If you want to change any of the Omarchy defaults, you can also safely add them here.
|
||||
|
||||
### Changing internal Omarchy files
|
||||
|
||||
Look, this is your computer. You can do whatever you want with it, but I would advise against making changes to the files in `/usr/share/omarchy` directly. They belong to the Omarchy pacman package, so your changes will simply be overwritten on the next update. You're better off just overwriting any default values you don't like in the `~/.config/*` folder instead.
|
||||
|
||||
You can change just about everything that way, like the default keybindings. Just edit `~/.config/hypr/bindings.lua` to, say, replace [Obsidian](https://obsidian.md/) with [Joplin](https://joplinapp.org/) (install with `omarchy-pkg-add joplin-bin`):
|
||||
|
||||
```
|
||||
hl.unbind("SUPER + SHIFT + O")
|
||||
o.bind("SUPER + SHIFT + O", "Joplin", "joplin-desktop")
|
||||
```
|
||||
|
||||
If you insist on hacking on the internal Omarchy files, switch to the dev channel via _Update > Channel > Dev_. That links Omarchy to a git checkout of the source code in `~/omarchy`, which you're free to change to your heart's content. Ain't nobody here to tell you what to do!
|
||||
|
||||
### Resetting any changes
|
||||
|
||||
If you end up making a mess of the configurations, you can always revert them to the defaults via _Update > Config_ in the Omarchy menu. Or by running `omarchy reinstall configs` to reset everything.
|
||||
@@ -0,0 +1,102 @@
|
||||
# Shell Plugins
|
||||
|
||||
The Omarchy desktop runs as a single long-lived Quickshell process called `omarchy-shell`, and almost everything you see on screen is a plugin inside it. The bar is a plugin. So are the panels that drop down from it, the fullscreen overlays like the emoji picker and the clipboard manager, the Omarchy menu itself, the lock screen, the polkit dialog, and the headless services that watch your battery and warm your screen up at night.
|
||||
|
||||
That's not just an implementation detail. It means you can turn pieces of the desktop off, swap them out, or write your own without touching a line of Omarchy's source.
|
||||
|
||||
The first-party plugins ship with Omarchy and live in `$OMARCHY_PATH/shell/plugins/`. Anything you add yourself — your own experiments, or something you found on GitHub — lives in `~/.config/omarchy/plugins/`. Both are discovered the same way at startup; the only difference is where they sit on disk.
|
||||
|
||||
## Seeing what you have
|
||||
|
||||
```
|
||||
omarchy plugin list
|
||||
```
|
||||
|
||||
That prints every discovered plugin with its id, whether it's enabled, whether it's first-party or third-party, its kinds, and its display name. Add `--json` if you're feeding it to something else.
|
||||
|
||||
Plugin ids are namespaced. The built-ins all start with `omarchy.` — `omarchy.clock`, `omarchy.network`, `omarchy.notifications` — and that namespace is reserved, so a third-party plugin can never claim it.
|
||||
|
||||
## Turning them on and off
|
||||
|
||||
```
|
||||
omarchy plugin enable omarchy.tailscale
|
||||
omarchy plugin disable omarchy.weather
|
||||
```
|
||||
|
||||
Or use the menu: _Setup > Plugins_ has Enable, Disable, Add, Clone, and Remove, each with a picker that only offers the plugins that make sense for that action.
|
||||
|
||||
Enabled state is stored in `~/.config/omarchy/shell.json`, and the rule differs slightly for the two kinds of plugin. A third-party plugin is enabled exactly when its id appears somewhere in that file — as a bar layout entry, as an entry in `plugins[]`, or as `bar.id`. First-party plugins that aren't bar widgets are the other way around: they're on by default and only turn off by being listed in `disabledPlugins[]`.
|
||||
|
||||
A full bar plugin has no off state at all. There's always exactly one bar, so you replace it by enabling another one. Bar widget placement is covered in [the top bar](05-the-top-bar.md).
|
||||
|
||||
## Adding a plugin from git
|
||||
|
||||
A third-party plugin is just a git repo with a `manifest.json` at its root.
|
||||
|
||||
```
|
||||
omarchy plugin add https://github.com/acme/omarchy-weather.git --enable
|
||||
```
|
||||
|
||||
Before it does anything, it tells you plainly that plugins run as arbitrary, unsandboxed code inside your long-lived shell process, shows you the URL, and asks you to confirm. Take that seriously. A plugin isn't a config file — it's code that runs for as long as your session does, with everything your user account can reach. Only add repos you're willing to run, and read them before you enable them.
|
||||
|
||||
Then it clones the repo into a staging directory, validates the manifest, refuses the install if another plugin already claims that id, and moves it into `~/.config/omarchy/plugins/<id>/`. Without `--enable` it asks whether you want it on now, and you can say no and go read the code first. It never runs anything from the plugin, never executes an install hook, and never asks for sudo — it clones files, checks the manifest, and flips a bit over IPC.
|
||||
|
||||
Updating is a fast-forward pull of that same checkout:
|
||||
|
||||
```
|
||||
omarchy plugin update acme.weather
|
||||
omarchy plugin update
|
||||
```
|
||||
|
||||
With no id it updates every git-managed plugin you have. It shows you the diff before applying it, refuses to update if you've got local changes it can't fast-forward past, and rolls back if the new revision fails validation.
|
||||
|
||||
```
|
||||
omarchy plugin remove acme.weather
|
||||
```
|
||||
|
||||
Removal disables the plugin first, then deletes it if it's a git checkout (the repo is still upstream) or unlinks it if it's a symlink. A hand-made plugin folder with no git repo gets moved to a timestamped backup inside the plugins directory instead of being deleted outright.
|
||||
|
||||
## Cloning a built-in to modify it
|
||||
|
||||
This is my favorite part. If you want to change how a built-in widget behaves, don't edit the files under `$OMARCHY_PATH` — those belong to the package and the next update will overwrite them. Clone it instead:
|
||||
|
||||
```
|
||||
omarchy plugin clone omarchy.clock
|
||||
```
|
||||
|
||||
That copies the whole plugin into `~/.config/omarchy/plugins/dhh.clock` (your username, not mine), renames it to "My Clock", enables it, and switches the shell over from the built-in to your copy — keeping an existing bar widget's position and settings. Add `--edit` to open the new directory in your `$EDITOR` right away, which is what the menu's _Setup > Plugins > Clone Plugin_ does for you.
|
||||
|
||||
The username prefix keeps your clone's id yours, so sharing it doesn't collide with anyone else's. Calls made to the original built-in id get routed to your clone, so nothing that referred to `omarchy.clock` needs updating. And if you make a mess of it, `omarchy plugin remove dhh.clock` puts the built-in back.
|
||||
|
||||
Saving a file anywhere under `~/.config/omarchy/plugins/` reloads the plugin code automatically, so you can leave the editor open and watch your changes land.
|
||||
|
||||
## Writing your own
|
||||
|
||||
A plugin is a directory with a `manifest.json` and some QML. The manifest declares `schemaVersion: 1`, an `id`, `name`, `version`, one or more `kinds`, and an `entryPoints` object pointing at the QML file for each kind:
|
||||
|
||||
| Kind | What it is |
|
||||
|------|------------|
|
||||
| `bar-widget` | A component the active bar can drop into a section |
|
||||
| `panel` | A persistent or summoned floating window |
|
||||
| `overlay` | A fullscreen overlay |
|
||||
| `menu` | A summoned menu surface |
|
||||
| `service` | A headless singleton with no UI |
|
||||
| `bar` | A full bar that replaces the built-in one |
|
||||
|
||||
A plugin can declare several kinds at once — the media plugin is both a `service` and a `bar-widget`. Bar widgets get an extra `barWidget` block with a display name, a category, an optional `defaultSection`, and `allowMultiple`, which says whether it makes sense to have more than one on the bar. Most widgets set it to `false`; spacers and indicators set it to `true`.
|
||||
|
||||
Before you publish anything, check it:
|
||||
|
||||
```
|
||||
omarchy plugin validate ./my-plugin
|
||||
```
|
||||
|
||||
That runs the same checks the shell does at load time: the schema version, the required fields, an id that isn't reserved, entry points that are safe relative paths and actually exist, an entry point for every kind you claimed, and no symlinks anywhere inside the folder.
|
||||
|
||||
For the full picture, the source is the documentation: `shell/README.md` in the Omarchy repo covers the manifest schema, the shell's IPC contract, and the exact shape of `shell.json`, and `shell/plugins/README.md` lists every first-party plugin with its id, kinds, and entry points.
|
||||
|
||||
## Sharing yours with the world
|
||||
|
||||
Once you've made something you like, put it in a public git repo. That's the whole distribution mechanism — anyone can then run `omarchy plugin add` against your URL and have it running in seconds.
|
||||
|
||||
To help people actually find it, list it at [omarchyplugins.com](https://omarchyplugins.com). That's the community directory of Omarchy shell plugins, and it's the first place to look when you're wondering whether someone has already built the widget you're about to write. Browse it before you start!
|
||||
@@ -0,0 +1,55 @@
|
||||
# Monitors
|
||||
|
||||
Omarchy assumes you're running on a 2x-capable retina-class display by default. This is what you need to get those nice, crisp programmer fonts. It's what almost all new premium laptops with high-resolution screens are optimized for. It's what you'd want to run on a 27" 5K [Apple Studio Display](https://www.apple.com/studio-display/)/[ProArt PA27JCV](https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-5k-pa27jcv/)/[Samsung S9](https://www.samsung.com/us/computing/monitors/5k/27-viewfinity-s9-5k-monitor-with-thunderbolt-4-matte-display-and-smart-features-ls27c900panxza/)/[Kuycon G27P](https://kuycon.us/monitors/G27P/) or 32" 6K [Apple XDR](https://www.apple.com/pro-display-xdr/)/[ProArt PA32QCV](https://www.asus.com/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/)/[Kuycon G32P](https://kuycon.us/monitors/G32P/).
|
||||
|
||||
But if you're not running a display with a PPI of 218 or above, you'll want to change the monitor settings. For example, if you have a 27" or 32" 4K, you can use fractional scaling by opening `~/.config/hypr/monitors.lua` (via _Setup > Monitors_ in the Omarchy menu) and switching to the recommendation for that combo:
|
||||
|
||||
```lua
|
||||
local omarchy_gdk_scale = 2
|
||||
local omarchy_monitor_scale = 1.6
|
||||
```
|
||||
|
||||
If you're using a 1080p or 1440p display, you'll probably just want to use 1x scaling, so you can use:
|
||||
|
||||
```lua
|
||||
local omarchy_gdk_scale = 1
|
||||
local omarchy_monitor_scale = 1
|
||||
```
|
||||
|
||||
Changes to `GDK_SCALE` apply to applications started after the change (and GTK only honors whole numbers, so keep it at the nearest integer of your monitor scale). So make sure you quit the windows that you have that are oversized after the change (or close all windows with `Ctrl + Alt + Del`!).
|
||||
|
||||
You can also quickly step through the major monitor scaling ratios (1x, 1.25x, 1.6x, 2x, 3x, 4x) using `Super + /` to go higher and `Super + Alt + /` to go lower. If you have the default configuration, these changes will also persist past reboot.
|
||||
|
||||
### Making text bigger or smaller
|
||||
|
||||
Monitor scaling changes the size of everything. If all you want is bigger or smaller _text_, there's a single knob for that:
|
||||
|
||||
```
|
||||
omarchy display text size 14
|
||||
```
|
||||
|
||||
That takes a pixel size between 9 and 20, and moves the Omarchy shell, GTK applications, and your terminal together, so the whole desktop stays in proportion. Run it without an argument to see where you're at, and `omarchy display text size reset` to go back to the default. Foot is the one straggler: it has no way to reload its config, so running terminals keep their old size until you open a new one.
|
||||
|
||||
### Extending and mirroring laptop displays
|
||||
|
||||
When you connect an external screen to your laptop, the display is automatically extended. But you can change that to mirroring instead using _Trigger > Hardware_ in the Omarchy menu or `Super + Ctrl + Alt + Delete`. This is especially helpful if that external screen is a projector, and you want to show something while working.
|
||||
|
||||
When you're extending, closing the lid on the laptop will automatically turn off the internal screen. Opening the lid will turn it back on. You can also control this manually using _Trigger > Hardware_ in the Omarchy menu or `Super + Ctrl + Delete`.
|
||||
|
||||
### Arranging multiple screens
|
||||
|
||||
Hyprland works great with multiple screens. Read more about how to lay them out in [the Hyprland monitor documentation](https://wiki.hypr.land/Configuring/Basics/Monitors/). You can [bind specific workspaces to specific monitors](https://wiki.hypr.land/Configuring/Basics/Workspace-Rules/) as well. In Omarchy, these rules go in `~/.config/hypr/monitors.lua` as `hl.monitor` entries — the file ships with commented examples for pinning a specific monitor to a resolution, position, and rotation.
|
||||
|
||||
You can also checkout [Hyprmon](https://github.com/erans/hyprmon/), if you'd like a TUI to help you with the positioning of multiple screens.
|
||||
|
||||
### Controlling brightness
|
||||
|
||||
Monitor brightness is controlled by the dedicated function keys for brightness up/down. If you hold down shift while pressing these, you'll go to maximum or minimum brightness. The keys control the display you're focused on, so external monitors that speak DDC/CI are adjusted the same way as the laptop screen.
|
||||
|
||||
### Apple Displays
|
||||
|
||||
If you're using an Apple display, the regular keyboard brightness keys will also automatically work, if you're focused on the Apple display. This is done through the `asdcontrol` command.
|
||||
|
||||
Note that if you're using an Apple 6K XDR display, you may see a phantom screen in your `hyprctl monitors` listing. You can turn this off with something like `hl.monitor({ output = "DP-2", disabled = true })` via _Setup > Monitors_.
|
||||
|
||||
On Intel machines, you should be connecting to Apple displays using a regular Thunderbolt cable. On other machines without Thunderbolt, you'll typically have to use a [DP + USB-A -> USB-C cable](https://www.amazon.com/dp/B0BNX7MS6N) to make it work.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Keyboard, Mouse, Trackpad
|
||||
|
||||
Hyprland lets you configure all your inputs in great detail. You can change the keyboard repeat to be supersonically fast or make the trackpad use natural scrolling. You change all of it in `~/.config/hypr/input.lua`, which you can also reach via _Setup > Input_ in the Omarchy menu (`Super + Space`). Anything you set there replaces Omarchy's defaults.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```lua
|
||||
hl.config({
|
||||
input = {
|
||||
-- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt
|
||||
kb_layout = "us,dk",
|
||||
kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
|
||||
|
||||
-- Change speed of keyboard repeat
|
||||
repeat_rate = 40,
|
||||
repeat_delay = 600,
|
||||
|
||||
-- Increase sensitivity for mouse/trackpad (default: 0)
|
||||
sensitivity = 0.35,
|
||||
|
||||
touchpad = {
|
||||
-- Use natural (inverse) scrolling
|
||||
natural_scroll = true,
|
||||
|
||||
-- Use two-finger clicks for right-click instead of lower-right corner
|
||||
clickfinger_behavior = true,
|
||||
|
||||
-- Control the speed of your scrolling
|
||||
scroll_factor = 0.3,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
-- Scroll faster in the terminal
|
||||
o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 })
|
||||
```
|
||||
|
||||
You can [see all the input options](https://wiki.hypr.land/Configuring/Basics/Variables/#input) on the Hyprland wiki for inputs.
|
||||
|
||||
### Trackpad gestures
|
||||
|
||||
You can also turn on [touchpad gestures](https://wiki.hypr.land/Configuring/Advanced-and-Cool/Gestures/), like swiping with three fingers to change workspaces:
|
||||
|
||||
```lua
|
||||
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
|
||||
```
|
||||
|
||||
On Dell XPS laptops with a haptic touchpad, you can also set the click strength to low, mid, or high under _Trigger > Hardware > Touchpad Haptics_.
|
||||
|
||||
### Typing in Chinese, Japanese, and other languages
|
||||
|
||||
Omarchy runs the [fcitx5](https://fcitx-im.org/) input method framework as part of every session — it's what powers the CapsLock compose sequences. That means the plumbing for non-Latin input is already in place: install an input engine like `fcitx5-mozc` (Japanese) or `fcitx5-chinese-addons` (Chinese) with `omarchy pkg add`, plus `fcitx5-configtool` to add the engine to your input methods and set the key that switches between them.
|
||||
|
||||
### Use ALT as SUPER
|
||||
|
||||
On some keyboards, it's not convenient to use the primary meta key (Windows/cmd key) as SUPER. You can change this to be ALT instead using this change:
|
||||
|
||||
```lua
|
||||
hl.config({
|
||||
input = {
|
||||
kb_options = "compose:caps,shift:both_capslock_cancel,altwin:swap_alt_win",
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
# Networking
|
||||
|
||||
Networking in Omarchy is handled by NetworkManager, and you drive it from the network icon in the [top bar](05-the-top-bar.md) or with `Super + Ctrl + W`.
|
||||
|
||||
That panel scans for Wi-Fi networks, shows signal strength, and connects. Ethernet needs nothing at all — plug it in and it works. If you'd rather stay in the terminal, `nmtui` gives you the same controls, and there's an `omarchy network` command group too.
|
||||
|
||||
## Sharing your Wi-Fi
|
||||
|
||||
Rather than reading a long password out loud, run _Setup > Network > QR Code_ while you're on Wi-Fi. That puts a QR code on screen that any phone camera can scan to join. It's one of those things you'll use more than you'd expect once you know it's there.
|
||||
|
||||
If you actually need the password itself, `omarchy network password <interface>` prints it.
|
||||
|
||||
## DNS
|
||||
|
||||
Omarchy uses whatever DNS your network hands out over DHCP. You can override that for the whole machine under _Setup > Network > DNS_, where Cloudflare and Google are one click away. Pick _Custom_ to type in your own servers.
|
||||
|
||||
From the terminal, `omarchy dns` prints the current provider and `omarchy dns Cloudflare` sets one.
|
||||
|
||||
## Pinning the Wi-Fi band
|
||||
|
||||
If your router puts 2.4GHz, 5GHz, and 6GHz on the same network name, your laptop will sometimes cling to the slow one. `omarchy network band` shows which band you're on, and `omarchy network band 5` pins it. Use `auto` to let it choose again.
|
||||
|
||||
This pins the band rather than a specific access point, so you keep roaming between APs normally.
|
||||
|
||||
## How fast is it?
|
||||
|
||||
_Trigger > Speed Test > Network Speed Test_ measures your actual up and down speed with a pair of dials. From the terminal it's `omarchy network speedtest down` or `up`. (There's a disk speed test sitting next to it in that menu, if you're benchmarking the other bottleneck.)
|
||||
|
||||
## The firewall
|
||||
|
||||
The firewall is on by default and blocks all incoming traffic, with one exception: port 53317, so [LocalSend](22-guis.md) works out of the box.
|
||||
|
||||
SSH is off until you turn it on with _Setup > Security > SSHD_, which starts the daemon, opens port 22 rate-limited against brute force, and authorizes a key. Docker is locked down too, so containers can't accidentally expose themselves to the world. See [security](48-security.md) for the whole story.
|
||||
|
||||
## Tailscale
|
||||
|
||||
[Tailscale](https://tailscale.com/) is a mesh VPN that makes reaching all your computers and servers over the internet simple and secure. Install it with _Install > Service > Tailscale_.
|
||||
|
||||
That gives you a Tailscale panel in the bar, which connects and disconnects the tailnet, switches accounts, and picks an exit node — your own machines and Mullvad regions both show up in the list. It also browses your machines, and that's where Taildrop lives: select a machine and press `s` to send it files, or `c`, `n`, and `d` to copy its IP, name, or full DNS name. The terminal equivalent is `omarchy tailscale send <machine> [file...]`, and files sent to you land in `~/Downloads` automatically.
|
||||
|
||||
Installing it also adds a web app for the Tailscale admin console.
|
||||
|
||||
## When it stops working
|
||||
|
||||
Before rebooting, try restarting the offending piece on its own. _Update > Hardware_ has Wi-Fi, Bluetooth, Audio, and Trackpad, and reloading one of those clears up most "it worked five minutes ago" situations. See [troubleshooting](45-troubleshooting.md).
|
||||
@@ -0,0 +1,19 @@
|
||||
# System sleep
|
||||
|
||||
Omarchy enables suspend and hibernation by default, but if you're having issues with either on your machine, you can toggle them off.
|
||||
|
||||
### Power profiles
|
||||
|
||||
On a laptop, Omarchy remembers your power profile separately for plugged in and running on battery, and switches between the two as you plug and unplug. Out of the box that means performance on AC and balanced on battery.
|
||||
|
||||
You can see what your machine offers with `omarchy powerprofiles list`, and set the one you want for the state you're currently in with `omarchy powerprofiles set autodetect power-saver`. To set the other state without unplugging anything, name it directly: `omarchy powerprofiles set battery power-saver`. Whatever you pick is what you'll get back the next time you're in that state.
|
||||
|
||||
### Toggle suspend
|
||||
|
||||
You toggle suspend by running `omarchy toggle suspend` from the terminal. That just reveals/hides the option under _System_ (or `Super + Esc`), and then you can see if it works consistently on your system. If not, you can hide it again with the same command.
|
||||
|
||||
### Toggle hibernation
|
||||
|
||||
You set up hibernation by running `omarchy hibernation setup` from the terminal. Hibernation creates a /swap subvolume on your boot drive the size of your physical RAM allocation, so make sure you have plenty of room to spare. On a 32GB machine, you'll always need 32GB+ free for this volume. Hibernation also requires the default Limine bootloader.
|
||||
|
||||
When set up, you'll see the hibernate option under _System_ (or `Super + Esc`), and then you can see if it works consistently on your system. If not, you can remove it again by running `omarchy hibernation remove`.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Hardware authentication
|
||||
|
||||
### Fingerprint authentication
|
||||
|
||||
A lot of laptops come with a fingerprint sensor to do authentication. You can use this with Omarchy by running _Setup > Security > Fingerprint_ in the Omarchy menu (`Super + Space`).
|
||||
|
||||
That'll install the fingerprint package, collect your print, verify it, and you'll be set to go using your fingerprint to unlock from the lock screen (which you can trigger with `Super + Ctrl + L`), enter sudo mode, and authorize system prompts.
|
||||
|
||||
When your laptop lid is closed, the fingerprint prompt is automatically skipped, so you go straight to the password prompt instead of waiting on a sensor you can't reach. If you otherwise need to work on an external keyboard that doesn't have a sensor, just hit `CTRL + C`, when you're prompted for your fingerprint during `sudo`.
|
||||
|
||||
You can remove the fingerprint authentication under _Remove > Security > Fingerprint_ in the Omarchy menu.
|
||||
|
||||
### Fido2 authentication
|
||||
|
||||
If you're using a Fido2 device, you can set it up for `sudo` authentication using _Setup > Security > Fido2_ in the Omarchy menu (`Super + Space`). It covers `sudo` and system authorization prompts, though, not unlocking your computer.
|
||||
|
||||
You can remove the fido2 authentication under _Remove > Security > Fido2_ in the Omarchy menu.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Fonts
|
||||
|
||||
Omarchy uses JetBrainsMono Nerd Font as both the terminal and system font by default.
|
||||
|
||||

|
||||
|
||||
You can change this through the _Style > Font_ menu in the Omarchy menu (`Super + Space`). That sets the monospace font everywhere: the terminal, the bar, and anything else that asks for it.
|
||||
|
||||
You can install other popular programming fonts via _Install > Style > Font_ in the Omarchy menu: Cascadia Mono, Meslo LG Mono, Fira Code, Victor Code, Bitstream Vera Mono, and Iosevka are all one click away, in their Nerd Font versions so the glyphs in the bar and terminal keep working. After installing one, pick it under _Style > Font_.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Backgrounds
|
||||
|
||||
Every theme ships with its own set of backgrounds, and you can add extras of your own in `~/.config/omarchy/backgrounds/[theme]`. If you want to add an extra background image to, say, the nord theme, you just put the file in `~/.config/omarchy/backgrounds/nord`.
|
||||
|
||||
You can do this most easily by going to _Install > Style > Background_ in the Omarchy Menu. That'll bring up the folder where the backgrounds for that theme is stored. Hit `Super + Shift + F` to start another file manager, find your background, copy it over. Now it'll be included in the choices of backgrounds you can select between using `Super + Ctrl + Space`.
|
||||
|
||||
You can find a huge collection of cool curated backgrounds on https://github.com/dharmx/walls.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Prompt
|
||||
|
||||
Omarchy ships with a minimal [Starship](https://starship.rs/) prompt by default. That's how I like to keep my prompt. I don't need to know the user, because it's always me, and I don't need to know the time, because it's always at the top.
|
||||
|
||||

|
||||
|
||||
If you want more information or style, you can change the [Starship.rs](https://starship.rs/) configuration in `~/.config/starship.toml`. There's a lot you can do. Just don't go overboard (or do go overboard, do whatever you want, it's your computer!)
|
||||
@@ -0,0 +1,43 @@
|
||||
# Branding
|
||||
|
||||
Omarchy allows you to set your company logo or personal image for both the boot unlock, the screensaver, and the about screen.
|
||||
|
||||
### Boot unlock
|
||||
|
||||
You can use `omarchy plymouth preview` to see what your custom logo and colors would look like. It takes a background color, a text color, a logo png, and a path for the preview image:
|
||||
|
||||
```
|
||||
omarchy plymouth preview '#1d2021' '#ebdbb2' logo.png preview.png
|
||||
```
|
||||
|
||||
Then apply the setup with `omarchy plymouth set '#1d2021' '#ebdbb2' logo.png`, which will also give the SDDM login screen the same colors and logo. If you want to revert, you can use `omarchy plymouth reset`.
|
||||
|
||||

|
||||
|
||||
### Screensaver
|
||||
|
||||
You can change the logo used for the screensaver under _Style > Screensaver_. It's an ASCII logo, so you can edit the text directly, but you can also hand it a png or svg image, and we'll convert that to ASCII. It looks pretty cool.
|
||||
|
||||

|
||||
|
||||
There are three entries in that menu:
|
||||
|
||||
- **Edit Text** opens `~/.config/omarchy/branding/screensaver.txt` in your editor. Type or paste whatever you like — ASCII art, your name, a rude word. Save and quit, and the screensaver fires up immediately so you can see it.
|
||||
- **Set From Image** opens a file picker for a png or svg, converts it to ASCII, and shows you the result. Logos with a clear silhouette work far better than photos.
|
||||
- **Restore Default** puts the Omarchy logo back.
|
||||
|
||||
### About screen
|
||||
|
||||
The same three options are under _Style > About_ for the _About_ screen you get from the Omarchy menu, and they work identically — the file is `~/.config/omarchy/branding/about.txt`, and the About window pops up after each change. The About art is converted to a smaller size than the screensaver's, since it has to fit in a window rather than fill your display.
|
||||
|
||||

|
||||
|
||||
### Converting images yourself
|
||||
|
||||
Both of the _Set From Image_ options are just calling `omarchy transcode ascii`, which you can run directly if you want control over the conversion:
|
||||
|
||||
```
|
||||
omarchy transcode ascii ~/logo.svg ~/.config/omarchy/branding/screensaver.txt --width 100
|
||||
```
|
||||
|
||||
It takes `--width` and `--height` in terminal columns and rows, a `--mode` of either `braille` (the default, and much finer) or `block`, a `--threshold` percentage for deciding which pixels count as part of the logo, and `--invert` for when your logo is light on a dark background. If a conversion comes out as a blob, the threshold is usually the knob to turn.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Common tweaks
|
||||
|
||||
This is a collection of common tailorings to the Omarchy setup. Know that it might occasionally be necessary for system updates to restore certain configs to their original condition. If this happens, your changes won't be lost, but put in a `.bak` file in the same directory.
|
||||
|
||||
If you screw something up, you can restore individual configs to their original setup via _Update > Config_ in the Omarchy menu. If you _really_ screw everything up, you can reset all configs via `omarchy-reinstall`.
|
||||
|
||||
### Reveal all tray icons all the time
|
||||
|
||||
By default, tray icons, like Dropbox, 1password, or Steam, are hidden behind the tray expander arrow, which reveals them when you hover it. If you'd like to have them exposed all the time, right-click the expander arrow to open the tray icon manager, then pin the icons you want to keep visible (you can also hide the ones you never want to see).
|
||||
|
||||
### Rounded window corners
|
||||
|
||||
Omarchy's default design is one of square corners, but if you like to soften that up a bit, you can change `~/.config/hypr/looknfeel.lua` so rounding is no longer commented out:
|
||||
|
||||
```
|
||||
hl.config({
|
||||
decoration = {
|
||||
-- Use round window corners.
|
||||
rounding = 8,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Remove window gaps
|
||||
|
||||
On laptop displays, some people prefer not to waste any pixels on window gaps (or even a top bar, which you can toggle off with `Super + Shift + Space`). You can toggle all gaps and borders off with `Super + Shift + Backspace`, or remove them permanently by removing the comments in this section of `~/.config/hypr/looknfeel.lua`:
|
||||
|
||||
```
|
||||
hl.config({
|
||||
general = {
|
||||
-- No gaps between windows or borders.
|
||||
gaps_in = 0,
|
||||
gaps_out = 0,
|
||||
border_size = 0,
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
# Making your own theme
|
||||
|
||||
You can add your own themes to `~/.config/omarchy/themes`. Just copy one of the existing ones as a base (look in `/usr/share/omarchy/themes`), then tweak to your delight. As long as your theme is inside that folder, it'll be included in the theme selection menu.
|
||||
|
||||
The main file you have to tweak is `colors.toml`. That defines the color set that's then used to generate configurations for the terminal (Foot/Alacritty/Ghostty/Kitty), btop, Chromium, Hyprland, Neovim, Helix, VSCode, Obsidian, and the entire Omarchy shell (top bar, menu, notifications, OSD, and lock screen).
|
||||
|
||||
You can also use the included Aether application to create a new theme using a lovely GUI interface to play with colors and search for backgrounds. Just start it via the apps menu on `Super + Alt + Space`.
|
||||
|
||||
### Light mode
|
||||
|
||||
If you're making a light mode theme, set `mode = "light"` at the top of your `colors.toml`. Then it'll automatically be paired with light mode for all the apps. (The old way of dropping an empty file called `light.mode` in the root of your theme still works too.)
|
||||
|
||||
### Icon colors
|
||||
|
||||
If you'd like to color-match the file manager icons to your theme, add a file called `icons.theme` with the name of the icon set you want to use. By default, the options are: `Yaru Yaru-blue Yaru-dark Yaru-magenta Yaru-olive Yaru-prussiangreen Yaru-purple Yaru-red Yaru-sage Yaru-wartybrown Yaru-yellow`.
|
||||
|
||||
### Unlock image
|
||||
|
||||
Themes supplied with `unlock.png` and `preview-unlock.png` images will be listed under _Style > Unlock_. Your `unlock.png` should preferably be a transparent png. And you can create the preview image using `omarchy plymouth preview`.
|
||||
|
||||
### Theming apps Omarchy doesn't cover
|
||||
|
||||
If you use an app that isn't in that list, you can teach Omarchy to theme it yourself with a template. Drop a file in `~/.config/omarchy/themed/` named after the config it generates plus a `.tpl` extension, and write the config with `{{ background }}`, `{{ foreground }}`, `{{ accent }}`, `{{ red }}`, `{{ color0 }}` through `{{ color15 }}`, and the rest of the palette as placeholders. Every time you switch themes, the file is regenerated with that theme's colors.
|
||||
|
||||
There's a fully commented `alacritty.toml.tpl.sample` in that folder to copy from — it lists every variable you can use, plus the `_strip` and `_rgb` modifiers for apps that want their colors without the `#` or as decimal RGB. Your templates take priority over Omarchy's own, so you can also use this to override how a built-in app gets themed.
|
||||
|
||||
### Distributing your theme
|
||||
|
||||
If you want to distribute your theme so others can use it, you need to put it on a public git server, like GitHub. Then people can install it using _Install > Style > Theme_ in the Omarchy menu using that URL. It's recommended that you follow the naming convention of `omarchy-[themename]-theme`, as the theme will show correctly as just `[themename]` in the theme selection menu after installation.
|
||||
|
||||
You can have your theme added to [the extra themes page](https://omarchy.org/themes/) by sending a pull request to [the omarchy-site repo](https://github.com/omacom-io/omarchy-site).
|
||||
@@ -0,0 +1,71 @@
|
||||
# Mac support
|
||||
|
||||
Omarchy has built-in support for **Intel Macs**. There are a couple of known limitations at the moment, but as long as you're aware and OK with those; you can breathe some new life into your old Macs by loading Omarchy.
|
||||
|
||||
Please note that installing on an M-series Mac is not directly supported at this time. You can find out more about the state of this in #omarchy-on-other in our [Discord](https://discord.gg/tXFUdasqhY).
|
||||
|
||||
In a simple test, we were able to achieve 36% performance gains on a 2019 MacBook Pro just by installing Omarchy.
|
||||
|
||||

|
||||
|
||||
### Installing Omarchy on Mac
|
||||
|
||||
Omarchy only supports being the **only** OS installed at the moment. During the installation, the drive will be wiped and MacOS will no longer be bootable.
|
||||
|
||||
You can still restore it later via Internet Recovery if you'd like.
|
||||
|
||||
For the sake of this part, we'll assume you've already reviewed [Getting Started](02-getting-started.md) and have your USB drive ready. If you don't go ahead and do that now.
|
||||
|
||||
#### Disable Secure Boot
|
||||
|
||||
It is necessary to disable Apple's Secure Boot in order to boot the bootable USB, as well as the OS. To disable it, perform the following:
|
||||
|
||||
1. Turn off your Mac
|
||||
2. Turn it on and _immediately_ press and hold Command-R until you see the loading screen appear
|
||||
3. Select your user and enter your password if prompted
|
||||
4. Once in the recovery screen, choose **Utilities > Startup Security Utility** from the menubar
|
||||
5. Enter your password when prompted to authenticate
|
||||
6. Choose "No Security" from the Secure Boot options
|
||||
7. Choose "Allow booting from external or removable media" from the External Boot options
|
||||
|
||||
#### Start the Installation
|
||||
|
||||
1. Insert the USB drive
|
||||
2. Restart your Mac and _immediately_ press and hold Option until you see a screen of boot devices
|
||||
3. Select the orange EFI Boot device
|
||||
4. Proceed with the [install as normal](02-getting-started.md)
|
||||
|
||||
The installer detects Mac hardware and applies the needed fixes automatically: Broadcom Wi-Fi drivers and firmware, the SPI keyboard driver on the MacBook models that need it, and an NVMe suspend fix for those same models.
|
||||
|
||||
### Known Limitations
|
||||
|
||||
Members of the community are constantly working on solutions to these challenges so if these are problematic for you, join #omarchy-on-other in our [Discord](https://discord.gg/tXFUdasqhY) and see if there's any up-to-date methods for resolving these.
|
||||
|
||||
#### Devices with T1 Chip
|
||||
|
||||
The Apple T1 chip was introduced in late 2016 and used exclusively in the first-generation MacBook Pro models with Touch Bar.
|
||||
- MacBook Pro 13-inch (2016, two Thunderbolt 3 ports) – Model: A1706
|
||||
- MacBook Pro 13-inch (2016, four Thunderbolt 3 ports) – Model: A1708
|
||||
- MacBook Pro 15-inch (2016) – Model: A1707
|
||||
|
||||
#### Known Issues
|
||||
|
||||
- Touch Bar is non-functional
|
||||
- Sound is not functioning
|
||||
|
||||
#### Devices with T2 Chip
|
||||
|
||||
The Apple T2 Security Chip was introduced in 2017. The T2 chip was discontinued with the transition to Apple silicon (M-series chips) starting in 2020.
|
||||
|
||||
- iMac Pro (2017) – Model: A1862
|
||||
- MacBook Pro 13-inch (2018, four Thunderbolt 3 ports) – Model: A1989
|
||||
- MacBook Pro 15-inch (2018) – Model: A1990
|
||||
- MacBook Air (Retina, 13-inch, 2018) – Model: A1932
|
||||
- Mac mini (2018) – Model: A1998
|
||||
- MacBook Pro 13-inch (2019, two Thunderbolt 3 ports) – Model: A2159
|
||||
- MacBook Pro 13-inch (2019, four Thunderbolt 3 ports) – Model: A2178
|
||||
- MacBook Pro 15-inch (2019) – Model: A1990
|
||||
- MacBook Pro 13-inch (2020, two Thunderbolt 3 ports) – Model: A2265
|
||||
- MacBook Pro 15-inch (2020) – Model: A1990
|
||||
|
||||
On these models, the installer automatically sets up the patched `linux-t2` kernel, the T2 audio configuration, Apple's Broadcom Wi-Fi/Bluetooth firmware, and fan control via `t2fanrd`. The Touch Bar runs on the kernel's built-in Boot Camp-style support.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Troubleshooting
|
||||
|
||||
### I broke my system with an update!
|
||||
|
||||
First try to [rollback your system](47-system-snapshots.md) the version before your recent update. If that doesn't work, use `omarchy-debug` to share with your problem on #omarchy-help in the Discord. And if all that fails, you can reinstall the defaults configs and packages using `omarchy-reinstall`.
|
||||
|
||||
### Why are some apps so large on my display?
|
||||
|
||||
Omarchy assumes a 2x high-resolution display, which requires setting `GDK_SCALE` to 2 in `~/.config/hypr/monitors.lua`. But if you're on a 1x display, you can change `local omarchy_gdk_scale = 2` to 1 (and then restart any app that's oversized). See [the manual on monitors](33-monitors.md).
|
||||
|
||||
For Spotify, you can use `Ctrl + Minus` to shrink the UI (and `Ctrl + Plus` to make it bigger).
|
||||
|
||||
### Why isn't Caps Lock working?
|
||||
|
||||
In Omarchy, Caps Lock has been designated to be the xcompose key. That's how you get [quick emojis](07-hotkeys.md#quick-emojis) and [other autocompletions](07-hotkeys.md#quick-completions) done. If you really miss using Caps Lock, you can remap the xcompose key to something else by editing `~/.config/hypr/input.lua`, like setting it to the right alt key:
|
||||
|
||||
```
|
||||
hl.config({
|
||||
input = {
|
||||
kb_options = "compose:ralt",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### My Wi-Fi, Bluetooth, audio, or trackpad just stopped working
|
||||
|
||||
Before you reboot, try restarting the offending subsystem on its own. _Update > Hardware_ in the Omarchy menu has Wi-Fi, Bluetooth, Audio, and Trackpad, and reloading one of those clears up the majority of "it worked five minutes ago" situations — a Bluetooth headset that won't reconnect, a trackpad that went dead after a suspend, sound that vanished when you unplugged a monitor.
|
||||
|
||||
### Why are my external speakers not playing?
|
||||
|
||||
Probably because they're not set as the primary output. Click on the speaker icon on the right side of the bar, and it'll open the volume popup where you can pick the output device (and mix per-app volumes too).
|
||||
|
||||
### My laptop speakers sound off
|
||||
|
||||
On some laptops, Omarchy automatically applies a speaker tuning that corrects the built-in speakers' frequency response. `omarchy audio tuning status` tells you whether one is active on your machine, and `omarchy audio tuning off` turns it off if you'd rather hear the speakers raw.
|
||||
|
||||
### Why can't I login or sudo with my password?
|
||||
|
||||
You probably typed it wrong too many times and got locked out. If this is happening on the lock screen, you can hit `CTRL + ALT + F2` to start a new TTY where you can login as root, then run `faillock --reset --user [your-username]`. That'll reset the lockout, and you're good to go.
|
||||
|
||||
### Why isn't my 1Password authorization prompts for 1Password SSH Agent / CLI appearing?
|
||||
|
||||
This can happen for 2 reasons:
|
||||
|
||||
In order for the rich approval prompt to appear, Settings > Advanced > Use Hardware Acceleration must be turned on. _Note: This requires a reboot to begin working._
|
||||
|
||||

|
||||
|
||||
Or if you haven't launched 1Password since booting up, the prompt will not appear.
|
||||
@@ -0,0 +1,79 @@
|
||||
# FAQ
|
||||
|
||||
### How do I switch between keyboard layouts?
|
||||
|
||||
Edit your `~/.config/hypr/input.lua` file and add this to switch between layouts on `Left Alt + Right Alt`:
|
||||
|
||||
```
|
||||
hl.config({
|
||||
input = {
|
||||
-- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt
|
||||
kb_layout = "us,fr",
|
||||
kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The bar will automatically show your current keyboard layout once you have multiple layouts configured (and you can click it to switch too).
|
||||
|
||||
### How do I change the clock format to 12-hour?
|
||||
|
||||
Right-click the clock in the bar to cycle through the common formats, including the 12-hour ones. You can also set the format directly:
|
||||
|
||||
```
|
||||
omarchy bar set omarchy.clock format "dddd h:mm AP"
|
||||
```
|
||||
|
||||
This will display Sunday 10:55 AM.
|
||||
|
||||
### How do I change my timezone?
|
||||
|
||||
Run _Update > Timezone_ in the Omarchy menu and pick from the list. If the timezone is right but the clock itself has drifted, _Update > Time_ restarts the time synchronization for you.
|
||||
|
||||
### How do I change my DNS, share my Wi-Fi, or check my connection speed?
|
||||
|
||||
Those all live in [networking](35-networking.md).
|
||||
|
||||
### How do I check how fast my disk is?
|
||||
|
||||
_Trigger > Speed Test > Disk Speed Test_ measures live read and write speed on your drive, or `omarchy disk speedtest` from the terminal.
|
||||
|
||||
### Why can't I sign into my Google account in Chromium?
|
||||
|
||||
The plain open source Chromium build doesn't ship with the OAuth credentials that Google requires for account sign-in. Run _Install > Service > Chromium Account_ in the Omarchy menu to add them, restart the browser, and the sign-in will go through.
|
||||
|
||||
### How do I add a printer?
|
||||
|
||||
Printing is set up and running out of the box, so a printer on your network is usually already discovered. Launch _Print Settings_ from the app launcher (`Super + Space`) to see what's there, add one by hand, or set the default. Printing to a PDF file works without any printer at all.
|
||||
|
||||
### How do I change where screenshots or screenrecordings are saved?
|
||||
|
||||
If you want screenshots to be saved to `~/Pictures/Screenshots` instead of just `~/Pictures`, you can add this to a file under `~/.config/uwsm/env.d/` (like `~/.config/uwsm/env.d/capture`):
|
||||
|
||||
```
|
||||
export OMARCHY_SCREENSHOT_DIR="$HOME/Pictures/Screenshots"
|
||||
```
|
||||
|
||||
You can do the same for screenrecordings using `OMARCHY_SCREENRECORD_DIR`.
|
||||
|
||||
Just remember to create the directoy you want to save to and restart Omarchy for this to take effect.
|
||||
|
||||
### How do I get the speakers + webcam working on my Apple Studio Display?
|
||||
|
||||
You'd think that it should all work just plugging in USB C, but unfortunately that isn't the case. The solution I've found to make it work reliably is using [the WJESOG DisplayPort + USB-A => USB-C cable](https://www.amazon.com/WJESOG-DisplayPort-Adapter-Converter-Thunderbolt/dp/B0BNX7MS6N/). Then speakers and webcam work like a charm.
|
||||
|
||||
Remember that you have built-in brightness control in Omarchy for the Apple Displays (both Studio and XDR) using the regular keyboard brightness buttons.
|
||||
|
||||
### How do I get rid of all the extra software?
|
||||
|
||||
If you don't want programs like Obsidian or LibreOffice or any of the other preinstalled stuff, you can very easily remove it.
|
||||
|
||||
Run _Remove > Package_ to see every package that's installed. Then you can select any package you'd like to remove with tab, and start removing everything you've selected with return.
|
||||
|
||||
And you can use _Remove > Web App_ from the Omarchy menu to remove any of the preinstalled web apps you don't want.
|
||||
|
||||
Or run _Remove > Preinstalls_ to sweep out all the preinstalled extras — web apps, TUIs, and optional applications — in one go. The hotkeys that launched them go away with them, so you're left with a clean slate of bindings to fill with your own in `~/.config/hypr/bindings.lua`.
|
||||
|
||||
---
|
||||
|
||||
For errors and broken bits, see [the Troubleshooting section](45-troubleshooting.md).
|
||||
Loaded 100 of 231 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user