Maksim Totmin 4c34f0bedc Initial commit: Go driver for the Lian Li Galahad II LCD
Streams H.264 to the Galahad II LCD via USB (gousb) with:
- ffmpeg CLI transcoding (no fragile FFmpeg C-ABI bindings)
- pure-Go Annex-B access-unit splitting
- framerate pacing with graceful shutdown and USB reconnect
- TOML config, cobra CLI, structured slog logging, unit tests
2026-08-19 12:03:54 +07:00

galahad2lcd

A modern Go driver for the Lian Li Galahad II LCD cooler display. It transcodes a video or GIF to H.264 and streams it over USB to the 480×480 LCD, running as a systemd service.

This is a clean-room rewrite of the archived GalahadII_LCD_Linux project. It keeps the exact wire protocol but drops the fragile FFmpeg C-ABI dependency in favour of the system ffmpeg binary, adds structured logging, automatic USB reconnection and a testable, layered architecture.

Features

  • daemon — transcodes media to H.264 and streams it in a loop
  • set — change the input/rotation/speed and restart the service
  • list — enumerate USB devices to diagnose connectivity
  • version — print the build version
  • Automatic reconnect if the USB link is lost (e.g. after suspend)
  • Graceful shutdown on SIGINT/SIGTERM (systemctl stop works cleanly)
  • Structured logs (log/slog) captured by journald
  • Rotation, playback speed and framerate overrides via config or flags

Requirements

  • Go 1.22+ (to build)
  • FFmpeg + FFprobe (any recent version, e.g. sudo pacman -S ffmpeg)
  • libusb (transitive build dependency of github.com/google/gousb)
  • systemd

Install

sudo ./packaging/install.sh /path/to/media.gif

The script builds the binary, installs the systemd unit and udev rule, writes an initial config and starts the service. If no media file is given, configure it afterwards:

sudo galahad2lcd set --input /path/to/media.gif --rotate 0

Configuration

Settings live in /etc/galahad2lcd.toml:

[display]
input = "/home/user/media.gif"
rotate = 0        # 0, 90, 180, 270 (clockwise)
speed = 1.0       # >1 slower, <1 faster

[usb]
vendor_id = 0x0416
product_id = 0x7395

[stream]
fps = 0           # 0 = auto-detect from the source
cache_path = "/tmp/galahad_cache.h264"

The set command updates this file and restarts the service:

sudo galahad2lcd set --input /home/user/media.gif --rotate 90 --speed 1.2
sudo galahad2lcd set --fps 15 --no-restart   # apply later

CLI flags also override the config for one-off runs:

sudo galahad2lcd daemon --input /home/user/media.gif --rotate 180

Diagnostics

galahad2lcd list                # find the LCD on the USB bus (needs udev rule or root)
journalctl -u galahad2lcd -f    # live daemon logs

The bundled udev rule (packaging/99-galahad2lcd.rules) grants the logged-in session user access to the device so list works without root.

Architecture

cmd/galahad2lcd      entry point (composition root)
internal/cli         cobra commands: daemon, set, list, version
internal/config      TOML configuration model, load/save/validate
internal/transcoder  ffmpeg/ffprobe wrapper, filter graph, FPS detection
internal/h264        Annex-B stream → access-unit splitting (pure Go)
internal/stream      frame-rate pacing loop (testable, device-agnostic)
internal/device      Device interface + gousb (libusb) implementation
internal/protocol    USB framing (pure functions, golden-tested)
internal/service     systemd unit management

Layers depend only downward. stream talks to a device.Device interface, so the transport can be swapped or faked in tests without touching the rest of the pipeline.

Development

make build     # build ./bin/galahad2lcd
make test      # unit + integration tests (integration needs ffmpeg)
make vet       # go vet
make fmt       # gofmt
make install   # copy binary to /usr/local/bin

Uninstall

sudo ./packaging/uninstall.sh

License

MIT

S
Description
No description provided
Readme
177 KiB
Languages
Go 93.8%
Shell 5.2%
Makefile 1%