sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/

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