Merge quattro into clock-calendar

Both sides tightened the same center-layout assertion. Taking quattro's:
it asserts the weather/update adjacency the test name is about instead of
pinning the whole row, which is what kept breaking it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-26 22:07:08 -07:00
co-authored by Claude Opus 5
54 changed files with 1510 additions and 148 deletions
+2
View File
@@ -45,6 +45,7 @@ GROUP_DESCRIPTIONS[dev]="Omarchy development tools"
GROUP_DESCRIPTIONS[display]="Display and text scaling"
GROUP_DESCRIPTIONS[dns]="DNS resolver configuration"
GROUP_DESCRIPTIONS[drive]="Drive selection and encryption"
GROUP_DESCRIPTIONS[file]="File selection helpers"
GROUP_DESCRIPTIONS[font]="Font management"
GROUP_DESCRIPTIONS[games]="Game launchers and helpers"
GROUP_DESCRIPTIONS[hibernation]="Hibernation setup and removal"
@@ -78,6 +79,7 @@ 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"
GROUP_DESCRIPTIONS[theme]="Theme management"
GROUP_DESCRIPTIONS[tmux]="Tmux session helpers"
GROUP_DESCRIPTIONS[toggle]="Toggle Omarchy features"
+10 -1
View File
@@ -288,6 +288,15 @@ cmd_move() {
fi
local default_section="${PLACEMENT_SECTION:-}"
# A bare section names the section, not the slot, so let it fall through to
# the same anchor placement 'add' uses. Passing it as an explicit target
# instead drops the widget on the far end of the row.
local target_section="$PLACEMENT_SECTION"
if [[ -z $PLACEMENT_INDEX && -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]]; then
target_section=""
fi
local prog
prog=$(cat <<JQ
$JQ_DEFS $NORMALIZE
@@ -302,7 +311,7 @@ JQ
commit "$prog" \
--arg id "$id" \
--arg defaultSection "$default_section" \
--arg targetSection "$PLACEMENT_SECTION" \
--arg targetSection "$target_section" \
--arg targetIndex "$PLACEMENT_INDEX" \
--arg before "$PLACEMENT_BEFORE" \
--arg after "$PLACEMENT_AFTER" \
+18 -2
View File
@@ -35,6 +35,7 @@ RESOLUTION=""
FULLSCREEN="false"
STOP_RECORDING="false"
RECORDING_FILE="/tmp/omarchy-screenrecord-filename"
REGION_FILE="${XDG_RUNTIME_DIR:-/tmp}/omarchy-screenrecord-region"
LOG_FILE=$([[ ${OMARCHY_SCREENRECORD_DEBUG:-false} == "true" ]] && echo "/tmp/omarchy-screenrecord.log" || echo "/dev/null")
for arg in "$@"; do
@@ -89,11 +90,26 @@ start_webcam_overlay() {
--title="WebcamOverlay" --wayland-app-id="WebcamOverlay-$WEBCAM_SIZE" \
--no-border --no-audio --no-osc --osd-level=0 \
--really-quiet &>/dev/null &
sleep 1
# The move has to settle before gpu-screen-recorder starts, or the camera is
# recorded sliding into its corner. Waiting for the map is what the blind
# second was partly guessing at, so the remainder is trimmed to hold the
# pre-capture delay where it was: starting later costs the first words spoken.
local waited=0
while ((waited < 40)) && ! hyprctl clients -j | jq -e 'any(.[]; .title == "WebcamOverlay")' >/dev/null 2>&1; do
sleep 0.05
((waited++))
done
[[ ${1:-} == region:* ]] && echo "${1#region:}" >"$REGION_FILE"
omarchy-capture-webcam-resize "$WEBCAM_SIZE"
sleep 0.6
}
cleanup_webcam() {
pkill -f "WebcamOverlay" 2>/dev/null
rm -f "$REGION_FILE"
}
default_resolution() {
@@ -155,7 +171,7 @@ start_screenrecording() {
esac
fi
[[ $WEBCAM == "true" ]] && start_webcam_overlay
[[ $WEBCAM == "true" ]] && start_webcam_overlay "$target"
local filename="$OUTPUT_DIR/screenrecording-$(date +'%Y-%m-%d_%H-%M-%S').mp4"
local audio_devices=""
+35 -8
View File
@@ -8,6 +8,7 @@
set -euo pipefail
readonly MARGIN=40
readonly REGION_FILE="${XDG_RUNTIME_DIR:-/tmp}/omarchy-screenrecord-region"
usage() {
echo "Usage: omarchy-capture-webcam-resize <smaller|larger|reset|small|medium|large>" >&2
@@ -57,13 +58,39 @@ read -r monitor_x monitor_y monitor_width monitor_height < <(
[[ $monitor_x =~ ^-?[0-9]+$ && $monitor_y =~ ^-?[0-9]+$ && $monitor_width =~ ^[0-9]+$ && $monitor_height =~ ^[0-9]+$ ]] || exit 0
# Scale the 8:9 portrait presets from monitor height so they occupy the same
# Anchor to the recorded region when there is one, so a window picked on a wide
# display keeps the camera in its own corner. Full-monitor captures, the portal
# backend, and resizes outside a recording publish none and fall back here.
anchor_x=$monitor_x
anchor_y=$monitor_y
anchor_width=$monitor_width
anchor_height=$monitor_height
if [[ -f $REGION_FILE ]] && region=$(<"$REGION_FILE"); then
if [[ $region =~ ^([0-9]+)x([0-9]+)\+(-?[0-9]+)\+(-?[0-9]+)$ ]]; then
anchor_width=${BASH_REMATCH[1]}
anchor_height=${BASH_REMATCH[2]}
anchor_x=${BASH_REMATCH[3]}
anchor_y=${BASH_REMATCH[4]}
fi
fi
# A tall, narrow region can't fit presets scaled from its own height, so cap the
# height they scale from to what the width allows — the large preset is the
# widest at 3/10 of it. Scaling the ladder as a whole leaves small, medium and
# large distinct sizes for smaller and larger to step between.
scale_height=$anchor_height
available_width=$((anchor_width - 2 * MARGIN))
((available_width > 0 && scale_height * 3 / 10 > available_width)) &&
scale_height=$((available_width * 10 / 3))
# Scale the 8:9 portrait presets from that height so they occupy the same
# proportion of a 1080p, HiDPI, ultrawide, or 6K recording.
small_height=$(((monitor_height * 9 + 25) / 50))
small_height=$(((scale_height * 9 + 25) / 50))
small_width=$(((small_height * 8 + 4) / 9))
medium_height=$(((monitor_height + 2) / 4))
medium_height=$(((scale_height + 2) / 4))
medium_width=$(((medium_height * 8 + 4) / 9))
large_height=$(((monitor_height * 27 + 40) / 80))
large_height=$(((scale_height * 27 + 40) / 80))
large_width=$(((large_height * 8 + 4) / 9))
target_width=$current_width
@@ -107,11 +134,11 @@ larger)
;;
esac
target_x=$((monitor_x + monitor_width - target_width - MARGIN))
target_y=$((monitor_y + monitor_height - target_height - MARGIN))
target_x=$((anchor_x + anchor_width - target_width - MARGIN))
target_y=$((anchor_y + anchor_height - target_height - MARGIN))
((target_x < monitor_x + MARGIN)) && target_x=$((monitor_x + MARGIN))
((target_y < monitor_y + MARGIN)) && target_y=$((monitor_y + MARGIN))
((target_x < anchor_x + MARGIN)) && target_x=$((anchor_x + MARGIN))
((target_y < anchor_y + MARGIN)) && target_y=$((anchor_y + MARGIN))
window="address:$address"
hypr_dispatch \
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/python3
# omarchy:summary=Pick files with the desktop file chooser
# omarchy:args=[--title <title>] [--multiple]
# omarchy:examples=omarchy file select --title "Send with Tailscale" --multiple
# Python rather than bash, alone among the commands here, because the portal
# answers a request with a Response signal addressed to the connection that
# asked, and D-Bus delivers a directed signal only to that connection. Every
# shell-callable client — gdbus call, busctl call, dbus-send — opens its own
# connection and exits before the answer arrives, and gdbus monitor registers
# with AddMatch rather than BecomeMonitor, so it never sees one either. Holding
# a single connection across both the call and the wait is the whole job, and
# bash has no way to hold one.
import argparse
import os
import sys
import gi
gi.require_version("Gio", "2.0")
from gi.repository import Gio, GLib
# A dialog nobody ever answers would otherwise keep this process, and whatever
# waits on its output, alive forever.
ANSWER_TIMEOUT_SEC = 600
# Callers act on these: nothing picked is a decision, a chooser that never ran
# is a fault, and the two want different handling.
EXIT_NOTHING_PICKED = 1
EXIT_CHOOSER_FAILED = 2
def main():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--title", default="Select file")
parser.add_argument("--multiple", action="store_true")
args, unknown = parser.parse_known_args()
if unknown:
print("omarchy-file-select: unknown option %s" % unknown[0], file=sys.stderr)
return EXIT_CHOOSER_FAILED
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
loop = GLib.MainLoop()
uris = []
def on_response(connection, sender, path, interface, signal, params):
code, results = params.unpack()
if code == 0:
uris.extend(results.get("uris", []))
loop.quit()
def subscribe(path):
bus.signal_subscribe(
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request",
"Response",
path,
None,
Gio.DBusSignalFlags.NONE,
on_response,
)
# The request path is derived from our bus name and the token we pass, so it
# can be subscribed to up front. Asking first would race a dialog that gets
# answered immediately.
token = "omarchy%d" % os.getpid()
sender = bus.get_unique_name()[1:].replace(".", "_")
predicted = "/org/freedesktop/portal/desktop/request/%s/%s" % (sender, token)
subscribe(predicted)
handle = bus.call_sync(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.FileChooser",
"OpenFile",
GLib.Variant("(ssa{sv})", ("", args.title, {
"handle_token": GLib.Variant("s", token),
"multiple": GLib.Variant("b", args.multiple),
})),
None,
Gio.DBusCallFlags.NONE,
-1,
None,
).unpack()[0]
# Portals predating the token convention answer on a path of their choosing.
if handle != predicted:
subscribe(handle)
GLib.timeout_add_seconds(ANSWER_TIMEOUT_SEC, loop.quit)
loop.run()
for uri in uris:
print(GLib.filename_from_uri(uri)[0])
return 0 if uris else EXIT_NOTHING_PICKED
if __name__ == "__main__":
try:
sys.exit(main())
except GLib.Error as error:
print("omarchy-file-select: %s" % error.message, file=sys.stderr)
sys.exit(EXIT_CHOOSER_FAILED)
+1 -38
View File
@@ -53,37 +53,6 @@ log_first_run() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$FIRST_RUN_LOG"
}
notification_server_ready() {
if omarchy-cmd-present gdbus; then
gdbus call --session \
--dest org.freedesktop.Notifications \
--object-path /org/freedesktop/Notifications \
--method org.freedesktop.Notifications.GetServerInformation >/dev/null 2>&1
elif omarchy-cmd-present busctl; then
busctl --user call \
org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications \
GetServerInformation >/dev/null 2>&1
else
return 0
fi
}
wait_for_notifications() {
omarchy-cmd-present omarchy-shell || return 0
for _ in {1..100}; do
if omarchy-shell notifications ping >/dev/null 2>&1 && notification_server_ready; then
return 0
fi
sleep 0.1
done
log_first_run "Timed out waiting for notification service; continuing"
return 0
}
run_first_run_step() {
local name="$1"
shift
@@ -98,12 +67,6 @@ run_first_run_step() {
fi
}
wait_for_notifications
run_first_run_step "enable migration notification watcher" \
systemctl --user enable --now omarchy-update-user-notify.path
run_first_run_step "notify about pending migrations" omarchy-migrate-notify
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" \
@@ -118,7 +81,7 @@ run_first_run_step "set GTK primary paste" \
run_first_run_step "apply speaker tuning" \
bash "$OMARCHY_PATH/install/user/first-run/audio-tuning.sh"
wait_for_notifications
omarchy-notification-wait || log_first_run "Timed out waiting for notification service; continuing"
run_first_run_step "show welcome notification" \
bash "$OMARCHY_PATH/install/user/first-run/welcome.sh"
# The first-run notification scripts register action callbacks in background
+3
View File
@@ -13,6 +13,9 @@ sudo tailscale up --accept-routes
echo -e "\nAllowing $USER to manage Tailscale..."
sudo tailscale set --operator="$USER"
echo -e "\nReceiving Taildrop files in $HOME/Downloads..."
systemctl --user enable --now omarchy-tailscale-receive.service
echo -e "\nAdding Tailscale to the bar..."
omarchy-bar-plugin add omarchy.tailscale
+3 -2
View File
@@ -96,6 +96,7 @@ while IFS=$'\t' read -r name file marker; do
fi
done < <(migration_entries)
# Clear notifications queued while an update was installing migrations. The
# substring matches both the current and legacy notification titles.
# Clear a login-time notification the user left sitting there and then resolved
# by running migrations some other way. The substring matches both the current
# and legacy notification titles.
omarchy-notification-dismiss "Omarchy Migrations" >/dev/null 2>&1 || true
+10 -4
View File
@@ -15,11 +15,17 @@ fi
notify_command=$(printf 'if [[ -n $(omarchy-notification-send -u critical -g  "Pending Omarchy Migrations" %q -a) ]]; then omarchy-launch-floating-terminal-with-presentation omarchy-migrate; fi' "$message")
if omarchy-cmd-present systemd-run; then
unit="omarchy-migrations-notification-$(date +%Y%m%d%H%M%S)"
systemd-run --user --scope --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
fi
# This runs from omarchy-migrate-notify.service at graphical-session.target,
# which the session can reach before the shell has claimed
# org.freedesktop.Notifications. Without the wait the toast is sent into the
# void and the user never learns about their pending migrations.
omarchy-notification-wait || true
unit="omarchy-migrations-notification-$(date +%Y%m%d%H%M%S)"
systemd-run --user --scope --unit="$unit" bash -lc "$notify_command" >/dev/null 2>&1 && exit 0
# Reached when there is no user manager to run the scope under, such as a
# non-graphical shell, so fall back to telling the user in the terminal.
print_pending_migrations() {
echo "Omarchy has pending migrations. Run omarchy-migrate in a terminal to apply them:"
while IFS= read -r migration; do
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# omarchy:summary=Wait for the desktop notification server to accept notifications
# omarchy:args=[timeout-seconds]
# omarchy:hidden=true
set -uo pipefail
timeout=${1:-10}
notification_server_ready() {
busctl --user call \
org.freedesktop.Notifications \
/org/freedesktop/Notifications \
org.freedesktop.Notifications \
GetServerInformation >/dev/null 2>&1
}
# The shell has to be up to serve the IPC, and it has to have claimed the
# notification bus name before notify-send has anywhere to deliver.
attempts=$((timeout * 10))
while (( attempts > 0 )); do
if omarchy-shell notifications ping >/dev/null 2>&1 && notification_server_ready; then
exit 0
fi
attempts=$((attempts - 1))
sleep 0.1
done
exit 1
+1
View File
@@ -4,6 +4,7 @@
# omarchy:requires-sudo=true
tailscale down 2>/dev/null || true
systemctl --user disable --now omarchy-tailscale-receive.service 2>/dev/null || true
sudo systemctl disable --now tailscaled.service 2>/dev/null || true
omarchy-bar-plugin remove omarchy.tailscale
omarchy-webapp-remove "Tailscale" 2>/dev/null || true
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# omarchy:summary=Save incoming Taildrop files and announce them
# omarchy:args=[--once] [directory]
# omarchy:examples=omarchy tailscale receive | omarchy tailscale receive --once ~/Desktop
set -euo pipefail
once=false
if [[ ${1:-} == "--once" ]]; then
once=true
shift
fi
dir="${1:-${XDG_DOWNLOAD_DIR:-$HOME/Downloads}}"
# Taildrop lands in a staging directory next door rather than straight in the
# downloads directory: waiting for a delivery can take hours, and everything
# else that shows up meanwhile is somebody else's file. Same filesystem, so
# handing the finished file over is a rename.
staging="$dir/.omarchy-taildrop"
mkdir -p "$staging"
# Take the name by linking to it rather than by looking and then renaming.
# link(2) refuses an existing name, so nothing can land on the chosen one in
# the gap between the two. Staging shares the filesystem with the downloads
# directory, so the link always resolves and unlinking the staged name
# finishes the move. Prints the name it took.
claim_path() {
local staged="$1" name="${staged##*/}" base ext candidate index=0
base="${name%.*}"
ext="${name#"$base"}"
[[ -z $base ]] && { base="$name"; ext=""; }
while (( index < 1000 )); do
if (( index == 0 )); then
candidate="$dir/$name"
else
candidate="$dir/$base-$index$ext"
fi
if ln -- "$staged" "$candidate" 2>/dev/null; then
rm -f -- "$staged"
printf '%s\n' "$candidate"
return 0
fi
# Only a taken name is worth another spin. Anything else failed the link
# itself, and the file keeps its place in staging for the next run.
[[ -e $candidate ]] || return 1
((index++))
done
return 1
}
announce() {
local path="$1"
local name="${path##*/}"
local args=("Received $name" "Saved to ${dir/#$HOME/~}")
case "${name,,}" in
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
args+=(--image "$path")
;;
*)
args+=(-g 󰒊)
;;
esac
# Clicking the notification opens the file, so this waits for the toast to
# go away. Callers background it to keep receiving in the meantime.
if [[ -n $(omarchy-notification-send "${args[@]}" -a) ]]; then
xdg-open "$path"
fi
}
deliver() {
local staged target
while IFS= read -r staged; do
target=$(claim_path "$staged") || continue
announce "$target" &
done < <(find "$staging" -mindepth 1 -maxdepth 1)
}
# Anything left staged by an interrupted run still deserves delivering.
deliver
while true; do
if ! tailscale file get --wait --conflict=rename "$staging"; then
$once && exit 1
sleep 10
continue
fi
deliver
$once && exit 0
done
+50
View File
@@ -0,0 +1,50 @@
#!/bin/bash
# omarchy:summary=Send files to a machine on your tailnet with Taildrop
# omarchy:args=<machine> [file...]
# omarchy:examples=omarchy tailscale send dhh-fd | omarchy tailscale send dhh-fd ~/Downloads/notes.pdf
set -euo pipefail
if (($# < 1)); then
echo "Usage: omarchy-tailscale-send <machine> [file...]" >&2
exit 1
fi
machine="$1"
shift
# Address the machine by whatever name we were handed, but talk about it by
# its short name, so a MagicDNS name does not spill into every message.
name="${machine%%.*}"
files=("$@")
if ((${#files[@]} == 0)); then
# Command substitution so the chooser's exit status survives: reading it
# through a process substitution reports success for a chooser that never
# opened, which is indistinguishable here from someone deciding not to send.
picked=$(omarchy-file-select --title "Send to $name" --multiple) || status=$?
if ((${status:-0} > 1)); then
omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" \
"The file chooser did not open"
exit 1
fi
readarray -t files <<<"$picked"
[[ -n $picked ]] || exit 0
fi
if ((${#files[@]} == 1)); then
what=$(basename "${files[0]}")
else
what="${#files[@]} files"
fi
if error=$(tailscale file cp --update-interval=0 -- "${files[@]}" "$machine:" 2>&1); then
omarchy-notification-send -g "󰒊" "Sent to $name" "$what"
else
omarchy-notification-send -g "󰒊" -u critical "Could not send to $name" "${error:-Taildrop transfer failed}"
exit 1
fi