# wallp — Unified Wallpaper Manager — Design

Date: 2026-06-11

## Overview

Single bash script `wallp` replacing two scripts:

- `multiwal.sh` — interactive wallpaper setter (qarma picker, two screens).
- `qar-lastwall.sh` — restores last-session wallpapers at session start.

Command-line flags pick the action. qarma is used **only** for GUI (file picker,
selection menu, dialogs, help). `swaybg` actually sets wallpapers and needs no
qarma. Per-output PID tracking allows updating a single screen without blanking
the other (the old `pkill swaybg` killed both). A config file holds defaults;
flags override where applicable.

Two-screen setup:

- Horizontal screen — logical `H`, physical output from conf `OUTPUT_H` (e.g. `DP-1`).
- Vertical screen — logical `V`, physical output from conf `OUTPUT_V` (e.g. `DP-3`).

## CLI

```
wallp                      # help screen
wallp --help / -h          # help screen
wallp --set                # qarma picker: menu H/V/Both -> file picker(s)
wallp --set H=<file>       # set horizontal only, no GUI
wallp --set V=<file>       # set vertical only, no GUI
wallp --set H=<f> V=<f>    # set both, no GUI
wallp --restore            # restore last session from persistent state
wallp --theme <name>       # override conf theme (combine with --set/--restore)
```

Rules:

- Bare invocation or `--help`/`-h` → help screen. Shown in a qarma window when
  `$WAYLAND_DISPLAY` is set, otherwise printed to stdout.
- `--set` with no `H=`/`V=` args:
  - `$WAYLAND_DISPLAY` set → qarma selection flow.
  - no display → error: "no display: pass H=<file> and/or V=<file>". Exit non-zero.
- `--set` with `H=`/`V=` args → set those outputs directly, no GUI (works
  headless; swaybg does not need qarma).
- `--restore` → no display required (intended for session-start autostart).
- `--theme <name>` → overrides conf `THEME` for this invocation.
- Unknown flag → error message + help, exit non-zero.

Logical `H=`/`V=` keys map to physical `OUTPUT_H`/`OUTPUT_V` from conf, so output
renames only touch the conf, not command lines or muscle memory.

## Config — `~/.config/wallp/wallp.conf`

Format: simple `key=value`, one per line. Parsed manually (no shell sourcing —
avoids code execution from the conf). Blank lines and lines starting with `#`
ignored. Leading `~` in path values expanded to `$HOME`.

Keys:

```
THEME=sexy-splurge
OUTPUT_H=DP-1
OUTPUT_V=DP-3
DEFAULT_H=/home/danix/Pictures/wallpapers/SFW/wallhaven-4yd27g_2560x1080.png
DEFAULT_V=/home/danix/Pictures/wallpapers/SFW/wallhaven-gwzqjl_2560x1080.png
```

Behavior:

- **Conf file missing** → write a commented template to the conf path, tell the
  user (stdout + notify-send if available) to fill it in and re-run, then
  **exit 0 without touching any wallpaper**. Applies to every action.
- **Conf present, required path/output key missing or empty** → hard error
  naming the missing key, exit non-zero, no wallpaper change. Required keys:
  `OUTPUT_H`, `OUTPUT_V`, `DEFAULT_H`, `DEFAULT_V`.
- **THEME** is the one exception: if absent, built-in fallback `sexy-splurge`
  is used. `--theme` flag also overrides it.

An example conf is shipped in the repo (`wallp.conf.example`).

## State Files

Persistent state (wallp-owned, `~/.config/wallp/`, survives reboot):

- `~/.config/wallp/wall_h` — H wallpaper path.
- `~/.config/wallp/wall_v` — V wallpaper path.
- `~/.config/wallp/theme`  — last-used theme name.
- `~/.config/wallp/wallp.conf` — config (see Config section).

Runtime state (volatile, reset on reboot):

- `~/.cache/wal/wpaper` — symlink to current H image. **Kept** because it has
  external consumers (rofi themes under `~/.config/rofi/darknix/*.rasi`). wallp
  maintains it.
- `~/.cache/wallp/H.pid` — swaybg PID for horizontal output.
- `~/.cache/wallp/V.pid` — swaybg PID for vertical output.

Dropped (no external consumer; confirmed via grep over `~/bin` and `~/.config`):

- `~/.cache/wal/wal`, `~/.cache/wal/wal2` — only the replaced scripts wrote
  these; nothing else reads them.
- `~/.config/wal/wal`, `~/.config/wal/wal2` — superseded by `~/.config/wallp/`.

wallp owns all its persistence directly in `~/.config/wallp/`. This moves
persistence out of `wal.sh` and into wallp. `wal.sh` is reduced to its theme-app
glue only (dunst symlink+restart, kitty symlink); it loses the wal/wal2/wpaper
copy logic. `wal.sh` remains the wal `-o` hook for future use.

Note: `~/.cache/wal/colors.json` is pywal-native (written by `wal` itself, read
by `pywal_sublime.py`); not touched by wallp.

## Theme Persistence

The last-used theme is persisted to `~/.config/wallp/theme` and survives reboot.
Conf `THEME` is only the first-run default. Precedence on every action:

1. `--theme <name>` flag → use it.
2. else persisted `~/.config/wallp/theme` (if present).
3. else conf `THEME`.
4. else built-in `sexy-splurge`.

Whatever theme is resolved gets persisted to `~/.config/wallp/theme`. This means
once a theme is set it sticks (for both `--set` and `--restore`) until changed
via `--theme`. `--restore` thus brings back the last-used theme, not the conf
default.

## Set Flow

For each chosen output (H and/or V):

1. Resolve the file path (from qarma picker or `H=`/`V=` arg). Validate it
   exists; if not, error and skip that output (do not blank it).
2. Selective kill: read `<X>.pid`; if the PID is alive, `kill` it. The other
   output's swaybg is left untouched.
3. `swaybg -o <OUTPUT> -i <file> -m fill &`; save the new PID to `<X>.pid`.
4. Persist the path to `~/.config/wallp/wall_h` (H) or `wall_v` (V).

After all chosen outputs are done:

5. Update `~/.cache/wal/wpaper` symlink → current H image (for rofi).
6. Resolve theme (see Theme Persistence), persist to `~/.config/wallp/theme`.
7. Run `wal --backend colorz -nq --theme <THEME> -o ~/bin/wal.sh`.
8. `notify-send` color-theme update.

qarma selection flow (`--set`, display present, no args):

- qarma list/menu: choose `H` / `V` / `Both` (no initial yes/no confirmation).
- File picker per chosen output: `qarma --file-selection --preview-images 500
  --width 1300 --height 600` with a per-output title.
- Picker cancelled → skip that output (no error).

## Restore Flow (`--restore`)

For each output H/V:

1. If the persistent pointer (`~/.config/wallp/wall_h` for H, `wall_v` for V)
   exists → read the path, selective-kill that output's old PID, `swaybg`,
   save PID.
2. If no persistent pointer for that output → use `DEFAULT_H`/`DEFAULT_V` from
   conf, persist it (`wall_h`/`wall_v`), swaybg, save PID.

After both outputs:

3. Update `wpaper` symlink → H image.
4. Resolve theme (persisted theme wins, see Theme Persistence), persist it,
   run wal theme + notify (same as set steps 7-8).

No display required. Replaces `qar-lastwall.sh`. Intended for session-start
autostart.

## Error Handling

- Missing/invalid file for an output → error, skip that output, do not blank it.
- qarma cancel → skip output, no error.
- Dead or missing PID file → start fresh swaybg, no kill attempt.
- GUI needed but no display → clear error, exit non-zero.
- Required binary missing (`swaybg`, `wal`, `qarma` when GUI needed,
  `notify-send` optional) → checked at start; error and exit if a required one
  is absent. `notify-send` treated as optional (skip notification if absent).
- Conf missing → generate template, instruct user, exit 0, no change.
- Required conf key missing → hard error, exit non-zero, no change.

## Testing

The script is side-effecting (swaybg, wal, filesystem). Strategy:

- Factor pure logic into functions: conf parsing, arg parsing, `~` expansion,
  logical→physical output mapping, file-existence validation.
- Test with bats (or a stub harness): place fake `swaybg`, `wal`, `qarma`,
  `notify-send` as executable stubs on `PATH` in a temp dir; point `HOME` at a
  temp dir.
- Assertions:
  - Conf-missing path generates the template and changes no wallpaper.
  - Missing required key hard-errors.
  - `--set V=<file>` writes `~/.config/wallp/wall_v` and leaves `H.pid`
    untouched (partial update does not blank H).
  - Theme persistence: `--theme X` writes `~/.config/wallp/theme`; a later
    no-flag run resolves X (persisted wins over conf THEME).
  - Selective kill targets only the changed output's PID.
  - Restore falls back to `DEFAULT_*` when no persistent pointer exists.
  - `~` in conf paths expands to `$HOME`.
- Manual smoke test on the real two-screen setup.

## Migration Notes

- `wal.sh` lines 16-27 (wal/wal2/wpaper copy + symlink) — remove the wal/wal2
  copy lines (16,19,22-23) entirely; the `wpaper` symlink moves into wallp.
  Keep dunst + kitty glue (lines 7,13). `wal.sh` stays the wal `-o` hook.
- Old `qar-lastwall.sh` read `~/.config/wal/*`; old `multiwal.sh` wrote
  `~/.cache/wal/*` — mismatch bug resolved: wallp uses a single owned location
  `~/.config/wallp/`.
- Old default-wallpaper paths had a literal quoted `~` (swaybg would not expand)
  — fixed via `~`→`$HOME` expansion on conf load.
- `~/.cache/wal/wpaper` retained — consumed by `~/.config/rofi/darknix/*.rasi`.
- `~/.cache/wal/wal` + `wal2` dropped — no external consumer (grep-verified).
