contrib/screenshot-generator/default.nix (7287B) - raw
1 let
2 sup-shell = import ../nix/ruby4.0-shell.nix;
3 pkgs = sup-shell.pkgs;
4 fileset = pkgs.lib.fileset;
5 sup = fileset.toSource {
6 root = ../..;
7 fileset = fileset.difference ../.. ../../contrib;
8 };
9 ht = pkgs.rustPlatform.buildRustPackage (finalAttrs: {
10 pname = "ht";
11 version = "0.4.0";
12 src = pkgs.fetchFromGitHub {
13 owner = "andyk";
14 repo = "ht";
15 tag = "v${finalAttrs.version}";
16 hash = "sha256-LKa5nnGhQITf0SDzE17NUJ9KQ6Soq6jOBWoTONRxiCc=";
17 };
18 cargoHash = "sha256-FlR7bvQAZZIJaONOGsZUtSuUOvAVeDXJQaEkvXYxp1w=";
19 });
20 deps = with pkgs; [
21 sup-shell.gems.wrappedRuby
22 gnupg
23 ncurses
24 which
25 rsync
26 libfaketime
27 ht
28 ];
29
30 now = "2026-04-21 20:00:00";
31 lists.supmua = pkgs.fetchgit {
32 name = "supmua-list";
33 url = "https://supmua.dev/lists/supmua/0";
34 rev = "5bb23abca5490dc0d946312a8dbc3ece92bbfa95";
35 hash = "sha256-c25+ZfDOz8oSx95p1Mq7LI35n1F4IFDWog4YZUn644M=";
36 deepClone = true;
37 };
38 lists.sup-devel = pkgs.fetchgit {
39 name = "sup-devel-list";
40 url = "https://supmua.dev/lists/sup-devel/0";
41 rev = "5e44841d194d5bfb581022d5951e0964a1d85edd";
42 hash = "sha256-g9tPF97noIVoc3xfWM6D+aNDWOyn2SX967YsO4jabpM=";
43 deepClone = true;
44 };
45 lists.sup-talk = pkgs.fetchgit {
46 name = "sup-talk-list";
47 url = "https://supmua.dev/lists/sup-talk/0";
48 rev = "93f1725cde6bb046a7a086eb1b99342a3371802c";
49 hash = "sha256-ksbTPTcE7ueGKWjC+rC1xkiRF4/MpJcSqJhoA735G/Y=";
50 deepClone = true;
51 };
52 lists-as-maildir = builtins.mapAttrs (
53 name: pirepo:
54 pkgs.stdenv.mkDerivation {
55 name = "${name}-as-maildir";
56 nativeBuildInputs = with pkgs; [ git ];
57 buildCommand = ''
58 mkdir -p $out/{cur,new,tmp}
59 export GIT_DIR=${pirepo}/.git
60 git rev-list HEAD | while read sha ; do
61 git show $sha:m >$out/new/$sha:2,S
62 done
63 '';
64 }
65 ) lists;
66 sup-is-plain-text = ./sup-is-plain-text.msg;
67 sup-is-plain-text-applied = ./sup-is-plain-text-applied.msg;
68 misc-maildir = pkgs.stdenv.mkDerivation {
69 name = "sup-screenshots-misc-maildir";
70 nativeBuildInputs = with pkgs; [
71 curl
72 python3
73 ];
74 buildCommand = ''
75 mkdir -p $out/{cur,new,tmp}
76 cp ${sup-is-plain-text} $out/cur/sup-is-plain-text:2,S
77 cp ${sup-is-plain-text-applied} $out/cur/sup-is-plain-text-applied:2,S
78 '';
79 };
80
81 # Build a fake .sup directory for screenshots.
82 sup-base = pkgs.stdenv.mkDerivation {
83 name = "sup-base-for-screenshots";
84 nativeBuildInputs = deps;
85 env = {
86 TZ = "UTC";
87 USER = "nobody";
88 LANG = "C.UTF-8";
89 };
90 buildCommand = ''
91 mkdir -p $out
92
93 cat >$out/config.yaml <<EOF
94 ---
95 :accounts:
96 :default:
97 :name: Sup User
98 :email: sup@example.invalid
99 :sendmail: /bin/false
100 :sync_back_to_maildir: false
101 :editor: /bin/false
102 :poll_interval: 86400
103 EOF
104
105 cat >$out/sources.yaml <<EOF
106 ---
107 - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
108 uri: maildir://${lists-as-maildir.supmua}
109 usual: true
110 archived: false
111 id: 1
112 labels: [supmua]
113 - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
114 uri: maildir://${lists-as-maildir.sup-devel}
115 usual: true
116 archived: false
117 id: 2
118 labels: [sup-devel]
119 - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
120 uri: maildir://${lists-as-maildir.sup-talk}
121 usual: true
122 archived: false
123 id: 3
124 labels: [sup-talk]
125 - !<tag:supmua.org,2006-10-01/Redwood/Maildir>
126 uri: maildir://${misc-maildir}
127 usual: true
128 archived: false
129 id: 4
130 labels: []
131 EOF
132
133 export SUP_BASE=$(readlink -f $out)
134 faketime -m "${now}" ruby ${sup}/bin/sup-sync
135 '';
136 };
137
138 # For each scene in scenes.yaml, drive sup in ht (a headless terminal)
139 # and capture the final screen state.
140 screenshots-ansi = pkgs.stdenv.mkDerivation {
141 name = "sup-screenshots-ansi";
142 nativeBuildInputs = deps;
143 env = {
144 TZ = "UTC";
145 USER = "nobody";
146 LANG = "C.UTF-8";
147 };
148 buildCommand = ''
149 faketime -m "${now}" ruby ${./shoot.rb} \
150 --sup-base=${sup-base} \
151 --sup=${sup}/bin/sup \
152 --scenes-yaml=${./scenes.yaml} \
153 --out=$out
154 '';
155 };
156
157 # Render the ANSI screens in a headless Wayland terminal.
158 iosevka = pkgs.iosevka-bin.override { variant = "SS09"; };
159 fonts-conf = pkgs.writeText "sup-screenshots-fonts.conf" ''
160 <?xml version="1.0"?>
161 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
162 <fontconfig>
163 <dir>${iosevka}/share/fonts</dir>
164 <dir>${pkgs.dejavu_fonts.minimal}/share/fonts</dir>
165 <cachedir prefix="xdg">fontconfig</cachedir>
166 <config></config>
167 </fontconfig>
168 '';
169 sway-conf = pkgs.writeText "sup-screenshots-sway.conf" ''
170 output HEADLESS-1 mode 3840x2160 scale 2
171 output HEADLESS-1 background #000000 solid_color
172 default_border none
173 default_floating_border none
174 gaps inner 0
175 gaps outer 0
176 xwayland disable
177 '';
178 foot-conf = pkgs.writeText "sup-screenshots-foot.ini" ''
179 [main]
180 font=Iosevka Term:size=12
181 [colors-dark]
182 # https://github.com/tinted-theming/tinted-terminal/blob/main/themes/foot/base16-pop.ini
183 foreground=d0d0d0
184 background=000000
185 regular0=000000 # black
186 regular1=eb008a # red
187 regular2=37b349 # green
188 regular3=f8ca12 # yellow
189 regular4=0e5a94 # blue
190 regular5=b31e8d # magenta
191 regular6=00aabb # cyan
192 regular7=d0d0d0 # white
193 bright0=505050 # bright black
194 bright1=eb008a # bright red
195 bright2=37b349 # bright green
196 bright3=f8ca12 # bright yellow
197 bright4=0e5a94 # bright blue
198 bright5=b31e8d # bright magenta
199 bright6=00aabb # bright cyan
200 bright7=ffffff # bright white
201 16=f29333
202 17=7a2d00
203 18=202020
204 19=303030
205 20=b0b0b0
206 21=e0e0e0
207 '';
208 screenshots = pkgs.stdenv.mkDerivation {
209 name = "sup-screenshots";
210 nativeBuildInputs = with pkgs; [
211 dbus
212 foot
213 ghostty
214 grim
215 jq
216 sway
217 procps
218 ];
219 env = {
220 LANG = "C.UTF-8";
221 FONTCONFIG_FILE = fonts-conf;
222 WLR_BACKENDS = "headless";
223 WLR_RENDERER = "pixman";
224 LIBGL_ALWAYS_SOFTWARE = "1";
225 WLR_LIBINPUT_NO_DEVICES = "1";
226 };
227 buildCommand = ''
228 export HOME=$(mktemp -d)
229 export XDG_RUNTIME_DIR=$HOME/run
230 mkdir -p $XDG_RUNTIME_DIR
231 export SWAYSOCK=$XDG_RUNTIME_DIR/sway.sock
232 dbus-run-session --config-file ${pkgs.dbus}/share/dbus-1/session.conf sway -c ${sway-conf} &
233 while ! test -S $SWAYSOCK ; do sleep 0.1 ; done
234 export WAYLAND_DISPLAY=wayland-1
235
236 mkdir -p $out
237 for f in ${screenshots-ansi}/* ; do
238 rm -f done
239 foot -c ${foot-conf} -W 120x28 --hold sh -c "echo -ne '\e[H\e[?25l' ; cat $f ; touch done" &
240 foot_pid=$!
241 while ! test -f done ; do sleep 0.1 ; done
242 geometry=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?) | select(.app_id == "foot").geometry | "\(.x),\(.y) \(.width)x\(.height)"')
243 grim -g "$geometry" $out/$(basename ''${f%.ansi}).png
244 kill -TERM $foot_pid
245 wait $foot_pid || :
246 done
247 '';
248 };
249 in
250 screenshots