diff --git a/bin/omarchy-toggle-crash-capture b/bin/omarchy-toggle-crash-capture new file mode 100755 index 00000000..1b4fb8b6 --- /dev/null +++ b/bin/omarchy-toggle-crash-capture @@ -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 diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 34f1fda0..a75b65c8 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -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"}, diff --git a/default/systemd/user/omarchy-crash-watch.service b/default/systemd/user/omarchy-crash-watch.service index 56cee15c..4c6a58c5 100644 --- a/default/systemd/user/omarchy-crash-watch.service +++ b/default/systemd/user/omarchy-crash-watch.service @@ -5,6 +5,9 @@ Description=Announce process crashes and offer an AI diagnosis 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 diff --git a/manual/16-ai.md b/manual/16-ai.md index 61193414..05cf66ba 100644 --- a/manual/16-ai.md +++ b/manual/16-ai.md @@ -34,6 +34,8 @@ Left-click the bar icon for the panel, right-click to launch your default agent. 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 `. +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 ` still works by hand. + ### 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. diff --git a/test/shell.d/crash-capture-test.sh b/test/shell.d/crash-capture-test.sh new file mode 100755 index 00000000..d4119cd8 --- /dev/null +++ b/test/shell.d/crash-capture-test.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +TMPDIR=$(mktemp -d) +trap 'rm -rf "$TMPDIR"' EXIT + +mkdir -p "$TMPDIR/bin" +SYSTEMCTL_LOG="$TMPDIR/systemctl-log" + +cat >"$TMPDIR/bin/systemctl" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >>"$SYSTEMCTL_LOG" +SH + +cat >"$TMPDIR/bin/omarchy-notification-send" <<'SH' +#!/bin/bash +exit 0 +SH + +chmod +x "$TMPDIR/bin/systemctl" "$TMPDIR/bin/omarchy-notification-send" + +test_home="$TMPDIR/home" +flag="$test_home/.local/state/omarchy/toggles/crash-capture-off" + +toggle_crash_capture() { + PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \ + SYSTEMCTL_LOG="$SYSTEMCTL_LOG" \ + HOME="$test_home" \ + "$ROOT/bin/omarchy-toggle-crash-capture" +} + +toggle_crash_capture +[[ -f $flag ]] || fail "crash capture toggle disables the watcher" +grep -Fqx -- "--user stop omarchy-crash-watch.service" "$SYSTEMCTL_LOG" || + fail "crash capture toggle stops the running watcher, so disabling takes effect before the next login" +pass "crash capture toggle disables the watcher" + +: >"$SYSTEMCTL_LOG" +toggle_crash_capture +[[ ! -f $flag ]] || fail "crash capture toggle re-enables the watcher" +grep -Fqx -- "--user start omarchy-crash-watch.service" "$SYSTEMCTL_LOG" || + fail "crash capture toggle starts the watcher, so enabling takes effect before the next login" +pass "crash capture toggle re-enables the watcher" + +service="$ROOT/default/systemd/user/omarchy-crash-watch.service" +grep -Fx 'ConditionPathExists=!%h/.local/state/omarchy/toggles/crash-capture-off' "$service" >/dev/null || + fail "the watcher is pulled back in at every login, so disabling it never survives a logout" +pass "crash watcher stays disabled across logins" + +grep -F 'omarchy-crash-watch.service' "$ROOT/install/user/first-run/enable-user-units.sh" >/dev/null || + fail "crash capture is no longer on by default for new installs" +pass "crash capture is on by default" + +run_node_test <<'JS' +const fs = require('fs') +const menu = requireFromRoot('shell/plugins/menu/MenuModel.js') +const items = menu.parseMenuJsonc(fs.readFileSync(path.join(root, 'default/omarchy/omarchy-menu.jsonc'), 'utf8')) +const byId = Object.fromEntries(items.map(item => [item.id, item])) + +assertEqual( + byId['trigger.toggle.crash-capture'].action, + 'omarchy-toggle-crash-capture', + 'menu toggles crash capture from Trigger > Toggle' +) +JS