feat(demo): record the README demo with containerised apps
- commit
- 9d71793ab523035cdc4f532eb0ddb5d2653a4626
- author
- brice · · 2026-10-21 05:17 UTC
- parent
- d34c114
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
12 files · +200 -54
M docs/dev/headless.md +5 -2
| @@ -25,8 +25,11 @@ ffmpeg -f concat -safe 0 -i /tmp/shots/frames.ffconcat -vf "fps=60,format=yuv420 | |||
| 25 | 25 | For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filters. | |
| 26 | 26 | ||
| 27 | 27 | ### Demo recording | |
| 28 | - | Run `tools/demo/record.sh` to rebuild and record the 1080p README demo (Go 1.27, foot, bash, fastfetch, ffmpeg). | |
| 29 | - | The config and timed input live beside it; temporary captures are kept at the printed path. | |
| 28 | + | Run `tools/demo/record.sh` with Go 1.27, rootless Docker and ffmpeg installed. | |
| 29 | + | It builds an Arch app image and the host compositor, then records the 1080p README demo. | |
| 30 | + | Apps run in a disposable, offline container; only the headless Wayland socket is mounted. | |
| 31 | + | The shell timeline launches apps, while `input.txt` drives keys; captures stay at the printed path. | |
| 32 | + | The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`. | |
| 30 | 33 | ||
| 31 | 34 | ## Input scripts | |
| 32 | 35 | ||
| @@ -25,8 +25,11 @@ ffmpeg -f concat -safe 0 -i /tmp/shots/frames.ffconcat -vf "fps=60,format=yuv420 | |||||
| 25 | For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filters. | 25 | For a GIF, convert `demo.mp4` with ffmpeg's `palettegen` and `paletteuse` filters. | ||
| 26 | 26 | ||||
| 27 | ### Demo recording | 27 | ### Demo recording | ||
| 28 | - | Run `tools/demo/record.sh` to rebuild and record the 1080p README demo (Go 1.27, foot, bash, fastfetch, ffmpeg). | 28 | + | Run `tools/demo/record.sh` with Go 1.27, rootless Docker and ffmpeg installed. |
| 29 | - | The config and timed input live beside it; temporary captures are kept at the printed path. | 29 | + | It builds an Arch app image and the host compositor, then records the 1080p README demo. |
| 30 | + | Apps run in a disposable, offline container; only the headless Wayland socket is mounted. | |||
| 31 | + | The shell timeline launches apps, while `input.txt` drives keys; captures stay at the printed path. | |||
| 32 | + | The recording and poster replace `docs/media/demo.mp4` and `docs/media/demo.png`. | |||
| 30 | 33 | ||||
| 31 | ## Input scripts | 34 | ## Input scripts | ||
| 32 | 35 | ||||
M docs/media/demo.mp4 +0 -0 binary file: no text diff
binary file: no text diff
M docs/media/demo.png +0 -0 binary file: no text diff
binary file: no text diff
A tools/demo/Dockerfile +24 -0
| @@ -0,0 +1,24 @@ | |||
| 1 | + | # syntax=docker/dockerfile:1 | |
| 2 | + | FROM archlinux:latest | |
| 3 | + | RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \ | |
| 4 | + | pacman -Sy --noconfirm archlinux-keyring \ | |
| 5 | + | && pacman -Syu --noconfirm --needed \ | |
| 6 | + | firefox foot btop fastfetch eza bat mousepad \ | |
| 7 | + | ttf-jetbrains-mono-nerd noto-fonts noto-fonts-emoji dbus mesa vulkan-swrast | |
| 8 | + | # Arch's btop carries file capabilities that rootless Docker cannot grant. | |
| 9 | + | RUN install -m 0755 /usr/bin/btop /usr/local/bin/btop | |
| 10 | + | RUN useradd -m -u 1000 demo \ | |
| 11 | + | && mkdir -p /home/demo/firefox /home/demo/.config/btop /run/wl \ | |
| 12 | + | && chmod 0700 /run/wl | |
| 13 | + | COPY firefox/user.js /home/demo/firefox/user.js | |
| 14 | + | COPY foot.ini /home/demo/.config/foot/foot.ini | |
| 15 | + | COPY btop.conf /home/demo/.config/btop/btop.conf | |
| 16 | + | COPY site /home/demo/site | |
| 17 | + | COPY config /home/demo/config | |
| 18 | + | RUN chown -R demo:demo /home/demo /run/wl | |
| 19 | + | ENV HOME=/home/demo XDG_RUNTIME_DIR=/run/wl WAYLAND_DISPLAY=wayland-0 \ | |
| 20 | + | GTK_THEME=Adwaita:dark ADW_DEBUG_COLOR_SCHEME=prefer-dark \ | |
| 21 | + | MOZ_ENABLE_WAYLAND=1 LIBGL_ALWAYS_SOFTWARE=1 COLORTERM=truecolor | |
| 22 | + | WORKDIR /home/demo | |
| 23 | + | # The recorder grants demo access to only the disposable headless socket. | |
| 24 | + | CMD ["sleep", "infinity"] | |
| @@ -0,0 +1,24 @@ | |||||
| 1 | + | # syntax=docker/dockerfile:1 | |||
| 2 | + | FROM archlinux:latest | |||
| 3 | + | RUN --mount=type=cache,target=/var/cache/pacman/pkg,sharing=locked \ | |||
| 4 | + | pacman -Sy --noconfirm archlinux-keyring \ | |||
| 5 | + | && pacman -Syu --noconfirm --needed \ | |||
| 6 | + | firefox foot btop fastfetch eza bat mousepad \ | |||
| 7 | + | ttf-jetbrains-mono-nerd noto-fonts noto-fonts-emoji dbus mesa vulkan-swrast | |||
| 8 | + | # Arch's btop carries file capabilities that rootless Docker cannot grant. | |||
| 9 | + | RUN install -m 0755 /usr/bin/btop /usr/local/bin/btop | |||
| 10 | + | RUN useradd -m -u 1000 demo \ | |||
| 11 | + | && mkdir -p /home/demo/firefox /home/demo/.config/btop /run/wl \ | |||
| 12 | + | && chmod 0700 /run/wl | |||
| 13 | + | COPY firefox/user.js /home/demo/firefox/user.js | |||
| 14 | + | COPY foot.ini /home/demo/.config/foot/foot.ini | |||
| 15 | + | COPY btop.conf /home/demo/.config/btop/btop.conf | |||
| 16 | + | COPY site /home/demo/site | |||
| 17 | + | COPY config /home/demo/config | |||
| 18 | + | RUN chown -R demo:demo /home/demo /run/wl | |||
| 19 | + | ENV HOME=/home/demo XDG_RUNTIME_DIR=/run/wl WAYLAND_DISPLAY=wayland-0 \ | |||
| 20 | + | GTK_THEME=Adwaita:dark ADW_DEBUG_COLOR_SCHEME=prefer-dark \ | |||
| 21 | + | MOZ_ENABLE_WAYLAND=1 LIBGL_ALWAYS_SOFTWARE=1 COLORTERM=truecolor | |||
| 22 | + | WORKDIR /home/demo | |||
| 23 | + | # The recorder grants demo access to only the disposable headless socket. | |||
| 24 | + | CMD ["sleep", "infinity"] | |||
A tools/demo/btop.conf +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | color_theme = "tokyo-night" | |
| 2 | + | theme_background = False | |
| 3 | + | truecolor = True | |
| 4 | + | update_ms = 500 | |
| 5 | + | custom_cpu_name = "CPU" | |
| 6 | + | show_cpu_freq = False | |
| 7 | + | shown_boxes = "cpu mem proc" | |
| 8 | + | show_battery = False | |
| 9 | + | proc_tree = False | |
| @@ -0,0 +1,9 @@ | |||||
| 1 | + | color_theme = "tokyo-night" | |||
| 2 | + | theme_background = False | |||
| 3 | + | truecolor = True | |||
| 4 | + | update_ms = 500 | |||
| 5 | + | custom_cpu_name = "CPU" | |||
| 6 | + | show_cpu_freq = False | |||
| 7 | + | shown_boxes = "cpu mem proc" | |||
| 8 | + | show_battery = False | |||
| 9 | + | proc_tree = False | |||
M tools/demo/config +18 -3
| @@ -1,10 +1,25 @@ | |||
| 1 | + | # NeferWL · a little space to think | |
| 1 | 2 | animations = on | |
| 2 | - | terminal = foot -c /dev/null -o font=monospace:size=16 -o colors-dark.background=1a1b26 -o colors-dark.foreground=c0caf5 -e bash --noprofile --norc | |
| 3 | - | terminal.auto-open = first | |
| 4 | 3 | layout.gaps = 8 | |
| 4 | + | layout.max-columns = 2 | |
| 5 | + | layout.overflow = cascade | |
| 5 | 6 | border.width = 2 | |
| 6 | 7 | border.active = #7aa2f7 | |
| 7 | 8 | border.inactive = #414868 | |
| 8 | 9 | background = #1a1b26 | |
| 9 | - | layout.max-columns = 2 | |
| 10 | + | terminal.auto-open = off | |
| 11 | + | stash.spawn-terminal = off | |
| 12 | + | stash.capture = off | |
| 10 | 13 | keyboard.layout = us | |
| 14 | + | ||
| 15 | + | # Columns fill a band; extra windows flow below. | |
| 16 | + | # Super+Up / Down travel between bands. | |
| 17 | + | # Super+Shift+s sets a window aside. | |
| 18 | + | # Super+s brings your stash into view. | |
| 19 | + | # Super+o opens the overview. | |
| 20 | + | bind.cmd+code:3 = focus-workspace 2 | |
| 21 | + | bind.cmd+code:2 = focus-workspace 1 | |
| 22 | + | ||
| 23 | + | # Warm up btop off-screen before bringing its real samples into band 2. | |
| 24 | + | rule.monitor.app-id = demo-monitor | |
| 25 | + | rule.monitor.workspace = 2 | |
| @@ -1,10 +1,25 @@ | |||||
| 1 | + | # NeferWL · a little space to think | |||
| 1 | animations = on | 2 | animations = on | ||
| 2 | - | terminal = foot -c /dev/null -o font=monospace:size=16 -o colors-dark.background=1a1b26 -o colors-dark.foreground=c0caf5 -e bash --noprofile --norc | |||
| 3 | - | terminal.auto-open = first | |||
| 4 | layout.gaps = 8 | 3 | layout.gaps = 8 | ||
| 4 | + | layout.max-columns = 2 | |||
| 5 | + | layout.overflow = cascade | |||
| 5 | border.width = 2 | 6 | border.width = 2 | ||
| 6 | border.active = #7aa2f7 | 7 | border.active = #7aa2f7 | ||
| 7 | border.inactive = #414868 | 8 | border.inactive = #414868 | ||
| 8 | background = #1a1b26 | 9 | background = #1a1b26 | ||
| 9 | - | layout.max-columns = 2 | 10 | + | terminal.auto-open = off |
| 11 | + | stash.spawn-terminal = off | |||
| 12 | + | stash.capture = off | |||
| 10 | keyboard.layout = us | 13 | keyboard.layout = us | ||
| 14 | + | ||||
| 15 | + | # Columns fill a band; extra windows flow below. | |||
| 16 | + | # Super+Up / Down travel between bands. | |||
| 17 | + | # Super+Shift+s sets a window aside. | |||
| 18 | + | # Super+s brings your stash into view. | |||
| 19 | + | # Super+o opens the overview. | |||
| 20 | + | bind.cmd+code:3 = focus-workspace 2 | |||
| 21 | + | bind.cmd+code:2 = focus-workspace 1 | |||
| 22 | + | ||||
| 23 | + | # Warm up btop off-screen before bringing its real samples into band 2. | |||
| 24 | + | rule.monitor.app-id = demo-monitor | |||
| 25 | + | rule.monitor.workspace = 2 | |||
D tools/demo/content.sh +0 -23
| @@ -1,23 +0,0 @@ | |||
| 1 | - | #!/usr/bin/env bash | |
| 2 | - | # Static, colorful terminal content for the recording (no personal shell config). | |
| 3 | - | # These listings are displayed, not parsed; truncation is intentional. | |
| 4 | - | # shellcheck disable=SC2012 | |
| 5 | - | set -euo pipefail | |
| 6 | - | clear | |
| 7 | - | case ${1:-} in | |
| 8 | - | system) | |
| 9 | - | fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors | |
| 10 | - | ls -lago --color=always /etc | head -24 || true | |
| 11 | - | ;; | |
| 12 | - | files) | |
| 13 | - | ls -lgo --color=always /usr/share | head -34 || true | |
| 14 | - | ;; | |
| 15 | - | stash) | |
| 16 | - | printf '\033[1;36m NEFERWL / YOUR STASH\033[0m\n\n' | |
| 17 | - | for ((i = 0; i < 6; i++)); do | |
| 18 | - | printf '\033[1;34m SET ASIDE\033[0m a window with Super+Shift+s\n' | |
| 19 | - | printf '\033[1;32m BRING BACK\033[0m a window with Super+s\n' | |
| 20 | - | printf '\033[1;35m STAY FOCUSED\033[0m keep your workspace clear\n\n' | |
| 21 | - | done | |
| 22 | - | ;; | |
| 23 | - | esac | |
| @@ -1,23 +0,0 @@ | |||||
| 1 | - | #!/usr/bin/env bash | |||
| 2 | - | # Static, colorful terminal content for the recording (no personal shell config). | |||
| 3 | - | # These listings are displayed, not parsed; truncation is intentional. | |||
| 4 | - | # shellcheck disable=SC2012 | |||
| 5 | - | set -euo pipefail | |||
| 6 | - | clear | |||
| 7 | - | case ${1:-} in | |||
| 8 | - | system) | |||
| 9 | - | fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors | |||
| 10 | - | ls -lago --color=always /etc | head -24 || true | |||
| 11 | - | ;; | |||
| 12 | - | files) | |||
| 13 | - | ls -lgo --color=always /usr/share | head -34 || true | |||
| 14 | - | ;; | |||
| 15 | - | stash) | |||
| 16 | - | printf '\033[1;36m NEFERWL / YOUR STASH\033[0m\n\n' | |||
| 17 | - | for ((i = 0; i < 6; i++)); do | |||
| 18 | - | printf '\033[1;34m SET ASIDE\033[0m a window with Super+Shift+s\n' | |||
| 19 | - | printf '\033[1;32m BRING BACK\033[0m a window with Super+s\n' | |||
| 20 | - | printf '\033[1;35m STAY FOCUSED\033[0m keep your workspace clear\n\n' | |||
| 21 | - | done | |||
| 22 | - | ;; | |||
| 23 | - | esac | |||
A tools/demo/firefox/user.js +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | user_pref("ui.systemUsesDarkTheme", 1); | |
| 2 | + | user_pref("layout.css.prefers-color-scheme.content-override", 0); | |
| 3 | + | user_pref("browser.theme.content-theme", 0); | |
| 4 | + | user_pref("browser.theme.toolbar-theme", 0); | |
| 5 | + | user_pref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org"); | |
| 6 | + | user_pref("browser.shell.checkDefaultBrowser", false); | |
| 7 | + | user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); | |
| 8 | + | user_pref("browser.startup.homepage_override.mstone", "ignore"); | |
| 9 | + | user_pref("browser.aboutwelcome.enabled", false); | |
| 10 | + | user_pref("toolkit.telemetry.reportingpolicy.firstRun", false); | |
| 11 | + | user_pref("browser.toolbars.bookmarks.visibility", "never"); | |
| 12 | + | user_pref("widget.gtk.overlay-scrollbars.enabled", true); | |
| 13 | + | user_pref("browser.tabs.warnOnClose", false); | |
| @@ -0,0 +1,13 @@ | |||||
| 1 | + | user_pref("ui.systemUsesDarkTheme", 1); | |||
| 2 | + | user_pref("layout.css.prefers-color-scheme.content-override", 0); | |||
| 3 | + | user_pref("browser.theme.content-theme", 0); | |||
| 4 | + | user_pref("browser.theme.toolbar-theme", 0); | |||
| 5 | + | user_pref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org"); | |||
| 6 | + | user_pref("browser.shell.checkDefaultBrowser", false); | |||
| 7 | + | user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); | |||
| 8 | + | user_pref("browser.startup.homepage_override.mstone", "ignore"); | |||
| 9 | + | user_pref("browser.aboutwelcome.enabled", false); | |||
| 10 | + | user_pref("toolkit.telemetry.reportingpolicy.firstRun", false); | |||
| 11 | + | user_pref("browser.toolbars.bookmarks.visibility", "never"); | |||
| 12 | + | user_pref("widget.gtk.overlay-scrollbars.enabled", true); | |||
| 13 | + | user_pref("browser.tabs.warnOnClose", false); | |||
A tools/demo/foot.ini +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | [main] | |
| 2 | + | font=JetBrainsMono Nerd Font:size=15 | |
| 3 | + | pad=20x20 | |
| 4 | + | ||
| 5 | + | [colors-dark] | |
| 6 | + | background=1a1b26 | |
| 7 | + | foreground=c0caf5 | |
| 8 | + | regular0=15161e | |
| 9 | + | regular1=f7768e | |
| 10 | + | regular2=9ece6a | |
| 11 | + | regular3=e0af68 | |
| 12 | + | regular4=7aa2f7 | |
| 13 | + | regular5=bb9af7 | |
| 14 | + | regular6=7dcfff | |
| 15 | + | regular7=a9b1d6 | |
| 16 | + | bright0=414868 | |
| 17 | + | bright1=f7768e | |
| 18 | + | bright2=9ece6a | |
| 19 | + | bright3=e0af68 | |
| 20 | + | bright4=7aa2f7 | |
| 21 | + | bright5=bb9af7 | |
| 22 | + | bright6=7dcfff | |
| 23 | + | bright7=c0caf5 | |
| @@ -0,0 +1,23 @@ | |||||
| 1 | + | [main] | |||
| 2 | + | font=JetBrainsMono Nerd Font:size=15 | |||
| 3 | + | pad=20x20 | |||
| 4 | + | ||||
| 5 | + | [colors-dark] | |||
| 6 | + | background=1a1b26 | |||
| 7 | + | foreground=c0caf5 | |||
| 8 | + | regular0=15161e | |||
| 9 | + | regular1=f7768e | |||
| 10 | + | regular2=9ece6a | |||
| 11 | + | regular3=e0af68 | |||
| 12 | + | regular4=7aa2f7 | |||
| 13 | + | regular5=bb9af7 | |||
| 14 | + | regular6=7dcfff | |||
| 15 | + | regular7=a9b1d6 | |||
| 16 | + | bright0=414868 | |||
| 17 | + | bright1=f7768e | |||
| 18 | + | bright2=9ece6a | |||
| 19 | + | bright3=e0af68 | |||
| 20 | + | bright4=7aa2f7 | |||
| 21 | + | bright5=bb9af7 | |||
| 22 | + | bright6=7dcfff | |||
| 23 | + | bright7=c0caf5 | |||
M tools/demo/input.txt +27 -20
| @@ -1,36 +1,43 @@ | |||
| 1 | - | sleep 2s | |
| 2 | 1 | move 1918 1078 | |
| 3 | - | type bash tools/demo/content.sh system | |
| 4 | - | key Return | |
| 5 | - | sleep 3s | |
| 6 | - | key Super+Return | |
| 2 | + | sleep 12s | |
| 3 | + | key Super+Up | |
| 7 | 4 | sleep 1500ms | |
| 8 | - | type bash tools/demo/content.sh files | |
| 9 | - | key Return | |
| 10 | - | sleep 3s | |
| 11 | - | key Super+Return | |
| 5 | + | key Super+Down | |
| 12 | 6 | sleep 1500ms | |
| 13 | - | type bash tools/demo/content.sh stash | |
| 14 | - | key Return | |
| 15 | - | sleep 2500ms | |
| 16 | 7 | key Super+Shift+s | |
| 17 | - | sleep 1500ms | |
| 8 | + | sleep 500ms | |
| 18 | 9 | key Super+s | |
| 10 | + | sleep 1500ms | |
| 11 | + | key Super+2 | |
| 12 | + | key Super+Shift+1 | |
| 13 | + | key Super+1 | |
| 19 | 14 | sleep 3s | |
| 15 | + | key Super+Shift+s | |
| 16 | + | sleep 500ms | |
| 17 | + | key Super+s | |
| 18 | + | sleep 1500ms | |
| 20 | 19 | key Super+s | |
| 21 | - | sleep 1s | |
| 22 | 20 | key Super+Left | |
| 23 | - | sleep 1s | |
| 21 | + | sleep 2s | |
| 24 | 22 | key Super+Right | |
| 23 | + | sleep 2s | |
| 24 | + | key Super+Left | |
| 25 | 25 | sleep 1s | |
| 26 | - | key Super+o | |
| 26 | + | key Super+s | |
| 27 | 27 | sleep 2s | |
| 28 | + | key Super+2 | |
| 29 | + | sleep 4s | |
| 30 | + | key Super+1 | |
| 31 | + | sleep 1500ms | |
| 32 | + | key Super+o | |
| 33 | + | sleep 1500ms | |
| 28 | 34 | key Left | |
| 29 | 35 | sleep 1s | |
| 36 | + | key Down | |
| 37 | + | sleep 1500ms | |
| 38 | + | key Right | |
| 39 | + | sleep 1500ms | |
| 30 | 40 | key Up | |
| 31 | 41 | sleep 1s | |
| 32 | 42 | key Return | |
| 33 | - | sleep 500ms | |
| 34 | - | key Super+s | |
| 35 | - | sleep 2s | |
| 36 | - | key Ctrl+Alt+BackSpace | |
| 43 | + | sleep 4s | |
| @@ -1,36 +1,43 @@ | |||||
| 1 | - | sleep 2s | |||
| 2 | move 1918 1078 | 1 | move 1918 1078 | ||
| 3 | - | type bash tools/demo/content.sh system | 2 | + | sleep 12s |
| 4 | - | key Return | 3 | + | key Super+Up |
| 5 | - | sleep 3s | |||
| 6 | - | key Super+Return | |||
| 7 | sleep 1500ms | 4 | sleep 1500ms | ||
| 8 | - | type bash tools/demo/content.sh files | 5 | + | key Super+Down |
| 9 | - | key Return | |||
| 10 | - | sleep 3s | |||
| 11 | - | key Super+Return | |||
| 12 | sleep 1500ms | 6 | sleep 1500ms | ||
| 13 | - | type bash tools/demo/content.sh stash | |||
| 14 | - | key Return | |||
| 15 | - | sleep 2500ms | |||
| 16 | key Super+Shift+s | 7 | key Super+Shift+s | ||
| 17 | - | sleep 1500ms | 8 | + | sleep 500ms |
| 18 | key Super+s | 9 | key Super+s | ||
| 10 | + | sleep 1500ms | |||
| 11 | + | key Super+2 | |||
| 12 | + | key Super+Shift+1 | |||
| 13 | + | key Super+1 | |||
| 19 | sleep 3s | 14 | sleep 3s | ||
| 15 | + | key Super+Shift+s | |||
| 16 | + | sleep 500ms | |||
| 17 | + | key Super+s | |||
| 18 | + | sleep 1500ms | |||
| 20 | key Super+s | 19 | key Super+s | ||
| 21 | - | sleep 1s | |||
| 22 | key Super+Left | 20 | key Super+Left | ||
| 23 | - | sleep 1s | 21 | + | sleep 2s |
| 24 | key Super+Right | 22 | key Super+Right | ||
| 23 | + | sleep 2s | |||
| 24 | + | key Super+Left | |||
| 25 | sleep 1s | 25 | sleep 1s | ||
| 26 | - | key Super+o | 26 | + | key Super+s |
| 27 | sleep 2s | 27 | sleep 2s | ||
| 28 | + | key Super+2 | |||
| 29 | + | sleep 4s | |||
| 30 | + | key Super+1 | |||
| 31 | + | sleep 1500ms | |||
| 32 | + | key Super+o | |||
| 33 | + | sleep 1500ms | |||
| 28 | key Left | 34 | key Left | ||
| 29 | sleep 1s | 35 | sleep 1s | ||
| 36 | + | key Down | |||
| 37 | + | sleep 1500ms | |||
| 38 | + | key Right | |||
| 39 | + | sleep 1500ms | |||
| 30 | key Up | 40 | key Up | ||
| 31 | sleep 1s | 41 | sleep 1s | ||
| 32 | key Return | 42 | key Return | ||
| 33 | - | sleep 500ms | 43 | + | sleep 4s |
| 34 | - | key Super+s | |||
| 35 | - | sleep 2s | |||
| 36 | - | key Ctrl+Alt+BackSpace | |||
M tools/demo/record.sh +68 -6
| @@ -1,18 +1,80 @@ | |||
| 1 | 1 | #!/usr/bin/env bash | |
| 2 | - | # Requires Go 1.27, foot 1.28+, bash, fastfetch and ffmpeg; run from any directory. | |
| 2 | + | # Requires Go 1.27, rootless Docker, bash and ffmpeg. Run from any directory. | |
| 3 | 3 | set -euo pipefail | |
| 4 | 4 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | |
| 5 | 5 | cd "$root" | |
| 6 | 6 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | |
| 7 | - | # Keep the temporary capture for inspection; every run gets a fresh shots dir. | |
| 8 | 7 | printf 'Capture directory: %s\n' "$work" | |
| 9 | 8 | mkdir -p "$work/shots" docs/media | |
| 9 | + | compositor='' launcher='' container_started='' | |
| 10 | + | cleanup() { | |
| 11 | + | if [[ -n $launcher ]]; then kill "$launcher" 2>/dev/null || true; fi | |
| 12 | + | if [[ -n $container_started ]]; then docker rm -f neferwl-demo >/dev/null 2>&1 || true; fi | |
| 13 | + | if [[ -n $compositor ]]; then kill "$compositor" 2>/dev/null || true; fi | |
| 14 | + | } | |
| 15 | + | trap cleanup EXIT | |
| 16 | + | trap 'exit 130' INT | |
| 17 | + | trap 'exit 143' TERM | |
| 18 | + | # Refuse to replace someone else's running container. | |
| 19 | + | if [[ -n $(docker ps -aq --filter 'name=^/neferwl-demo$') ]]; then | |
| 20 | + | echo 'Container neferwl-demo already exists; remove it before recording.' >&2 | |
| 21 | + | exit 1 | |
| 22 | + | fi | |
| 23 | + | docker build -t neferwl-demo -f tools/demo/Dockerfile tools/demo | |
| 10 | 24 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | |
| 11 | - | env -u NEFERWL_STATE -u NEFERWL_SLOT "$work/neferwl" \ | |
| 25 | + | log=${XDG_STATE_HOME:-$HOME/.local/state}/neferwl/runs/headless/latest.log | |
| 26 | + | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY "$work/neferwl" \ | |
| 12 | 27 | --backend=headless --size 1920x1080 --config tools/demo/config \ | |
| 13 | - | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 35s | |
| 28 | + | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 45s & | |
| 29 | + | compositor=$! | |
| 30 | + | SECONDS=0 | |
| 31 | + | # The host starts only the compositor; all apps and their content stay in Docker. | |
| 32 | + | # A fixed startup allowance avoids reading a previous run's socket from the log. | |
| 33 | + | sleep 1 | |
| 34 | + | kill -0 "$compositor" | |
| 35 | + | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^"]*\)".*/\1/p' "$log" | tail -1) | |
| 36 | + | [[ -n $display ]] || { echo "No listening socket in $log" >&2; exit 1; } | |
| 37 | + | socket=$XDG_RUNTIME_DIR/${display##*/} | |
| 38 | + | [[ -S $socket ]] || { echo "Not a socket: $socket" >&2; exit 1; } | |
| 39 | + | # Container root maps to the host uid under rootless Docker. Grant demo access | |
| 40 | + | # to this disposable socket only; Firefox rejects root with a demo-owned HOME. | |
| 41 | + | docker run -d --name neferwl-demo --hostname neferwl-demo --network none \ | |
| 42 | + | -v "$socket:/run/wl/wayland-0" neferwl-demo | |
| 43 | + | container_started=yes | |
| 44 | + | docker exec neferwl-demo chmod 666 /run/wl/wayland-0 | |
| 45 | + | at() { local delay=$(($1 - SECONDS)); if ((delay > 0)); then sleep "$delay"; fi; } | |
| 46 | + | launch_apps() { | |
| 47 | + | # This shell timeline shares t=0 with input.txt. Only keys live in input.txt; | |
| 48 | + | # Docker launches cannot be invoked by compositor input scripts. | |
| 49 | + | docker exec -d --user demo neferwl-demo foot --title 'System / NeferWL' bash -c \ | |
| 50 | + | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |
| 51 | + | at 3 | |
| 52 | + | docker exec -d --user demo neferwl-demo dbus-run-session -- firefox --no-remote \ | |
| 53 | + | --profile /home/demo/firefox file:///home/demo/site/index.html | |
| 54 | + | # btop's first per-core sample is bogus in this container. Let it collect | |
| 55 | + | # real deltas on workspace 2; input.txt brings it into view at t=17. | |
| 56 | + | at 5 | |
| 57 | + | docker exec -d --user demo neferwl-demo foot --app-id demo-monitor \ | |
| 58 | + | --title 'Monitor / btop' /usr/local/bin/btop | |
| 59 | + | at 9 | |
| 60 | + | docker exec -d --user demo neferwl-demo dbus-run-session -- bash -c \ | |
| 61 | + | 'gsettings set org.xfce.mousepad.preferences.view use-default-monospace-font false; gsettings set org.xfce.mousepad.preferences.view font-name "JetBrainsMono Nerd Font 15"; gsettings set org.xfce.mousepad.preferences.window menubar-visible false; mousepad /home/demo/config' | |
| 62 | + | at 30 | |
| 63 | + | docker exec -d --user demo neferwl-demo foot --title 'Fonts / workspace 2' bash -c \ | |
| 64 | + | 'eza --tree --level=2 --icons --color=always /usr/share/fonts | head -30; exec sleep infinity' | |
| 65 | + | } | |
| 66 | + | launch_apps & | |
| 67 | + | launcher=$! | |
| 68 | + | # --timeout exits nonzero; accept only its explicit timeout log entry. | |
| 69 | + | if ! wait "$compositor"; then | |
| 70 | + | grep -q '"reason":"timeout"' "$log" || exit 1 | |
| 71 | + | fi | |
| 72 | + | compositor= | |
| 73 | + | wait "$launcher" | |
| 74 | + | launcher= | |
| 14 | 75 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | |
| 15 | - | -i "$work/shots/frames.ffconcat" -vf 'fps=60,format=yuv420p' \ | |
| 76 | + | -i "$work/shots/frames.ffconcat" -vf 'fps=30,format=yuv420p' \ | |
| 16 | 77 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | |
| 17 | - | ffmpeg -hide_banner -loglevel warning -ss 15.5 -i docs/media/demo.mp4 \ | |
| 78 | + | ffmpeg -hide_banner -loglevel warning -ss 7 -i docs/media/demo.mp4 \ | |
| 18 | 79 | -frames:v 1 -update 1 docs/media/demo.png -y | |
| 80 | + | printf 'Recorded: %s\n' "$work" | |
| @@ -1,18 +1,80 @@ | |||||
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash | ||
| 2 | - | # Requires Go 1.27, foot 1.28+, bash, fastfetch and ffmpeg; run from any directory. | 2 | + | # Requires Go 1.27, rootless Docker, bash and ffmpeg. Run from any directory. |
| 3 | set -euo pipefail | 3 | set -euo pipefail | ||
| 4 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | 4 | root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd) | ||
| 5 | cd "$root" | 5 | cd "$root" | ||
| 6 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | 6 | work=$(mktemp -d "${TMPDIR:-/tmp}/neferwl-demo.XXXXXX") | ||
| 7 | - | # Keep the temporary capture for inspection; every run gets a fresh shots dir. | |||
| 8 | printf 'Capture directory: %s\n' "$work" | 7 | printf 'Capture directory: %s\n' "$work" | ||
| 9 | mkdir -p "$work/shots" docs/media | 8 | mkdir -p "$work/shots" docs/media | ||
| 9 | + | compositor='' launcher='' container_started='' | |||
| 10 | + | cleanup() { | |||
| 11 | + | if [[ -n $launcher ]]; then kill "$launcher" 2>/dev/null || true; fi | |||
| 12 | + | if [[ -n $container_started ]]; then docker rm -f neferwl-demo >/dev/null 2>&1 || true; fi | |||
| 13 | + | if [[ -n $compositor ]]; then kill "$compositor" 2>/dev/null || true; fi | |||
| 14 | + | } | |||
| 15 | + | trap cleanup EXIT | |||
| 16 | + | trap 'exit 130' INT | |||
| 17 | + | trap 'exit 143' TERM | |||
| 18 | + | # Refuse to replace someone else's running container. | |||
| 19 | + | if [[ -n $(docker ps -aq --filter 'name=^/neferwl-demo$') ]]; then | |||
| 20 | + | echo 'Container neferwl-demo already exists; remove it before recording.' >&2 | |||
| 21 | + | exit 1 | |||
| 22 | + | fi | |||
| 23 | + | docker build -t neferwl-demo -f tools/demo/Dockerfile tools/demo | |||
| 10 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | 24 | CGO_ENABLED=0 go build -o "$work/neferwl" ./cmd/neferwl | ||
| 11 | - | env -u NEFERWL_STATE -u NEFERWL_SLOT "$work/neferwl" \ | 25 | + | log=${XDG_STATE_HOME:-$HOME/.local/state}/neferwl/runs/headless/latest.log |
| 26 | + | env -u NEFERWL_STATE -u NEFERWL_SLOT -u WAYLAND_DISPLAY "$work/neferwl" \ | |||
| 12 | --backend=headless --size 1920x1080 --config tools/demo/config \ | 27 | --backend=headless --size 1920x1080 --config tools/demo/config \ | ||
| 13 | - | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 35s | 28 | + | --no-xwayland --screenshot "$work/shots" --input tools/demo/input.txt --timeout 45s & |
| 29 | + | compositor=$! | |||
| 30 | + | SECONDS=0 | |||
| 31 | + | # The host starts only the compositor; all apps and their content stay in Docker. | |||
| 32 | + | # A fixed startup allowance avoids reading a previous run's socket from the log. | |||
| 33 | + | sleep 1 | |||
| 34 | + | kill -0 "$compositor" | |||
| 35 | + | display=$(sed -n '/"message":"listening"/s/.*"WAYLAND_DISPLAY":"\([^"]*\)".*/\1/p' "$log" | tail -1) | |||
| 36 | + | [[ -n $display ]] || { echo "No listening socket in $log" >&2; exit 1; } | |||
| 37 | + | socket=$XDG_RUNTIME_DIR/${display##*/} | |||
| 38 | + | [[ -S $socket ]] || { echo "Not a socket: $socket" >&2; exit 1; } | |||
| 39 | + | # Container root maps to the host uid under rootless Docker. Grant demo access | |||
| 40 | + | # to this disposable socket only; Firefox rejects root with a demo-owned HOME. | |||
| 41 | + | docker run -d --name neferwl-demo --hostname neferwl-demo --network none \ | |||
| 42 | + | -v "$socket:/run/wl/wayland-0" neferwl-demo | |||
| 43 | + | container_started=yes | |||
| 44 | + | docker exec neferwl-demo chmod 666 /run/wl/wayland-0 | |||
| 45 | + | at() { local delay=$(($1 - SECONDS)); if ((delay > 0)); then sleep "$delay"; fi; } | |||
| 46 | + | launch_apps() { | |||
| 47 | + | # This shell timeline shares t=0 with input.txt. Only keys live in input.txt; | |||
| 48 | + | # Docker launches cannot be invoked by compositor input scripts. | |||
| 49 | + | docker exec -d --user demo neferwl-demo foot --title 'System / NeferWL' bash -c \ | |||
| 50 | + | 'fastfetch --logo small --structure OS:Kernel:WM:Terminal:Memory:Colors; eza -la --no-user --icons --color=always /etc | head -24; exec sleep infinity' | |||
| 51 | + | at 3 | |||
| 52 | + | docker exec -d --user demo neferwl-demo dbus-run-session -- firefox --no-remote \ | |||
| 53 | + | --profile /home/demo/firefox file:///home/demo/site/index.html | |||
| 54 | + | # btop's first per-core sample is bogus in this container. Let it collect | |||
| 55 | + | # real deltas on workspace 2; input.txt brings it into view at t=17. | |||
| 56 | + | at 5 | |||
| 57 | + | docker exec -d --user demo neferwl-demo foot --app-id demo-monitor \ | |||
| 58 | + | --title 'Monitor / btop' /usr/local/bin/btop | |||
| 59 | + | at 9 | |||
| 60 | + | docker exec -d --user demo neferwl-demo dbus-run-session -- bash -c \ | |||
| 61 | + | 'gsettings set org.xfce.mousepad.preferences.view use-default-monospace-font false; gsettings set org.xfce.mousepad.preferences.view font-name "JetBrainsMono Nerd Font 15"; gsettings set org.xfce.mousepad.preferences.window menubar-visible false; mousepad /home/demo/config' | |||
| 62 | + | at 30 | |||
| 63 | + | docker exec -d --user demo neferwl-demo foot --title 'Fonts / workspace 2' bash -c \ | |||
| 64 | + | 'eza --tree --level=2 --icons --color=always /usr/share/fonts | head -30; exec sleep infinity' | |||
| 65 | + | } | |||
| 66 | + | launch_apps & | |||
| 67 | + | launcher=$! | |||
| 68 | + | # --timeout exits nonzero; accept only its explicit timeout log entry. | |||
| 69 | + | if ! wait "$compositor"; then | |||
| 70 | + | grep -q '"reason":"timeout"' "$log" || exit 1 | |||
| 71 | + | fi | |||
| 72 | + | compositor= | |||
| 73 | + | wait "$launcher" | |||
| 74 | + | launcher= | |||
| 14 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | 75 | ffmpeg -hide_banner -loglevel warning -f concat -safe 0 \ | ||
| 15 | - | -i "$work/shots/frames.ffconcat" -vf 'fps=60,format=yuv420p' \ | 76 | + | -i "$work/shots/frames.ffconcat" -vf 'fps=30,format=yuv420p' \ |
| 16 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | 77 | -c:v libx264 -crf 23 -movflags +faststart docs/media/demo.mp4 -y | ||
| 17 | - | ffmpeg -hide_banner -loglevel warning -ss 15.5 -i docs/media/demo.mp4 \ | 78 | + | ffmpeg -hide_banner -loglevel warning -ss 7 -i docs/media/demo.mp4 \ |
| 18 | -frames:v 1 -update 1 docs/media/demo.png -y | 79 | -frames:v 1 -update 1 docs/media/demo.png -y | ||
| 80 | + | printf 'Recorded: %s\n' "$work" | |||
A tools/demo/site/index.html +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | <!doctype html> | |
| 2 | + | <html lang="en"> | |
| 3 | + | <meta charset="utf-8"> | |
| 4 | + | <meta name="color-scheme" content="dark"> | |
| 5 | + | <title>NeferWL — Space to think</title> | |
| 6 | + | <style> | |
| 7 | + | *{box-sizing:border-box}body{margin:0;background:#1a1b26;color:#c0caf5;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#7aa2f7;letter-spacing:.2em;font:700 18px monospace}small{color:#9ece6a}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a9b1d6}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#24283b;border:1px solid #414868;border-radius:12px}.card b{color:#7dcfff}.card p{font-size:15px;margin-bottom:0}pre{background:#16161e;border-left:3px solid #7aa2f7;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#9ece6a}footer{color:#737aa2;font:14px monospace} | |
| 8 | + | </style> | |
| 9 | + | <main><header>◈ NEFERWL</header><h1>Your desktop.<br>A little more focused.</h1><p>A Wayland compositor with room for your work.<br>Tile naturally, set things aside, see the whole picture.</p><div class="cards"><section class="card"><b>01 / Performance</b><p>Small, responsive.<br>Built in Go. Made to stay out of your way.</p></section><section class="card"><b>02 / Stash</b><p>Keep tools close.<br>Set aside a window. Bring it back in a keystroke.</p></section><section class="card"><b>03 / Overview</b><p>Find your focus.<br>Every workspace and stashed window, at a glance.</p></section></div><pre># A desktop that flows with you | |
| 10 | + | layout.max-columns = 2 | |
| 11 | + | layout.overflow = cascade | |
| 12 | + | animations = on</pre><footer>SUPER + ↑ / ↓ bands · SUPER + S stash · SUPER + O overview</footer></main> | |
| 13 | + | </html> | |
| @@ -0,0 +1,13 @@ | |||||
| 1 | + | <!doctype html> | |||
| 2 | + | <html lang="en"> | |||
| 3 | + | <meta charset="utf-8"> | |||
| 4 | + | <meta name="color-scheme" content="dark"> | |||
| 5 | + | <title>NeferWL — Space to think</title> | |||
| 6 | + | <style> | |||
| 7 | + | *{box-sizing:border-box}body{margin:0;background:#1a1b26;color:#c0caf5;font:18px/1.6 sans-serif}main{max-width:960px;margin:auto;padding:60px 38px}header{color:#7aa2f7;letter-spacing:.2em;font:700 18px monospace}small{color:#9ece6a}h1{font-size:54px;line-height:1.12;letter-spacing:-2px;margin:40px 0 24px}p{color:#a9b1d6}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin:36px 0}.card{padding:18px;background:#24283b;border:1px solid #414868;border-radius:12px}.card b{color:#7dcfff}.card p{font-size:15px;margin-bottom:0}pre{background:#16161e;border-left:3px solid #7aa2f7;border-radius:6px;padding:24px;font:16px/1.8 monospace;color:#9ece6a}footer{color:#737aa2;font:14px monospace} | |||
| 8 | + | </style> | |||
| 9 | + | <main><header>◈ NEFERWL</header><h1>Your desktop.<br>A little more focused.</h1><p>A Wayland compositor with room for your work.<br>Tile naturally, set things aside, see the whole picture.</p><div class="cards"><section class="card"><b>01 / Performance</b><p>Small, responsive.<br>Built in Go. Made to stay out of your way.</p></section><section class="card"><b>02 / Stash</b><p>Keep tools close.<br>Set aside a window. Bring it back in a keystroke.</p></section><section class="card"><b>03 / Overview</b><p>Find your focus.<br>Every workspace and stashed window, at a glance.</p></section></div><pre># A desktop that flows with you | |||
| 10 | + | layout.max-columns = 2 | |||
| 11 | + | layout.overflow = cascade | |||
| 12 | + | animations = on</pre><footer>SUPER + ↑ / ↓ bands · SUPER + S stash · SUPER + O overview</footer></main> | |||
| 13 | + | </html> | |||