GTKWave conda-forge (GTK2) draws a blank wave pane on Xvfb; GTK3 build from source + conda-forge xorg-xvfb-server works
Goal: record GTKWave off-screen on a headless Linux server with no root and no system X packages.
What failed: conda-forge gtkwave 3.3.121 is the GTK2 build. On Xvfb (both forwarded over ssh -X to a Mac Xvfb and on a local Xvfb on the server) the wave pane stayed white. There were no trace lines and no black background; only faint antialiased text (time labels, values) showed. The Tcl steps still ran (the time axis followed every zoom), so it was a drawing problem, not a scripting one. None of these changed it: ssh -Y, Xvfb without Composite, copying the example gtkwaverc colours, GDK_NATIVE_WINDOWS=1. Only that one GTKWave version exists on conda-forge.
What worked:
1. An X server without root: conda-forge xorg-xvfb-server (21.1.23) in its own micromamba env. It's native and runs on a RHEL-9-like host. (The older xorg-x11-server-xvfb-cos7-x86_64 CDT package needs libcrypto.so.10/libXfont2/libXdmcp that aren't packaged for x86_64 cos7.) Run it as Xvfb :N -screen 0 1440x900x24 -fbdir <dir> -nolisten tcp -auth <private xauth file with a random MIT-MAGIC-COOKIE-1>.
2. GTKWave's GTK3 tree from the upstream tag (gtkwave3-gtk3/ in the v3.3.116 source), built against a conda-forge env with gtk3, tk, pkg-config, gperf, make, zlib, xz, bzip2. Configure with --enable-gtk3 --with-tcl=$E/lib --with-tk=$E/lib, PKG_CONFIG_PATH into the env, and LDFLAGS with -Wl,-rpath,$E/lib. Gotchas: fontconfig's .pc needs expat installed; glibconfig.h comes only with the glib dev package (the libglib runtime package lacks it); put $(pkg-config --cflags gtk+-3.0) in CPPFLAGS because one bundled helper lib compiles without the GTK flags. That build draws normally on the same Xvfb.
3. Window size without a window manager: in ./.gtkwaverc set initial_window_x/y to the screen size and initial_window_xpos/ypos 0.
Recording: read the -fbdir file (an XWD image) through mmap about 30 times a second, and keep a frame only when the bytes change (zlib level 1). A waveform viewer is mostly static, so a 30 s session came to ~16 frames / 1.3 MB. Convert each frame with ffmpeg -f xwd_pipe -i - and concat with per-frame durations for a true-time video. Step latency resolution is the grab interval (~33 ms).