Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions bin/omarchy-notification-font
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/bash

# omarchy:summary=Configure the notification text font
# omarchy:args=[show | default | system | set <font-name> | list]
# omarchy:examples=omarchy notification font system | omarchy notification font set "Inter" | omarchy notification font default

set -euo pipefail

source omarchy-shell-config

DEFAULT_FONT="Liberation Sans"

usage() {
cat <<USAGE
Usage: omarchy notification font [show | default | system | set <font-name> | list]

show Show the current notification font mode
default Use the built-in notification font ($DEFAULT_FONT)
system Follow the system monospace font (omarchy font set)
set <font-name> Use a specific installed font family
list List available font families

Examples:
omarchy notification font system
omarchy notification font set "Inter"
omarchy notification font default
USAGE
}

current_mode() {
jq -r '.notifications.font // "default"' "$(source_file)"
}

resolved_family() {
local mode="$1" raw line
case "$mode" in
default) printf '%s\n' "$DEFAULT_FONT" ;;
system)
# No pipeline: under `pipefail`, `head -n1` exits before fc-match
# finishes writing, and the SIGPIPE failure would poison the status.
raw=$(fc-match monospace -f '%{family}\n' 2>/dev/null)
line=${raw%%$'\n'*}
printf '%s\n' "${line%%,*}"
;;
*) printf '%s\n' "$mode" ;;
esac
}

cmd_show() {
(( $# == 0 )) || fail "show takes no arguments"
local mode
mode=$(current_mode)
printf 'mode: %s\nfamily: %s\n' "$mode" "$(resolved_family "$mode")"
}

cmd_default() {
(( $# == 0 )) || fail "default takes no arguments"
commit "$NORMALIZE | .notifications.font = \"default\""
echo "Notification font set to default ($DEFAULT_FONT)"
}

cmd_system() {
(( $# == 0 )) || fail "system takes no arguments"
commit "$NORMALIZE | .notifications.font = \"system\""
echo "Notification font follows the system monospace font ($(resolved_family system))"
}

cmd_set() {
local font_name="${1:-}" matches
(( $# == 1 )) || fail "set takes a single font family name"
# grep -c reads the whole input, so fc-list never takes a SIGPIPE
# the way it would with `grep -q` under `pipefail`.
matches=$(fc-list 2>/dev/null | grep -Fic -- "$font_name")
if (( matches == 0 )); then
echo "Font '$font_name' not found." >&2
exit 1
fi
commit "$NORMALIZE | .notifications.font = \$font" --arg font "$font_name"
echo "Notification font set to $font_name"
}

cmd_list() {
(( $# == 0 )) || fail "list takes no arguments"
omarchy-font-list
}

cmd="${1:-show}"
shift || true
case "$cmd" in
-h | --help) usage ;;
show) cmd_show "$@" ;;
default) cmd_default "$@" ;;
system) cmd_system "$@" ;;
set) cmd_set "$@" ;;
list) cmd_list "$@" ;;
*) fail "unknown font command: $cmd (see 'omarchy notification font --help')" ;;
esac
19 changes: 19 additions & 0 deletions shell/plugins/notifications/Service.qml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ Item {
readonly property int liveBarSize: shell && shell.bar && !shell.bar.barHidden ? Math.max(0, shell.bar.barSize) : defaultBarSize
readonly property int barClearance: liveBarSize + Style.gapsOut

// Text font for notification summary/body, from shell.json `notifications.font`
// (see `omarchy notification font`). "default" is the built-in proportional
// font, "system" follows the live system monospace family, and anything else
// is a custom installed family name. shellConfig is reactive, so edits apply
// without a shell restart; "system" stays live through the bar binding.
readonly property string notificationFontSetting: {
var cfg = shell && shell.shellConfig ? shell.shellConfig.notifications : null
var f = cfg ? cfg.font : null
return (typeof f === "string" && f.length > 0) ? f : "default"
}
readonly property string notificationTextFontFamily: {
if (service.notificationFontSetting === "system")
return (shell && shell.bar && shell.bar.fontFamily) || Style.font.family
if (service.notificationFontSetting === "default")
return "Liberation Sans"
return service.notificationFontSetting
}

// Live Notification objects by originalId, kept OUT of the ListModels: a
// QObject stored in a model role becomes a dangling C++ pointer when the
// server destroys the notification (sender close, DND untrack, dismiss),
Expand Down Expand Up @@ -1050,6 +1068,7 @@ Item {
timestamp: cardSlot.timestamp
cornerRadius: service.cornerRadius
fontFamily: service.shell && service.shell.bar ? service.shell.bar.fontFamily : ""
textFontFamily: service.notificationTextFontFamily
glyph: cardSlot.glyph

onCloseRequested: service.dismissPopup(cardSlot.index)
Expand Down
8 changes: 6 additions & 2 deletions shell/plugins/notifications/components/NotificationCard.qml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ BorderSurface {

// System monospace font injected by the container.
property string fontFamily: ""
// Text font for summary/body, resolved by the service from the
// `notifications.font` shell.json setting (`omarchy notification font`).
// Glyphs always use fontFamily above; this is prose text only.
property string textFontFamily: "Liberation Sans"

readonly property bool hovered: hoverTracker.hovered

Expand Down Expand Up @@ -169,7 +173,7 @@ BorderSurface {
Layout.fillWidth: true
visible: root.summary.length > 0
text: root.summary
font.family: "Liberation Sans"
font.family: root.textFontFamily
color: Color.notifications.text
font.pixelSize: Style.font.title
font.bold: true
Expand All @@ -184,7 +188,7 @@ BorderSurface {
visible: root.sanitizedBody.length > 0
text: root.styledBody
textFormat: Text.StyledText
font.family: "Liberation Sans"
font.family: root.textFontFamily
color: root.bodyColor
font.pixelSize: Style.font.title
wrapMode: Text.WordWrap
Expand Down
103 changes: 103 additions & 0 deletions test/shell.d/notification-font-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

# Stub the shell IPC: commit() refreshes the running shell, which does not
# exist under test. Record calls and succeed silently.
printf '%s\n' '#!/bin/bash' 'echo "$@" >>"$OMARCHY_TEST_SHELL_ARGS"' 'exit 0' >"$tmpdir/omarchy-shell"
chmod +x "$tmpdir/omarchy-shell"
touch "$tmpdir/shell-args"

export OMARCHY_TEST_SHELL_ARGS="$tmpdir/shell-args"

# Isolate shell.json writes: omarchy-shell-config resolves the user config
# from $HOME, so point HOME at the scratch dir.
export HOME="$tmpdir"
export PATH="$tmpdir:$ROOT/bin:$PATH"

font() {
omarchy-notification-font "$@"
}

mode() {
jq -r '.notifications.font // "default"' "$HOME/.config/omarchy/shell.json"
}

# ------------------------------------------------------------------ CLI

out=$(font show)
[[ $out == *"mode: default"* ]] || fail "notification font show defaults to default mode" "$out"
[[ $out == *"Liberation Sans"* ]] || fail "notification font show resolves the default family" "$out"
pass "notification font show defaults to default mode"

font default >/dev/null
[[ $(mode) == "default" ]] || fail "notification font default persists default mode"
pass "notification font default persists default mode"

font system >/dev/null
[[ $(mode) == "system" ]] || fail "notification font system persists system mode"
pass "notification font system persists system mode"

out=$(font show)
[[ $out == *"mode: system"* ]] || fail "notification font show reports system mode" "$out"
pass "notification font show reports system mode"

font set "DejaVu Sans" >/dev/null
[[ $(mode) == "DejaVu Sans" ]] || fail "notification font set persists a custom family"
pass "notification font set persists a custom family"

if font set "No Such Font XYZ-123" 2>/dev/null; then
fail "notification font set rejects unknown families"
fi
[[ $(mode) == "DejaVu Sans" ]] || fail "notification font set leaves the stored mode alone on rejection"
pass "notification font set rejects unknown families"

if font bogus 2>/dev/null; then
fail "notification font rejects unknown subcommands"
fi
pass "notification font rejects unknown subcommands"

[[ -n $(font list) ]] || fail "notification font list prints families"
pass "notification font list prints families"

# ------------------------------------------------------------- QML wiring

service_qml=$(cat "$ROOT/shell/plugins/notifications/Service.qml")
card_qml=$(cat "$ROOT/shell/plugins/notifications/components/NotificationCard.qml")

grep -q 'shellConfig.notifications' <<<"$service_qml" \
|| fail "notifications service reads the font setting from shellConfig"
pass "notifications service reads the font setting from shellConfig"

grep -q '"default"' <<<"$service_qml" \
|| fail "notifications service falls back to default mode"
pass "notifications service falls back to default mode"

grep -q 'notificationTextFontFamily' <<<"$service_qml" \
|| fail "notifications service resolves a text font family"
pass "notifications service resolves a text font family"

grep -q 'textFontFamily: service.notificationTextFontFamily' <<<"$service_qml" \
|| fail "notifications service passes the resolved font to the card"
pass "notifications service passes the resolved font to the card"

grep -q 'property string textFontFamily: "Liberation Sans"' <<<"$card_qml" \
|| fail "notification card keeps Liberation Sans as the default text font"
pass "notification card keeps Liberation Sans as the default text font"

[[ $(grep -c 'font.family: root.textFontFamily' <<<"$card_qml") == 2 ]] \
|| fail "notification card binds summary and body to the configured font"
pass "notification card binds summary and body to the configured font"

! grep -q 'font.family: "Liberation Sans"' <<<"$card_qml" \
|| fail "notification card has no hardcoded text font literals left"
pass "notification card has no hardcoded text font literals left"

grep -q 'font.family: root.fontFamily' <<<"$card_qml" \
|| fail "notification card keeps glyphs on the system font"
pass "notification card keeps glyphs on the system font"