Files
Panama/tests/setup/webapp-contract
T
Gabriel Brown 05fd5346db Turn a website into an application, and notice the charger
Two of Section E's small wins.

panama-webapp gives a site its own icon, its own window with no browser
chrome, and its own launcher entry, which is most of what "installed"
means in practice and what both macOS and Windows now ship. It scrapes
the site's apple-touch-icon, falls back twice, and never fails an
install over a favicon: a web app with a generic icon still works.
Names are slugged, so "../../../../tmp/pwn" lands inside the
applications directory as tmp-pwn rather than anywhere else, and
remove refuses anything without the marker it writes -- sharing a name
with a real application must not delete that application. A browser
that cannot do app mode is told so rather than handed something that
opens an ordinary window and pretends.

The charger now announces itself through StatusEvents, which was
already the right layer and only wanted a producer. Ambient priority,
so Do Not Disturb quiets it, because a charger is exactly what DND is
for. A critically low battery is published at a priority DND does not
silence, because the one message you must not miss is the one saying
the machine is about to stop. Both join the existing silent-startup
window rather than announcing the state they found.

The keyboard-layout toast the plan also listed is deliberately not
built. Hyprland exposes the active keymap but not a change event
Quickshell already consumes, so it would need either polling or new
event plumbing, and this machine has one layout and could not test it.
2026-08-22 06:33:19 -04:00

159 lines
6.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# A website, as an application.
#
# The dock and the launcher had nothing to feed them but installed packages,
# while both macOS and Windows now turn a site into something with its own
# icon and its own window. This is that, in about a hundred lines of shell.
#
# What must hold:
#
# 1. The entry it writes is a valid desktop entry that opens the site in app
# mode. A malformed one is invisible rather than broken, which is worse.
# 2. A name cannot escape the applications directory. The name comes from a
# person typing into a launcher box, so "../../../.bashrc" is a thing it
# will eventually be handed.
# 3. Remove only ever removes its own. Sharing a name with a real
# application must not delete that application.
# 4. A missing icon does not fail the install. A web app with a generic icon
# still works; an install that failed because a favicon 404'd does not.
# 5. A browser that cannot do app mode is refused rather than given
# something that opens a normal window and pretends.
#
# Runs against a throwaway XDG_DATA_HOME, so nothing here touches the real
# applications directory. Network calls are stubbed.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
webapp="$repo_dir/bin/panama-webapp"
findings=()
note() { findings+=("$1"); }
[[ -x "$webapp" ]] || { printf 'webapp contract: %s is not executable\n' "$webapp" >&2; exit 1; }
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
data="$work/data"
stub="$work/bin"
mkdir -p "$data/applications" "$stub"
# curl fails for everything, which is the icon-less case. Item 4 says an
# install must survive it.
cat >"$stub/curl" <<'STUB'
#!/usr/bin/env bash
exit 1
STUB
chmod +x "$stub/curl"
# A chromium-family default browser.
cat >"$stub/xdg-settings" <<'STUB'
#!/usr/bin/env bash
printf 'chromium.desktop\n'
STUB
chmod +x "$stub/xdg-settings"
cat >"$stub/chromium" <<'STUB'
#!/usr/bin/env bash
exit 0
STUB
chmod +x "$stub/chromium"
mkdir -p "$data/applications"
cat >"$data/applications/chromium.desktop" <<STUB
[Desktop Entry]
Name=Chromium
Exec=$stub/chromium %U
STUB
run() { XDG_DATA_HOME="$data" PATH="$stub:$PATH" "$webapp" "$@" 2>&1; }
# ── 1 & 4. Install writes a valid entry, even with no icon ──────────────────
output="$(run install https://app.example.com "Example App")"
status=$?
(( status == 0 )) || note "install failed when the icon could not be fetched: $output"
entry="$data/applications/panama-webapp-example-app.desktop"
[[ -f "$entry" ]] || note 'install did not write a desktop entry'
if [[ -f "$entry" ]]; then
grep -q '^Type=Application$' "$entry" || note 'the entry is not an Application'
grep -q '^Name=Example App$' "$entry" || note 'the entry does not carry the name given'
grep -q -- '--app=https://app.example.com' "$entry" \
|| note 'the entry does not open the site in app mode, so it would open an ordinary browser window'
grep -q '^X-Panama-WebApp=' "$entry" \
|| note 'the entry is not marked as a Panama web app, so remove cannot tell it from a real application'
if command -v desktop-file-validate >/dev/null 2>&1; then
desktop-file-validate "$entry" >/dev/null 2>&1 \
|| note 'the entry does not pass desktop-file-validate, so a launcher may ignore it'
fi
fi
# ── 2. A hostile name cannot write outside the directory ────────────────────
run install https://example.com "../../../../tmp/panama-escape" >/dev/null 2>&1
[[ -e "$work/panama-escape.desktop" || -e "/tmp/panama-escape.desktop" ]] \
&& note 'a name containing path separators wrote outside the applications directory'
# It should have landed as a slug inside the directory, or been refused.
escaped="$(find "$data/applications" -name '*escape*' | head -1)"
if [[ -n "$escaped" ]]; then
[[ "$(dirname "$escaped")" == "$data/applications" ]] \
|| note 'a hostile name escaped the applications directory'
fi
# A name with nothing usable in it is refused rather than producing a file
# called ".desktop".
run install https://example.com "///" >/dev/null 2>&1 \
&& note 'a name with no usable characters was accepted'
# ── 3. Remove only removes its own ──────────────────────────────────────────
# A real application that happens to share a name.
cat >"$data/applications/panama-webapp-decoy.desktop" <<'DECOY'
[Desktop Entry]
Type=Application
Name=Decoy
Exec=/bin/true
DECOY
run remove "Decoy" >/dev/null 2>&1 \
&& note 'remove deleted an entry that is not a Panama web app'
[[ -f "$data/applications/panama-webapp-decoy.desktop" ]] \
|| note 'remove deleted a file it should have refused to touch'
run remove "Example App" >/dev/null 2>&1 || note 'remove failed on a web app it installed'
[[ -f "$entry" ]] && note 'remove left the entry behind'
run remove "Not Installed" >/dev/null 2>&1 \
&& note 'removing something that was never installed reported success'
# ── 5. A browser without app mode is refused ────────────────────────────────
cat >"$stub/xdg-settings" <<'STUB'
#!/usr/bin/env bash
printf 'firefox.desktop\n'
STUB
cat >"$data/applications/firefox.desktop" <<'STUB'
[Desktop Entry]
Name=Firefox
Exec=/usr/bin/firefox %U
STUB
output="$(run install https://example.com "Firefox Test" 2>&1)"
status=$?
(( status != 0 )) || note 'a browser with no app mode was accepted, so the entry would open an ordinary window'
grep -qi 'app' <<<"$output" || note 'the refusal does not explain why'
# ── The launcher command ────────────────────────────────────────────────────
command_file="$repo_dir/config/local/share/vicinae/scripts/install-web-app"
[[ -x "$command_file" ]] || note 'there is no launcher command to install a web app'
grep -q 'argument1' "$command_file" \
|| note 'the launcher command takes no URL argument'
if (( ${#findings[@]} > 0 )); then
printf 'webapp contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2
exit 1
fi
printf 'webapp contract: PASS\n'