From 6a0935d248c59c97c38536b9618b805a208dd5b5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 09:34:04 +0200 Subject: [PATCH 1/9] Improve notification --- bin/omarchy-voxtype-install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/omarchy-voxtype-install b/bin/omarchy-voxtype-install index e2ab8a53..32a1153e 100755 --- a/bin/omarchy-voxtype-install +++ b/bin/omarchy-voxtype-install @@ -21,5 +21,5 @@ if gum confirm "Install Voxtype + AI model (~150MB) to enable dictation?"; then voxtype setup systemd omarchy-restart-waybar - notify-send " Voxtype Dictation Ready" "Hold F9 (or toggle Super + Ctrl + X) to dictate.\nEdit ~/.config/voxtype/config.toml for options." -t 10000 + notify-send " Voxtype Dictation Ready" "Hold F9 to dictate (or toggle with Super + Ctrl + X)." -t 10000 fi From f0969c6793e92a2504b8cfc07f620adf392b64e2 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 09:46:00 +0200 Subject: [PATCH 2/9] Allow hooks to live in a .d directory --- bin/omarchy-hook | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/omarchy-hook b/bin/omarchy-hook index 85b5a180..db647881 100755 --- a/bin/omarchy-hook +++ b/bin/omarchy-hook @@ -1,6 +1,6 @@ #!/bin/bash -# omarchy:summary=Run a named hook, like post-update (available in ~/.config/omarchy/hooks/post-update). +# omarchy:summary=Run a named hook from ~/.config/omarchy/hooks/ and ~/.config/omarchy/hooks/.d/. # omarchy:args=[name] [args...] set -e @@ -12,8 +12,16 @@ fi HOOK=$1 HOOK_PATH="$HOME/.config/omarchy/hooks/$1" +HOOK_DIR="$HOOK_PATH.d" shift if [[ -f $HOOK_PATH ]]; then bash "$HOOK_PATH" "$@" fi + +if [[ -d $HOOK_DIR ]]; then + for hook in "$HOOK_DIR"/*; do + [[ -f $hook ]] || continue + bash "$hook" "$@" + done +fi From fe0ea4de3e8a5d779782a4b3754ee452a79c9343 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 09:59:18 +0200 Subject: [PATCH 3/9] Move to .d directories for hooks instead of a single file Easier to manage especially with separate installs --- bin/omarchy-hook | 1 + .../play-warning-sound.sample} | 2 +- .../show-font-notification.sample} | 4 ++-- .../show-update-notification.sample} | 2 +- .../show-theme-notification.sample} | 2 +- migrations/1772990935.sh | 6 +++--- 6 files changed, 9 insertions(+), 8 deletions(-) rename config/omarchy/hooks/{battery-low.sample => battery-low.d/play-warning-sound.sample} (95%) rename config/omarchy/hooks/{font-set.sample => font-set.d/show-font-notification.sample} (57%) rename config/omarchy/hooks/{post-update.sample => post-update.d/show-update-notification.sample} (79%) rename config/omarchy/hooks/{theme-set.sample => theme-set.d/show-theme-notification.sample} (78%) diff --git a/bin/omarchy-hook b/bin/omarchy-hook index db647881..c6c6f87f 100755 --- a/bin/omarchy-hook +++ b/bin/omarchy-hook @@ -22,6 +22,7 @@ fi if [[ -d $HOOK_DIR ]]; then for hook in "$HOOK_DIR"/*; do [[ -f $hook ]] || continue + [[ $hook == *.sample ]] && continue bash "$hook" "$@" done fi diff --git a/config/omarchy/hooks/battery-low.sample b/config/omarchy/hooks/battery-low.d/play-warning-sound.sample similarity index 95% rename from config/omarchy/hooks/battery-low.sample rename to config/omarchy/hooks/battery-low.d/play-warning-sound.sample index dc3726a9..61da3320 100644 --- a/config/omarchy/hooks/battery-low.sample +++ b/config/omarchy/hooks/battery-low.d/play-warning-sound.sample @@ -1,7 +1,7 @@ #!/bin/bash # This hook is called with the current battery percentage when the low battery -# notification is sent. To put it into use, remove .sample from the name. +# notification is sent. To put it into use, remove .sample from this file name. SOUND_FILE="/usr/share/sounds/freedesktop/stereo/dialog-warning.oga" diff --git a/config/omarchy/hooks/font-set.sample b/config/omarchy/hooks/font-set.d/show-font-notification.sample similarity index 57% rename from config/omarchy/hooks/font-set.sample rename to config/omarchy/hooks/font-set.d/show-font-notification.sample index 3f0109a9..a75f2fa8 100644 --- a/config/omarchy/hooks/font-set.sample +++ b/config/omarchy/hooks/font-set.d/show-font-notification.sample @@ -1,7 +1,7 @@ #!/bin/bash # This hook is called with the snake-cased name of the font that has just been set. -# To put it into use, remove .sample from the name. +# To put it into use, remove .sample from this file name. -# Example: Show the name of the theme that was just set. +# Example: Show the name of the font that was just set. # notify-send -u low "New font" "Your new font is $1" diff --git a/config/omarchy/hooks/post-update.sample b/config/omarchy/hooks/post-update.d/show-update-notification.sample similarity index 79% rename from config/omarchy/hooks/post-update.sample rename to config/omarchy/hooks/post-update.d/show-update-notification.sample index f740d5bb..f6ba07b0 100644 --- a/config/omarchy/hooks/post-update.sample +++ b/config/omarchy/hooks/post-update.d/show-update-notification.sample @@ -1,7 +1,7 @@ #!/bin/bash # This hook is called after an Omarchy system update has been performed. -# To put it into use, remove .sample from the name. +# To put it into use, remove .sample from this file name. # Example: Show notification after the system has been updated. # notify-send -u low "Update Performed" "Your system is now up to date" diff --git a/config/omarchy/hooks/theme-set.sample b/config/omarchy/hooks/theme-set.d/show-theme-notification.sample similarity index 78% rename from config/omarchy/hooks/theme-set.sample rename to config/omarchy/hooks/theme-set.d/show-theme-notification.sample index 2d13287f..db19b298 100644 --- a/config/omarchy/hooks/theme-set.sample +++ b/config/omarchy/hooks/theme-set.d/show-theme-notification.sample @@ -1,7 +1,7 @@ #!/bin/bash # This hook is called with the snake-cased name of the theme that has just been set. -# To put it into use, remove .sample from the name. +# To put it into use, remove .sample from this file name. # Example: Show the name of the theme that was just set. # notify-send -u low "New theme" "Your new theme is $1" diff --git a/migrations/1772990935.sh b/migrations/1772990935.sh index 2f3526c2..2b58912d 100644 --- a/migrations/1772990935.sh +++ b/migrations/1772990935.sh @@ -1,7 +1,7 @@ echo "Add sample low battery notification hook" -mkdir -p ~/.config/omarchy/hooks +mkdir -p ~/.config/omarchy/hooks/battery-low.d -if [[ ! -f ~/.config/omarchy/hooks/battery-low.sample ]]; then - cp "$OMARCHY_PATH/config/omarchy/hooks/battery-low.sample" ~/.config/omarchy/hooks/battery-low.sample +if [[ ! -f ~/.config/omarchy/hooks/battery-low.d/play-warning-sound.sample ]]; then + cp "$OMARCHY_PATH/config/omarchy/hooks/battery-low.d/play-warning-sound.sample" ~/.config/omarchy/hooks/battery-low.d/play-warning-sound.sample fi From c9aece1cb74f4b966c2adaced18db2834eab8532 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 10:33:12 +0200 Subject: [PATCH 4/9] Make sure individual hooks can't halt the process --- bin/omarchy-hook | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/omarchy-hook b/bin/omarchy-hook index c6c6f87f..8c2a59d9 100755 --- a/bin/omarchy-hook +++ b/bin/omarchy-hook @@ -16,13 +16,13 @@ HOOK_DIR="$HOOK_PATH.d" shift if [[ -f $HOOK_PATH ]]; then - bash "$HOOK_PATH" "$@" + bash "$HOOK_PATH" "$@" || echo "Hook failed: $HOOK_PATH" fi if [[ -d $HOOK_DIR ]]; then for hook in "$HOOK_DIR"/*; do [[ -f $hook ]] || continue [[ $hook == *.sample ]] && continue - bash "$hook" "$@" + bash "$hook" "$@" || echo "Hook failed: $hook" done fi From c8f65ccdb1643dde6e959d54255921e299c47195 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 10:34:28 +0200 Subject: [PATCH 5/9] Add a structured way to install a hook --- bin/omarchy-hook-install | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 bin/omarchy-hook-install diff --git a/bin/omarchy-hook-install b/bin/omarchy-hook-install new file mode 100755 index 00000000..7c536482 --- /dev/null +++ b/bin/omarchy-hook-install @@ -0,0 +1,31 @@ +#!/bin/bash + +# omarchy:summary=Install a hook into ~/.config/omarchy/hooks/.d/ +# omarchy:group=hook +# omarchy:name=install +# omarchy:args= +# omarchy:examples=omarchy hook install post-update ~/my-hook + +set -e + +if (( $# != 2 )); then + echo "Usage: omarchy-hook-install " + exit 1 +fi + +HOOK_TYPE=$1 +HOOK_FILE=$2 +HOOK_DIR="$HOME/.config/omarchy/hooks/$HOOK_TYPE.d" +HOOK_NAME=$(basename "$HOOK_FILE") +HOOK_PATH="$HOOK_DIR/$HOOK_NAME" + +if [[ ! -f $HOOK_FILE ]]; then + echo "Hook file not found: $HOOK_FILE" + exit 1 +fi + +mkdir -p "$HOOK_DIR" +cp "$HOOK_FILE" "$HOOK_PATH" +chmod 755 "$HOOK_PATH" + +echo "Installed $HOOK_TYPE hook: $HOOK_PATH" From 42cd7472cfc3093ca7e0180bc94c156717f4ed8d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 10:35:23 +0200 Subject: [PATCH 6/9] Give people an invitation to install dictation after the first update --- bin/omarchy-first-run | 1 + default/mako/core.ini | 3 +++ install/first-run/install-voxtype.hook | 8 ++++++++ 3 files changed, 12 insertions(+) create mode 100644 install/first-run/install-voxtype.hook diff --git a/bin/omarchy-first-run b/bin/omarchy-first-run index 8fde0534..6ad54141 100755 --- a/bin/omarchy-first-run +++ b/bin/omarchy-first-run @@ -18,6 +18,7 @@ if [[ -f $FIRST_RUN_MODE ]]; then bash "$OMARCHY_PATH/install/first-run/gnome-theme.sh" bash "$OMARCHY_PATH/install/first-run/gtk-primary-paste.sh" bash "$OMARCHY_PATH/install/first-run/elephant.sh" + omarchy-hook-install post-update "$OMARCHY_PATH/install/first-run/install-voxtype.hook" sudo rm -f /etc/sudoers.d/first-run bash "$OMARCHY_PATH/install/first-run/welcome.sh" diff --git a/default/mako/core.ini b/default/mako/core.ini index 203d4e8c..78c24452 100644 --- a/default/mako/core.ini +++ b/default/mako/core.ini @@ -29,6 +29,9 @@ on-button-left=exec sh -c 'omarchy-notification-dismiss "Update System"; omarchy [summary~="Learn Keybindings"] on-button-left=exec sh -c 'omarchy-notification-dismiss "Learn Keybindings"; omarchy-menu-keybindings' +[summary~="Install Dictation with Voxtype"] +on-button-left=exec sh -c 'omarchy-notification-dismiss "Install Dictation with Voxtype"; omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install' + [summary~="Screenshot copied & saved"] max-icon-size=80 format=%s\n%b diff --git a/install/first-run/install-voxtype.hook b/install/first-run/install-voxtype.hook new file mode 100644 index 00000000..5513c4e6 --- /dev/null +++ b/install/first-run/install-voxtype.hook @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +# Remove this hook after use +rm -f "$0" + +notify-send " Install Dictation with Voxtype" "Click to install voice dictation for Omarchy." -u critical From 6b6d71a9d2c2228f0e22e92cdfdf155ffe96c496 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 10:48:32 +0200 Subject: [PATCH 7/9] Add live weather (#5633) * Add live weather * Just use jq * Add Weather app and make it click target * Simplify * Looks better * Nicer flow * Simplify * Move to using the notification instead of tooltip * No longer needed --- bin/omarchy | 1 + bin/omarchy-weather-icon | 36 ++++++++++++++++++++++++++++ bin/omarchy-weather-status | 26 ++++++++++++++++++++ config/waybar/config.jsonc | 9 ++++++- config/waybar/style.css | 11 +++++++++ default/hypr/bindings/utilities.conf | 1 + default/waybar/weather.sh | 10 ++++++++ migrations/1778139028.sh | 29 ++++++++++++++++++++++ 8 files changed, 122 insertions(+), 1 deletion(-) create mode 100755 bin/omarchy-weather-icon create mode 100755 bin/omarchy-weather-status create mode 100755 default/waybar/weather.sh create mode 100644 migrations/1778139028.sh diff --git a/bin/omarchy b/bin/omarchy index ad743ce7..29b7cfb4 100755 --- a/bin/omarchy +++ b/bin/omarchy @@ -69,6 +69,7 @@ GROUP_DESCRIPTIONS[tz]="Timezone selection" GROUP_DESCRIPTIONS[update]="Omarchy and system updates" GROUP_DESCRIPTIONS[version]="Version and channel information" GROUP_DESCRIPTIONS[voxtype]="Voxtype dictation" +GROUP_DESCRIPTIONS[weather]="Weather status" GROUP_DESCRIPTIONS[webapp]="Web app launchers" GROUP_DESCRIPTIONS[wifi]="Wi-Fi helpers" GROUP_DESCRIPTIONS[windows]="Windows VM management" diff --git a/bin/omarchy-weather-icon b/bin/omarchy-weather-icon new file mode 100755 index 00000000..072af706 --- /dev/null +++ b/bin/omarchy-weather-icon @@ -0,0 +1,36 @@ +#!/bin/bash + +# omarchy:summary=Returns a weather condition icon, adjusted for live sunrise and sunset. + +weather_data=$(curl -fsS --max-time 3 "https://wttr.in?format=j1" 2>/dev/null | jq -er '[.current_condition[0].weatherCode, .weather[0].astronomy[0].sunrise, .weather[0].astronomy[0].sunset] | select(all(. != null and . != "")) | @tsv' 2>/dev/null) || exit 1 + +IFS=$'\t' read -r weather_code sunrise sunset <<< "$weather_data" +if [[ ! $weather_code =~ ^[0-9]+$ || ! $sunrise =~ ^[0-9]{1,2}:[0-9]{2}\ [AP]M$ || ! $sunset =~ ^[0-9]{1,2}:[0-9]{2}\ [AP]M$ ]]; then + exit 1 +fi + +now_epoch=$(date +%s) +sunrise_epoch=$(date -d "today $sunrise" +%s 2>/dev/null || echo 0) +sunset_epoch=$(date -d "today $sunset" +%s 2>/dev/null || echo 0) + +if (( sunrise_epoch > 0 && sunset_epoch > 0 && (now_epoch < sunrise_epoch || now_epoch >= sunset_epoch) )); then + night=true +else + night=false +fi + +case $weather_code in + 113) [[ $night == "true" ]] && icon="" || icon="" ;; + 116) [[ $night == "true" ]] && icon="" || icon="" ;; + 119|122) icon="" ;; + 143|248|260) icon="" ;; + 176|263|266|293|296|353) [[ $night == "true" ]] && icon="" || icon="" ;; + 179|227|230|323|326|368) [[ $night == "true" ]] && icon="" || icon="" ;; + 182|185|281|284|311|314|317|320|350|362|365|374|377) icon="" ;; + 200|386|389|392|395) icon="" ;; + 299|302|305|308|356|359) icon="" ;; + 329|332|335|338|371) icon="" ;; + *) icon="" ;; +esac + +printf '%s\n' "$icon" diff --git a/bin/omarchy-weather-status b/bin/omarchy-weather-status new file mode 100755 index 00000000..126d6582 --- /dev/null +++ b/bin/omarchy-weather-status @@ -0,0 +1,26 @@ +#!/bin/bash + +# omarchy:summary=Returns a formatted weather status string with temperature and wind speed. + +weather=$(curl -fsS --max-time 4 "https://wttr.in?format=%l|%t|%w" 2>/dev/null | tr -d '\n') + +if [[ -z $weather ]]; then + echo "Weather unavailable" + exit 1 +fi + +IFS='|' read -r place temperature wind <<< "$weather" +place=${place%%,*} +place=${place^} +format_temperature() { + local celsius=${1#+} + celsius=${celsius//°/} + celsius=${celsius%C} + + echo "${celsius}°c / $((celsius * 9 / 5 + 32))°f" +} + +temperature=$(format_temperature "$temperature") +wind=${wind//km\// km/} + +echo "$(omarchy-weather-icon) $place · Temperature $temperature · Wind $wind" diff --git a/config/waybar/config.jsonc b/config/waybar/config.jsonc index cbf211c3..61a11eca 100644 --- a/config/waybar/config.jsonc +++ b/config/waybar/config.jsonc @@ -5,7 +5,7 @@ "spacing": 0, "height": 26, "modules-left": ["custom/omarchy", "hyprland/workspaces"], - "modules-center": ["clock", "custom/update", "custom/voxtype", "custom/screenrecording-indicator", "custom/idle-indicator", "custom/notification-silencing-indicator"], + "modules-center": ["clock", "custom/weather", "custom/update", "custom/voxtype", "custom/screenrecording-indicator", "custom/idle-indicator", "custom/notification-silencing-indicator"], "modules-right": [ "group/tray-expander", "bluetooth", @@ -66,6 +66,13 @@ "tooltip": false, "on-click-right": "omarchy-launch-floating-terminal-with-presentation omarchy-tz-select" }, + "custom/weather": { + "exec": "$OMARCHY_PATH/default/waybar/weather.sh", + "return-type": "json", + "interval": 600, + "tooltip": false, + "on-click": "notify-send -u low \"$(omarchy-weather-status)\"" + }, "network": { "format-icons": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"], "format": "{icon}", diff --git a/config/waybar/style.css b/config/waybar/style.css index 4bbf0ddb..63302efa 100644 --- a/config/waybar/style.css +++ b/config/waybar/style.css @@ -67,6 +67,17 @@ tooltip { margin-left: 8.75px; } +#custom-weather { + margin-left: 7.5px; + margin-right: 7.5px; +} + +#custom-weather.unavailable { + min-width: 0; + margin: 0; + padding: 0; +} + .hidden { opacity: 0; } diff --git a/default/hypr/bindings/utilities.conf b/default/hypr/bindings/utilities.conf index 083a4c91..c152f884 100644 --- a/default/hypr/bindings/utilities.conf +++ b/default/hypr/bindings/utilities.conf @@ -49,6 +49,7 @@ bindd = SUPER CTRL, R, Transcode, exec, omarchy-menu transcode # Waybar-less information bindd = SUPER CTRL ALT, T, Show time, exec, notify-send -u low " $(date +"%A %H:%M · %d %B %Y · Week %V")" bindd = SUPER CTRL ALT, B, Show battery remaining, exec, notify-send -u low "$(omarchy-battery-status)" +bindd = SUPER CTRL ALT, W, Show weather, exec, notify-send -u low "$(omarchy-weather-status)" # Control panels bindd = SUPER CTRL, A, Audio controls, exec, omarchy-launch-audio diff --git a/default/waybar/weather.sh b/default/waybar/weather.sh new file mode 100755 index 00000000..02e7bc2b --- /dev/null +++ b/default/waybar/weather.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +icon=$(omarchy-weather-icon 2>/dev/null) + +if [[ -n $icon ]]; then + icon=$(printf '%s' "$icon" | sed 's/["\\]/\\&/g') + printf '{"text":"%s"}\n' "$icon" +else + printf '{"text":"","class":"unavailable"}\n' +fi diff --git a/migrations/1778139028.sh b/migrations/1778139028.sh new file mode 100644 index 00000000..e5728ced --- /dev/null +++ b/migrations/1778139028.sh @@ -0,0 +1,29 @@ +echo "Add weather widget to Waybar" + +WAYBAR_CONFIG="$HOME/.config/waybar/config.jsonc" +WAYBAR_STYLE="$HOME/.config/waybar/style.css" + +if [[ -f $WAYBAR_CONFIG ]]; then + if ! grep -q '"custom/weather"' "$WAYBAR_CONFIG"; then + sed -i 's/"modules-center": \["clock",/"modules-center": ["clock", "custom\/weather",/' "$WAYBAR_CONFIG" + sed -i '/"network": {/i\ "custom/weather": {\n "exec": "$OMARCHY_PATH/default/waybar/weather.sh",\n "return-type": "json",\n "interval": 600,\n "tooltip": false,\n "on-click": "notify-send -u low \\"$(omarchy-weather-status)\\""\n },' "$WAYBAR_CONFIG" + fi +fi + +if [[ -f $WAYBAR_STYLE ]] && ! grep -q '#custom-weather' "$WAYBAR_STYLE"; then + cat >>"$WAYBAR_STYLE" <<'EOF' + +#custom-weather { + margin-left: 7.5px; + margin-right: 7.5px; +} + +#custom-weather.unavailable { + min-width: 0; + margin: 0; + padding: 0; +} +EOF +fi + +omarchy-restart-waybar From 5cf83d207382aaa8ab7e2eb912204f5ec93b67f3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 7 May 2026 11:16:40 +0200 Subject: [PATCH 8/9] Match sddm to decrypt style (#5645) * Make SDDM screen match the normal decrypt screen as close as possible * Try with a different error style on wrong pw * Do red lock on failure too --- bin/omarchy-plymouth-set | 10 +- default/sddm/omarchy/Main.qml | 173 ++++++++++++++------------ default/sddm/omarchy/bullet.png | Bin 0 -> 293 bytes default/sddm/omarchy/entry-failed.png | Bin 0 -> 419 bytes default/sddm/omarchy/entry.png | Bin 0 -> 694 bytes default/sddm/omarchy/lock-failed.png | Bin 0 -> 1444 bytes default/sddm/omarchy/lock.png | Bin 0 -> 1537 bytes default/sddm/omarchy/logo.png | Bin 0 -> 3072 bytes default/sddm/omarchy/logo.svg | 1 - 9 files changed, 103 insertions(+), 81 deletions(-) create mode 100644 default/sddm/omarchy/bullet.png create mode 100644 default/sddm/omarchy/entry-failed.png create mode 100644 default/sddm/omarchy/entry.png create mode 100644 default/sddm/omarchy/lock-failed.png create mode 100644 default/sddm/omarchy/lock.png create mode 100644 default/sddm/omarchy/logo.png delete mode 100644 default/sddm/omarchy/logo.svg diff --git a/bin/omarchy-plymouth-set b/bin/omarchy-plymouth-set index ff9ca27d..c92ca53c 100755 --- a/bin/omarchy-plymouth-set +++ b/bin/omarchy-plymouth-set @@ -68,10 +68,16 @@ sddm_dir="/usr/share/sddm/themes/omarchy" sddm_template="$HOME/.local/share/omarchy/default/sddm/omarchy/Main.qml" sed \ - -e "s/#000000/#$bg_hex/g" \ + -e "s/#1a1b26/#$bg_hex/g" \ -e "s/#ffffff/#$text_hex/g" \ - -e 's|source: "logo.svg"|source: "logo.png"|' \ "$sddm_template" | sudo tee "$sddm_dir/Main.qml" >/dev/null sudo cp "$logo_path" "$sddm_dir/logo.png" +for asset in bullet.png entry.png lock.png; do + sudo cp "$staging_dir/$asset" "$sddm_dir/$asset" +done +for asset in entry lock; do + magick "$staging_dir/$asset.png" -channel RGB +level-colors "#f7768e","#f7768e" "$staging_dir/$asset-failed.png" + sudo cp "$staging_dir/$asset-failed.png" "$sddm_dir/$asset-failed.png" +done sudo rm -f "$sddm_dir/logo.svg" diff --git a/default/sddm/omarchy/Main.qml b/default/sddm/omarchy/Main.qml index d074913e..a65411e2 100644 --- a/default/sddm/omarchy/Main.qml +++ b/default/sddm/omarchy/Main.qml @@ -2,98 +2,115 @@ import QtQuick 2.0 import SddmComponents 2.0 Rectangle { - id: root - width: 640 - height: 480 - color: "#000000" + id: root + width: 640 + height: 480 + color: "#1a1b26" - property string currentUser: userModel.lastUser - property int sessionIndex: { - for (var i = 0; i < sessionModel.rowCount(); i++) { - var name = (sessionModel.data(sessionModel.index(i, 0), Qt.DisplayRole) || "").toString() - if (name.indexOf("uwsm") !== -1) - return i - } - return sessionModel.lastIndex + property string currentUser: userModel.lastUser + property bool loginFailed: false + property int sessionIndex: { + for (var i = 0; i < sessionModel.rowCount(); i++) { + var name = (sessionModel.data(sessionModel.index(i, 0), Qt.DisplayRole) || "").toString() + if (name.indexOf("uwsm") !== -1) + return i + } + return sessionModel.lastIndex + } + + Connections { + target: sddm + function onLoginFailed() { + root.loginFailed = true + password.text = "" + password.focus = true + } + function onLoginSucceeded() { + root.loginFailed = false + } + } + + Column { + anchors.centerIn: parent + spacing: 40 + + Image { + id: logo + source: "logo.png" + width: Math.min(sourceSize.width, root.width * 0.8) + height: sourceSize.width > 0 ? Math.round(width * sourceSize.height / sourceSize.width) : 0 + fillMode: Image.PreserveAspectFit + anchors.horizontalCenter: parent.horizontalCenter } - Connections { - target: sddm - function onLoginFailed() { - errorMessage.text = "Login failed" - password.text = "" - password.focus = true - } - function onLoginSucceeded() { - errorMessage.text = "" - } - } + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 15 - Column { - anchors.centerIn: parent - spacing: root.height * 0.04 - width: parent.width + Image { + source: root.loginFailed ? "lock-failed.png" : "lock.png" + width: 34 + height: 38 + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter + } + + Item { + width: entry.width + height: entry.height Image { - source: "logo.svg" - width: root.width * 0.35 - height: Math.round(width * sourceSize.height / sourceSize.width) - fillMode: Image.PreserveAspectFit - anchors.horizontalCenter: parent.horizontalCenter + id: entry + source: root.loginFailed ? "entry-failed.png" : "entry.png" + anchors.centerIn: parent } Row { - anchors.horizontalCenter: parent.horizontalCenter - spacing: root.width * 0.007 + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.verticalCenter: parent.verticalCenter + spacing: 5 - Text { - text: "\uf023" - color: "#ffffff" - font.family: "JetBrainsMono Nerd Font" - font.pixelSize: root.height * 0.025 - anchors.verticalCenter: parent.verticalCenter - } - - Rectangle { - width: root.width * 0.17 - height: root.height * 0.04 - color: "#000000" - border.color: "#ffffff" - border.width: 1 - clip: true - - TextInput { - id: password - anchors.fill: parent - anchors.margins: root.height * 0.008 - verticalAlignment: TextInput.AlignVCenter - echoMode: TextInput.Password - font.family: "JetBrainsMono Nerd Font" - font.pixelSize: root.height * 0.02 - font.letterSpacing: root.height * 0.004 - passwordCharacter: "\u2022" - color: "#ffffff" - focus: true - - Keys.onPressed: { - if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - sddm.login(root.currentUser, password.text, root.sessionIndex) - event.accepted = true - } - } - } + Repeater { + model: Math.min(password.text.length, 21) + + Image { + source: "bullet.png" + width: 7 + height: 7 } + } } - Text { - id: errorMessage - text: "" - color: "#f7768e" - font.family: "JetBrainsMono Nerd Font" - font.pixelSize: root.height * 0.018 - anchors.horizontalCenter: parent.horizontalCenter + TextInput { + id: password + anchors.fill: parent + anchors.leftMargin: 20 + anchors.rightMargin: 20 + verticalAlignment: TextInput.AlignVCenter + echoMode: TextInput.Password + font.family: "JetBrainsMono Nerd Font" + font.pixelSize: 24 + font.letterSpacing: 5 + passwordCharacter: "\u2022" + color: "transparent" + selectionColor: "transparent" + selectedTextColor: "transparent" + focus: true + + onTextChanged: root.loginFailed = false + + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + sddm.login(root.currentUser, password.text, root.sessionIndex) + event.accepted = true + } + } } + } } - Component.onCompleted: password.forceActiveFocus() + } + + Component.onCompleted: password.forceActiveFocus() } diff --git a/default/sddm/omarchy/bullet.png b/default/sddm/omarchy/bullet.png new file mode 100644 index 0000000000000000000000000000000000000000..62249b3bd82b0b670a8a503a3bb69bbfe79cc4f4 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xamSQK*5Dp-y;YjHK^6z-MIEH9U zoIA;|uZcmx^}qZQCjEr9ou<6JoZt7m0FRe~3I-q?kdfVZu-@kQlF=GdcP{_{ literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/entry-failed.png b/default/sddm/omarchy/entry-failed.png new file mode 100644 index 0000000000000000000000000000000000000000..a42f9188478f536ac0e439b61bcf86db9919346e GIT binary patch literal 419 zcmeAS@N?(olHy`uVBq!ia0y~yV3Y&04Op0gq^TK)IFM3E_6YK2V5m}KU}$JzVE6?T zYIwoGP-?)y@G60U!DUO_QmvAUQh^kM zk%5t!u7Rnpfq965g_Ws+m4T7A0gyCcysEw%MMG|WN@iLmZVd$;^VxwKG~hOrWag$8 zmn7yEpzATSGBSWz^562tX`r4sPZ!6Kh{JDhItn%z2(TRNZgvn6U>Etf_n^7pfxC;{ z9k17!-wIqQ_ju(MS>@_UOV1advx>Dr0j!T6Lcq_%lWPUv)H{A_-Sk&e7pV9j(}Cj3 VCsvWJ$AC65c)I$ztaD0e0su?Dgzo?V literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/entry.png b/default/sddm/omarchy/entry.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7891792b8872db933f17a667a449a478c0be53 GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yV3Y&04FuSLa@a2CEqi4B`cIb_Lo1CD@X@-Ch2J0cXVbJ3tZk5>H=O_LuA|BAOCC zHaNEIWB8gpRxhIrP-q3h6ugFYL#HXzL zs@XQt|A*ck*2`WtwNt_B=Yyw}k1W;qd^{d$X}`l_w|(sWyd1OlR=bb4vobhzyu2J; zZnSHU?(>Dgw^zkpzx{Xds&+;O9q+{%->h5%r*w;NJ|6l0@y}PwUNtf>?BDuhVsPE} zg8wPcOLO0+EPi#6fx-T(fyDNE?akAVe!dFQ`p5hrGs6*K8wLlPp~KkoX?J(~!5v@i zWcGyE3NSoazmLT~cXe58{`dY>a%>D6be?{Bxz<`T_MTZ++WO$HQVb2Mv&zq@Pirx0 z4OBAy4vaL_64!{5l*E!$tK_0oAjM#0U}UOmV6JOm5@KXvWnyS$WTI_gU}a!%k#!|7 zD3LVe=BH$)RpQpLAVSa+oZUJsR-+c`p0rfC=y85}Sb4q9e01cG# AHUIzs literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/lock-failed.png b/default/sddm/omarchy/lock-failed.png new file mode 100644 index 0000000000000000000000000000000000000000..a88afece7ac58e0a76d6177d7d4cb9ce11791138 GIT binary patch literal 1444 zcmeAS@N?(olHy`uVBq!ia0vp^AwZnK!3-p~#4YszQVPi)LB0$ORcZ_j4J`}|zkosw zFBlj~4Hy_+B``2p&0t^k#5KkdlGMqr5NmIV0) zGcf5zHOyPTn>%*RTa7ti6j%IXtNwn<^?mp+cjZs)XIs}*hPoUH-1^YRaFgKbxy&NJ zv+wX8aN_u}-O(Zc5#!XEI}?vuU(|Y_5V!2g2d?J3pFgsk&$wqbjpvowAIn7^asn!0 zX{Ym?cg)v3>AOLQf5kOEpv8o>z%ybP*bq&lz3@ofn4Xg}|v<-ly z0pnHm-6$Gz^HVa@DsgKl=$OwA)Sv;kp(HamwYVfPw*Xy_rIm>R#F7)ST?ZK$m=<}u zIEF+VetRQ4{F8$~!$XZMod^cOz>qgdj=VP<-e^edPI}KOGUI=Jwu`E;S6SoEpMQ+X zPRyQ`R?M1` zJ)4!QZ|T~tK9{nx+ZSEV+dQpwcG(5lYDx3k4;hYGSb3RR&(#q=a5HjN=B5q4Pd^(? zO)4wAlm0Aj_m8uA;`2Elyn1F@T~+DZa8J%}i~j%SvhVyne0*Paf8H-tTeHmaJFl$N zxw?yM4?FM7*S_yx=2Ct2#f)!HHwM0at!%T*z547AmplW*b-Fe(FP1#((#)@WIQNpu z#2oKo3N_^d-g4V`$3(I2lW85D~G2eA~yDV-; z=OJFUU9Gn^#7Ve46l1>Y_O>r>NAsavX44Dx5?@^x?dFS|_EGMX>T8~RMT+o7rUKza_YIo(!ps~bw$nP<;4{WmliK7b9OVXWBwbx;+U9?ukd49;3 zP9D+FDe2RX>~wRjJImpJw$p09F}wZ3^mR|CRNt+ytgWuDeVG0K9^X+1Z^y@9GGaU~ zEap7hsCkj4wY=&}tbpB$v;0prCoXZD9DjbE$l95wY?LntJ)L9~^y~SZu7IhV)KfAy zl`BkjTOR9K{ZygBc4o1jIG@_1e_U$wIMVo!*)vAG@La=^Dg-QE85lfW{an^LB{Ts5 D{FkOh literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/lock.png b/default/sddm/omarchy/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..3046de1b6a3f20d194f010fee671c936326beb27 GIT binary patch literal 1537 zcmaKseKgYx7{|AHUkq{cvLRb`y@V2`mnmXdjLEhr)$(pBuF;Fg7MiYcO37PGhEj}Q zjkx2~FG}6iyi`n^Yf__8QcEw2yZ-5)d(J)Q^E}@_p65B=bDrm%=QKHZn*rPs4uL=n zhyi{()JjnYT~}M3b6C_2wdkb;L@*(cWhOrZ3aPkg4uNP1iGDtmqZi-uNcB{I6qr4G z#i8)RDVJ5cL1!Nt{l+uGVeBIuqiPA1leLuzROguk=T?Ra9MON;!yO9zRCRCo6Z~dO zZEkEpocTDP92U}<@#4T*rj2a+(x$Pv@seyCNY93qf#zr(*_!G?;{%hf$7{)0wjI)D_X( zm}u}Ln8`jY-p@J5VO8CjI08(3E0;){?Q-|cD59AsumI2J9^x48{P#=1M@1> z`3H_14&4M@jmRG{e0-T%a$_};xp+UGO5W(w?k>lx9F;;;TAULlesb+OonM22Q!c81az}6c*t>v7~tOVPm zhU!UpisjNWSukBNpiE6b9Q+!olgTZa?zW>>3@H1~d|7^cD&u?XCD2;IcY{V&A|v%} zdgu$wb94J!*DI4eckMCCA2lAHSD>gLRdsonw|WzWbCTnPs>+EhLc?+2mx$6;Pv&90 z`0L*_g4}j}dR=HC;<$8$V8H_R+5)ZzLV8t}3+GKlEaK#JdXNo5RObC<{#qWnTDYlq z)?=4}W)I5KWoU07<8~IqmNFI8_hC%4r#B}#Xw19S$T7|LGujzt$Su_wD^oXACsp6Y z@KIlS8YxqxORtv@zY6`OX~XEy`ZfP$tM|_>py#PwsWV|^yI&|}5<#J-3naFWlPt&0 zMM1pA=0DF$cD`C*@wVbov-TLG5Xo`|Cp3#t1lw$AGfc{*?c{bf87cSbWy55&%tM%p zeXIrPK75<{GLPwF=XCHZ+fTLU^X`|rK`Cd;+jrMtGa(^cXr+} zgVU;=CUsvl!V_DzOK)XsWtP;Abb!{3qlSAP;8kvbDCe<{Y>C!!vz1@u+lCa}en)#) z;t*rZHexumT)tUH-d4SN1?85h(q5JM8U5Fz3%*u)MG1WJ{{KN~pSIm6Hn6(LH){pv ze0V$g{08_ag*>vOzNWv9Sb-lDoN*GiYdmB~>U7k^= z!sR4-SaIB}#^(07LNV=C+^Wxvp#$8peZDi2`#_iZs= zqu`0Tb?EN1+Q1#UGG4l`!VHb{C~R_k3u*CyVN%VraV9KWDT5xw*ws95W?`)tyZ4)2<_Ue8M2_Lb zaSg1=`gadV(~oTBd5ZNyUQ=(QUE*OKK)-|hxBH_7i+@D@94nGF6k#qnb#ZuUGt_nK Vg$$|X5%o`p5dDMwntbUe{{_St%Ul2e literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/logo.png b/default/sddm/omarchy/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f185b2660763bebf2343775d2a682133d5bf78d2 GIT binary patch literal 3072 zcmb7`dpwi<8^>>l=qXa_p(N|vOGGPYhGLi+QCbc)wX$TC#G18HejOx@XQ8JxE6K`X zni(~wJcT0b$sxxnDQvUJlO34D@2)?7uU^03^Splfp+o^$^kjx+aiZi_`>w)u{kD<}Yyv zeX!A-4*(fnfCd1x%wG)v?j{=A0N}GO8MHgnS8w=p-P`fatOEAO!K#L|&c*B8BJ5M< zKTSV+EHKwLpyqmJcfOpk{n|E_MGu=qNKy`O^J;{ui5ORG$7dcaKi;t+n=PAUG1*BC z9+J4%*>d_gEW)Fd84A6l&>FGCJa;HhxZB%z1KHOuXXIuXGIZREkfeQiu2;uQ#xJKp zEx4|f5M>JEU>yWf;lByG)84pCVe-XJWr3+F2(wM9M&-E}kQE*iGsPs{%609P)>asy zT&O#<_&uZC-vFaEE=_9+!IPBdnX~w_c=C|SQs<+QB^ZPBpNv7G!CDu-L+@yzIg+z9 zD%|iG^G#dhgzS`7@Fj(8`VCL)TLOWX)QX4WtojaImapJK?-JH0mQ)oqKsVG?iR$Sc zpAc=7r~#9$I*f}#~e0iq6IL%hfZ)qb6 zaAN%3S z+lpX@bZ4-hBXqiQR?Tqk2o0sT=InY1fv<|WXoVqpr3c^OK`dmnv=kz8yNE60>F$}< zl(ILc$m{xig^`rdp-EFtjxnVG)trDwnxUecHk6<<-FdHGsxBkXg&00rDU-2PYpbPDT%+$8$2anSv(+QmRX&$t= zz#RQ%RIf5XIk2?qA6XLrw>{Un4C%^pW1hXtMxJn=bsZRJVdQ|ysSx_ zCiyVv{ZdgS$-UXvMkAj#)Cvwnhb=_zWn`J9b!|EDU+Jg4cn&)K$;ELN0Pybpv2+!W zQjDrZk6H$a4VyHI^(w(X#kiRMo7IAh3d$s3m_Is}0?g{g4k^TVhNw<=JJaX50jfO|E6`y!)ZlW>SU`3ue<5~ z3ufkPwENsnBwU|<$W)N{s~ytPV^mqGQ+Bzwgjyqcfq%VCudAT~qyB3~_VyTVzJ_+V zepU#>yF?B3+*?ZO%bJHy-@Hm^y>J&H^nBw&NKV5I}W0OZ=765$6i{evd{UOT!7aRo=*Ni|i7Xe*2lwS?2{sg!tqMWjDA#uYXtkjQ;OHjz4q7}b@A zV}+@&B!WK>x96?5*x(vj+5>^}oYMP;(<~x_N$fUNwK-M5MdhuCu@0380u}WEvoIZ- zho9fZn|}kg?R#fe(Pk&U!)@-Gqt5_CjzTNR$b$6-Z?B4ynOS!;sVrXW*OcHT?si?D zx4qjmTAHe&W9pB$cSb@Ij&McacNy!Lo%|HU;296;dgxpdIgO;3RyIoT#P+1dIh|&& zcyIYCR1S(Qgh62}e0Rg_m0vsXR|jrJAM8MZ1uTul3luX0B;-fiPV9DiQ-`p9g||HM zVz%7-Q+`p5WO5P-jKa}jTO=*U>f$OeqKcuJHJ1t2aN_G49HlF-D8{*hNXPOuUrr0L z_Oo3Glu%UHPgcWA&jBLmy4crb`N^kG93!3mds{~rDR%v`{|cg}=)NaG{S2C7;QOCH zC!BPNQr25Q-1C0F-8^c_sB`bV0JfMv(c68O0J3z)zev#y?X;o@bxiT&iM|sCYo<0- z+?pKW;pk`Dqu+P?F}%82%%=Or%ux1~4G)k?&-S;p-F{+9tkJ9NSb;)@-xcDYud0Tf zbS4gDNa7+%nM$Vh3?J-CB&P+7_y#swE5lRx4sK_AT$|T+e5~!G_GFHXPkng6ABJ5& zU*10;%b=duu)8=Xg|9ec=mZpB3^~+M^4BmU3+G>9t(A=44QM1k4f?43Dja|VTOH<9 bg1%yVhS4QqD-cs);|n0*ex9tI;TQf5WcB8Z literal 0 HcmV?d00001 diff --git a/default/sddm/omarchy/logo.svg b/default/sddm/omarchy/logo.svg deleted file mode 100644 index 0853b047..00000000 --- a/default/sddm/omarchy/logo.svg +++ /dev/null @@ -1 +0,0 @@ - From ec4a3d024915114bd770bd98ea6eba7958e1a9ca Mon Sep 17 00:00:00 2001 From: Gavin Nugent Date: Thu, 7 May 2026 10:19:31 +0100 Subject: [PATCH 9/9] Add Transcode entry to Nautilus context menu (#5635) * Add Transcode entry to Nautilus context menu Right-click on image or video files in Nautilus to send them to omarchy-transcode in a presentation terminal, mirroring the existing LocalSend integration. Co-Authored-By: Claude Opus 4.7 (1M context) * Use shlex.quote for shell-safe command construction in transcode.py Quote both the resolved binary path and file paths via shlex.quote instead of a custom escaper. Addresses Copilot review on #5635. Co-Authored-By: Claude Opus 4.7 (1M context) * Style changes --------- Co-authored-by: Claude Opus 4.7 (1M context) Co-authored-by: David Heinemeier Hansson --- .../nautilus-python/extensions/transcode.py | 94 +++++++++++++++++++ install/config/nautilus-python.sh | 1 + migrations/1778099894.sh | 3 + 3 files changed, 98 insertions(+) create mode 100644 default/nautilus-python/extensions/transcode.py create mode 100644 migrations/1778099894.sh diff --git a/default/nautilus-python/extensions/transcode.py b/default/nautilus-python/extensions/transcode.py new file mode 100644 index 00000000..be1dfe4b --- /dev/null +++ b/default/nautilus-python/extensions/transcode.py @@ -0,0 +1,94 @@ +import shlex +import shutil + +from gi import require_version + +require_version("Nautilus", "4.1") + +from gi.repository import GObject, Gio, Nautilus + + +SUPPORTED_MIME_PREFIXES = ("image/", "video/") +SUPPORTED_EXTENSIONS = { + ".jpg", ".jpeg", ".png", ".webp", ".gif", ".heic", ".avif", + ".mp4", ".mov", ".m4v", ".mkv", ".webm", ".avi", +} + + +class TranscodeAction(GObject.GObject, Nautilus.MenuProvider): + def _launch_transcode(self, paths): + wrapper = shutil.which("omarchy-launch-floating-terminal-with-presentation") + binary = shutil.which("omarchy-transcode") + if not wrapper or not binary: + return + + if len(paths) == 1: + cmd = shlex.join([binary, paths[0]]) + else: + cmd = "; ".join( + f"echo {shlex.quote(f'Transcoding {path}')} && " + f"{shlex.join([binary, path])} || true" + for path in paths + ) + + Gio.Subprocess.new([wrapper, cmd], Gio.SubprocessFlags.NONE) + + def _is_supported(self, file): + mime = file.get_mime_type() or "" + if mime.startswith(SUPPORTED_MIME_PREFIXES): + return True + + location = file.get_location() + if not location: + return False + path = location.get_path() or "" + lower = path.lower() + return any(lower.endswith(ext) for ext in SUPPORTED_EXTENSIONS) + + def _selected_paths(self, files): + paths = [] + seen = set() + + for file in files: + if file.is_directory(): + continue + if not self._is_supported(file): + continue + location = file.get_location() + if not location: + continue + path = location.get_path() + if path and path not in seen: + seen.add(path) + paths.append(path) + return paths + + def _make_item(self, paths): + label = "Transcode" if len(paths) == 1 else f"Transcode {len(paths)} items" + item = Nautilus.MenuItem( + name="OmarchyTranscodeNautilus::transcode", + label=label, + icon="media-playback-start", + ) + item.connect("activate", self._on_activate, paths) + return item + + def _on_activate(self, _menu, paths): + self._launch_transcode(paths) + + def _tools_available(self): + return bool( + shutil.which("omarchy-launch-floating-terminal-with-presentation") + and shutil.which("omarchy-transcode") + ) + + def get_file_items(self, *args): + files = args[0] if len(args) == 1 else args[1] + if not self._tools_available(): + return [] + + paths = self._selected_paths(files) + if not paths: + return [] + + return [self._make_item(paths)] diff --git a/install/config/nautilus-python.sh b/install/config/nautilus-python.sh index fd2ef0f7..76266353 100644 --- a/install/config/nautilus-python.sh +++ b/install/config/nautilus-python.sh @@ -2,3 +2,4 @@ EXTENSIONS_DIR="$HOME/.local/share/nautilus-python/extensions" mkdir -p "$EXTENSIONS_DIR" cp "$OMARCHY_PATH/default/nautilus-python/extensions/localsend.py" "$EXTENSIONS_DIR/" +cp "$OMARCHY_PATH/default/nautilus-python/extensions/transcode.py" "$EXTENSIONS_DIR/" diff --git a/migrations/1778099894.sh b/migrations/1778099894.sh new file mode 100644 index 00000000..bf83d6d1 --- /dev/null +++ b/migrations/1778099894.sh @@ -0,0 +1,3 @@ +echo "Add Transcode entry to Nautilus context menu" + +source $OMARCHY_PATH/install/config/nautilus-python.sh