aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-31 09:32:10 +0100
committerDanilo M. <danix@danix.xyz>2026-10-31 09:32:10 +0100
commitea405da5bcb491dcb8f0b3b35f19841cfe9dce8a (patch)
treed156ff80a905095452aa0a92014f8100e66ac66b
parent845e6d3f393811bdc78833c9457d9768e685e572 (diff)
downloadwallp-ea405da5bcb491dcb8f0b3b35f19841cfe9dce8a.tar.gz
wallp-ea405da5bcb491dcb8f0b3b35f19841cfe9dce8a.zip
feat: copy the vertical wallpaper to a phone
With PHONE_V set, every change of the vertical wallpaper is copied to that path, a fixed name inside a folder Syncthing shares with a phone, where a Termux loop applies it (script in the README). A copy, not a symlink, because Syncthing does not follow links. It is built in ~/.cache/wallp and moved into place, so Syncthing never sees a partial file. PHONE_SIZE (WxH) crops it to the phone screen, centred, with magick, as swaybg's fill does: Android otherwise scales a landscape image to the screen height and shows its left edge. A stamp of source and size skips an image already copied, so a --restore at login does not make the phone reapply it. Unset PHONE_V does nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--README.md29
-rw-r--r--tests/wallp.bats49
-rwxr-xr-xwallp31
-rw-r--r--wallp.conf.example5
4 files changed, 113 insertions, 1 deletions
diff --git a/README.md b/README.md
index 394ba29..a715cca 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ with one flag-driven program.
| `swaybg` | yes | paints the wallpaper on each output |
| `udt-accent` | optional | refreshes the desktop accent colour |
| `qarma` | only for GUI `--set`| screen-choice menu + file picker |
+| `magick` | only for `PHONE_SIZE`| crops the phone copy (ImageMagick) |
| `bats` | dev only | running the test suite |
`wallp` checks for the required binaries at startup and exits with a clear
@@ -132,6 +133,7 @@ State is owned entirely by `wallp`:
| `~/.cache/wallp/H.pid` | PID of the horizontal `swaybg` |
| `~/.cache/wallp/V.pid` | PID of the vertical `swaybg` |
| `~/.cache/udt/wpaper` | symlink to the current horizontal image |
+| `PHONE_V` (optional) | copy of the current vertical image, for a phone |
Setting a screen: validate the file, kill **only** that screen's old `swaybg`,
launch a new one (`swaybg -o <output> -i <file> -m fill`), record the new PID,
@@ -144,6 +146,33 @@ configured default; apply it; then refresh the accent the same way.
The `wpaper` symlink is the hand-off point to the rest of the theme: other
tools (rofi, `udt-accent`) read it rather than `wallp`'s own state.
+## Phone wallpaper
+
+Set `PHONE_V` in the config and every change of the vertical wallpaper is
+copied to that path, a fixed name inside a folder Syncthing shares with a
+phone. Unset, nothing happens. With `PHONE_SIZE=1440x3120` (the phone's
+screen in pixels) it is cropped to that size, centred, using ImageMagick's
+`magick`. Without it Android scales a landscape image to the screen height and
+shows its left edge. A wallpaper already copied is not rewritten.
+
+On an Android phone with Termux and Termux:API (both from F-Droid), a loop
+applies the file whenever it changes. Start it from Termux:Boot and exempt
+Termux from battery optimisation:
+
+```sh
+#!/data/data/com.termux/files/usr/bin/sh
+# Polls rather than using inotify, which is unreliable on shared storage.
+f=~/storage/shared/Documents/phone-wallpaper.png
+last=
+while sleep 60; do
+ m=$(stat -c %Y "$f" 2>/dev/null) || continue
+ [ "$m" = "$last" ] || { termux-wallpaper -f "$f"; termux-wallpaper -l -f "$f"; last=$m; }
+done
+```
+
+Drop the `-l` call to leave the lock screen alone. The launcher draws the
+system wallpaper, so it follows without setup of its own.
+
## Integration with sway / Hyprland
Bind the interactive setter to a key and run a restore at startup. For Hyprland:
diff --git a/tests/wallp.bats b/tests/wallp.bats
index 56f3d54..8a092d4 100644
--- a/tests/wallp.bats
+++ b/tests/wallp.bats
@@ -286,3 +286,52 @@ EOF
run require_bins
[ "$status" -eq 0 ]
}
+
+@test "parse_conf reads optional PHONE_V and expands tilde" {
+ conf="$HOME/c.conf"
+ printf '%s\n' 'PHONE_V=~/sync/phone.png' 'PHONE_SIZE=1440x3120' > "$conf"
+ parse_conf "$conf"
+ [ "$CONF_PHONE_V" = "$HOME/sync/phone.png" ]
+ [ "$CONF_PHONE_SIZE" = "1440x3120" ]
+}
+
+@test "sync_phone copies the vertical wallpaper to PHONE_V" {
+ printf 'img' > "$HOME/v.png"
+ mkdir -p "$HOME/.config/wallp"
+ printf '%s\n' "$HOME/v.png" > "$HOME/.config/wallp/wall_v"
+ CONF_PHONE_V="$HOME/sync/phone.png"
+ sync_phone
+ cmp -s "$HOME/v.png" "$HOME/sync/phone.png"
+}
+
+@test "sync_phone skips a source it already copied" {
+ printf 'img' > "$HOME/v.png"
+ mkdir -p "$HOME/.config/wallp"
+ printf '%s\n' "$HOME/v.png" > "$HOME/.config/wallp/wall_v"
+ CONF_PHONE_V="$HOME/sync/phone.png"
+ sync_phone
+ touch -d '2000-01-01' "$HOME/sync/phone.png"
+ sync_phone
+ [ "$(stat -c %Y "$HOME/sync/phone.png")" = "$(date -d 2000-01-01 +%s)" ]
+}
+
+@test "sync_phone crops to PHONE_SIZE, centred" {
+ magick -size 400x100 xc:red -fill blue -draw 'rectangle 180,0 219,99' "$HOME/v.png"
+ mkdir -p "$HOME/.config/wallp"
+ printf '%s\n' "$HOME/v.png" > "$HOME/.config/wallp/wall_v"
+ CONF_PHONE_V="$HOME/sync/phone.png"
+ CONF_PHONE_SIZE="20x50"
+ sync_phone
+ [ "$(magick identify -format '%wx%h' "$HOME/sync/phone.png")" = "20x50" ]
+ [ "$(magick "$HOME/sync/phone.png" -format '%[hex:p{10,25}]' info:)" = "0000FF" ]
+}
+
+@test "sync_phone does nothing when PHONE_V is unset" {
+ printf 'img' > "$HOME/v.png"
+ mkdir -p "$HOME/.config/wallp"
+ printf '%s\n' "$HOME/v.png" > "$HOME/.config/wallp/wall_v"
+ CONF_PHONE_V=""
+ run sync_phone
+ [ "$status" -eq 0 ]
+ [ ! -e "$HOME/sync" ]
+}
diff --git a/wallp b/wallp
index be5ff50..694d2b2 100755
--- a/wallp
+++ b/wallp
@@ -14,7 +14,7 @@ expand_tilde() {
# Parse key=value conf into CONF_* globals. Ignores blanks and #-comments.
# Expands ~ in DEFAULT_* path values. Does not validate (see require_conf_keys).
-CONF_OUTPUT_H="" CONF_OUTPUT_V="" CONF_DEFAULT_H="" CONF_DEFAULT_V=""
+CONF_OUTPUT_H="" CONF_OUTPUT_V="" CONF_DEFAULT_H="" CONF_DEFAULT_V="" CONF_PHONE_V="" CONF_PHONE_SIZE=""
parse_conf() {
local file="$1" line key val
while IFS= read -r line || [ -n "$line" ]; do
@@ -26,6 +26,8 @@ parse_conf() {
OUTPUT_V) CONF_OUTPUT_V="$val" ;;
DEFAULT_H) CONF_DEFAULT_H="$(expand_tilde "$val")" ;;
DEFAULT_V) CONF_DEFAULT_V="$(expand_tilde "$val")" ;;
+ PHONE_V) CONF_PHONE_V="$(expand_tilde "$val")" ;;
+ PHONE_SIZE) CONF_PHONE_SIZE="$val" ;;
esac
done < "$file"
}
@@ -151,6 +153,32 @@ update_wpaper() {
ln -sf "$img" "$link"
}
+# Copy the vertical wallpaper to PHONE_V, a fixed name inside a folder synced
+# to a phone, where a watcher sets it as the phone wallpaper. Optional: an
+# unset PHONE_V does nothing. A copy, not a symlink, because Syncthing does not
+# follow links. With PHONE_SIZE (WxH) it is cropped to that size, centred, as
+# swaybg's fill does: Android shows the left edge of a wider image, the part a
+# scrolling home screen starts on. A source already copied at that size is
+# skipped, so a --restore at login does not make the phone reapply it.
+sync_phone() {
+ local wf img stamp tmp; wf="$(wall_file_for V)"
+ [ -n "$CONF_PHONE_V" ] && [ -s "$wf" ] || return 0
+ img="$(cat "$wf")"
+ [ -f "$img" ] || return 0
+ stamp="$HOME/.cache/wallp/phone.src"
+ [ -f "$CONF_PHONE_V" ] && [ "$(cat "$stamp" 2>/dev/null)" = "$img $CONF_PHONE_SIZE" ] && return 0
+ mkdir -p "$(dirname "$CONF_PHONE_V")" "$(dirname "$stamp")"
+ # Built outside the synced folder, so Syncthing never sees a partial file.
+ tmp="$HOME/.cache/wallp/phone.png"
+ if [ -n "$CONF_PHONE_SIZE" ]; then
+ magick "$img" -resize "${CONF_PHONE_SIZE}^" -gravity center \
+ -extent "$CONF_PHONE_SIZE" "png:$tmp" || return 0
+ else
+ cp "$img" "$tmp" || return 0
+ fi
+ mv "$tmp" "$CONF_PHONE_V" && printf '%s\n' "$img $CONF_PHONE_SIZE" > "$stamp"
+}
+
# Pick saved path for a logical output, else its conf default.
restore_path_for() {
local logical="$1" wf; wf="$(wall_file_for "$logical")"
@@ -172,6 +200,7 @@ do_restore() {
finalize() {
update_wpaper
+ sync_phone
# Refresh the theme accent from the wallpaper. udt-accent owns the whole
# palette: it writes rofi, Hyprland, Firefox, quickshell and Sublime, so
# there is no pywal run here to render anything first. Never fatal: a
diff --git a/wallp.conf.example b/wallp.conf.example
index 26cd444..8a28f7e 100644
--- a/wallp.conf.example
+++ b/wallp.conf.example
@@ -5,3 +5,8 @@ OUTPUT_V=DP-3
# Defaults used by --restore when no saved state exists.
DEFAULT_H=~/Pictures/wallpapers/SFW/horizontal.png
DEFAULT_V=~/Pictures/wallpapers/SFW/vertical.png
+# Optional: copy the vertical wallpaper here on every change, for a phone
+# that syncs this folder (see README).
+#PHONE_V=~/Documents/phone-wallpaper.png
+# Optional: crop to the phone screen, centred (WxH). Unset copies as is.
+#PHONE_SIZE=1440x3120