Skip to content

2026-10-24 — Session Log (raw)

Long mixed session: kitty shaders → JPEG XL → CachyOS upgrade → tblockd repair → Razer LED dead end → repo reviews → built a Hal overlay.

Kitty custom shaders (0.49.2)

  • kitty 0.49 added custom_shaders (slang, single end slot). Shipped set lives in /usr/lib/kitty/kitty/shaders/custom/ — backgrounds (inside-the-matrix, northern-lights, fireworks, fireworks-rockets, water, underwater), mouse (pond-ripple, spotlight), cursor trails (cursor-trail-default/-blaze/-lightning/-motion-blur), navigation (dim-inactive-windows, focus-highlight, tab-change), retro (crt, crt-blue, tft), composable fragments (negative, bloom, sample).
  • Enabled custom_shaders water with a dated backup (kitty.conf.bak-2026-10-22-water).
  • Verdict: too distracting / hurts legibility. Causes: UV_DISPLACEMENT 0.04 resamples and smears glyphs, WATER_COLOR 0.5 × 1.2 washes the whole screen toward grey. Unlike underwater/fireworks it has no luminance gate.
  • Wrote a replacement ~/.config/kitty/shaders/water-bg.{slang,pipeline}: same caustic math, no UV warp, additive ripples gated by luminance (BLACK_BLEND_THRESHOLD 0.35 + softness) so text is untouched; animation gated on os-window-focus-in|user-activity. Compiled through kitty's own builder (parse_pipeline + build_custom_shader_pipeline_glsl, slangc -warnings-as-errors all) → OK, 3216 B vert / 8332 B frag.
  • Shader reload semantics (important): var overrides only work on uncommented lines and must match the shader's static const name+type exactly (a typo fails silently). kitty watches config files, not shaders/, and recompile_if_needed() only rebuilds when the custom_shaders option changes or the force flag is armed (armed after the check by load_config_file) — so a restart is the reliable reload, or touch kitty.conf twice.
  • Reverted to the backup; kept the shader files + kitty.conf.shaders-water-bg for later.

JPEG XL

  • Their ~/Pictures/wall/3000.jxl (1020×510, lossy, 8-bit sRGB, 43 KB) — native JXL, not a JPEG rewrap. Tooling present: magick (libjxl 0.12.0 rw), djxl/cjxl/jxlinfo, ffmpeg/mpv (libjxl decoders). No gdk-pixbuf JXL loader → eog can't open it; loupe/glycin likely can.
  • magick 3000.jxl → png took 0.26 s (43 KB → 544 KB). Noctalia handles JXL for wallpaper, so no conversion needed.

CachyOS upgrade

  • checkupdates (fresh sync) → 55 repo + 4 AUR; local DBs were 2 days stale.
  • Notable: kernel 7.2.11 → 7.3.0, linux-firmware* 20260916→20261019, mesa 26.2.4→26.2.5, pipewire 1.6.9→1.6.10, mutter 51.1, nss/ca-certificates, cachyos-hooks big jump, opencode 2.1.3, yt-dlp. AUR: google-chrome 156, vscode-bin 1.143.1, pyprland, hyprwhspr.
  • Only DKMS module on the box was openrazer-driver/3.12.4 (7.2.11) — the one thing a kernel bump could break. Removed the whole openrazer stack (openrazer-daemon, openrazer-driver-dkms, python-openrazer, razercommander; daemon had been disabled and razercommander never configured) → dkms status empty → plain upgrade, then reboot.
  • Post-reboot verification: 7.3.0-2 running, 0 pending, dkms empty, no razer pkgs, razermouse not loaded, one failed unit (tblockd).

pacnew cleanup

  • /etc/locale.gen.pacnew → keep current (pacnew enables no locales; adopting would wipe en_AU/en_US).
  • /etc/pacman.d/mirrorlist.pacnew → keep current (CachyOS-generated list vs stock Arch).
  • Both tpm2-tss/fapi-profiles/*.json.pacnew → adopt (upstream data).

tblockd repair (broken since 2026-10-16)

  • Symptom: ModuleNotFoundError: No module named 'tblock'. Cause: package installed into /usr/lib/python3.13/site-packages, system python is 3.14.8 (python-autoeq was fine — it is in 3.14).
  • paru -S tblock tblock-gui did nothing — "is up to date — reinstall? [y/N]" defaults to No, so no transaction ran (pacman.log showed only rebuild-detector chatter).
  • --rebuild then failed on sha512 mismatch: Codeberg regenerated its archive tarballs (top dir also changed from tblock-2.7.5/ to tblock/; the PKGBUILD's cd "$srcdir/$pkgname" still matches). Verified content was genuine (gzip OK, VERSION = "2.7.5", Makefile/pyproject present, AUR tags confirm 2.7.5 is current stable) → built with paru -S --rebuild --mflags "--skipchecksums" tblock tblock-gui.
  • Then the daemon failed three ways in sequence:
  • User is not a member of the openrazer style group — actually openrazer group membership was missing → sudo gpasswd -a dini openrazer (needs re-login).
  • systemctl restart SIGKILLed it after TimeoutStopUSec=10s while it rebuilt its DB → stale /run/tblockd.pid → "instance already running". Fixed by removing the pidfile and adding a drop-in (TimeoutStopSec=90, Restart=on-failure, RestartSec=5).
  • sudo tblock -B -n && sudo tblock -E -n → protection active, /etc/hosts = 3,526,030 lines.
  • Verified: googleadservices.com and adsbygoogle.js → "could not resolve" (000); example.com 200. Note getent returns nothing (not 0.0.0.0) because nsswitch runs resolve first and nss-resolve drops 0.0.0.0 → surfaces as EAI_NODATA. Entry is present in /etc/hosts; cosmetic.
  • Cost: tblockd ~338 MB + systemd-resolved ~346 MB for 3.5 M rules. User has RAM, so kept.

Razer DeathAdder Essential — the LED dead end

  • Device 1532:0098. OpenRGB 1.1: CLI -m/-c is a no-op — verified in a 3973-line -vv trace and against a live server on :6742; no SetMode/SetColor ever reaches the device. GUI works but setting Direct/Static just left the logo black.
  • Reinstalled OpenRazer (daemon was disabled before — that's why it "never worked"). Rebuild gave sysfs attrs: logo_led_brightness, logo_matrix_effect_static/breath/none, device_mode. Writes worked (LED went black) but colour never applied.
  • device_mode is a binary 2-byte attribute (buf[0]=mode, buf[1]=param); echo 3 sent ASCII 0x33 0x0A and was rejected → extended colour packets ignored. Correct write is printf "\003\000". Confirmed od → 03 00.
  • Still black after driver mode + \377\000\000. Answer: the DeathAdder Essential has a single-colour green LED (Razer spec sheet: "Single-color green lighting"; Razer Insider confirms colour cannot change). Firmware implements the RGB protocol, but only a green emitter exists → red/blue bytes = off; green bytes would light it.
  • Decision: remove openrazer + openrgb again (LED is green-only, nothing to gain, and it removes the DKMS module). Keep the hand-made /etc/udev/rules.d/99-razer-no-autosuspend.rules (unowned by any package, still useful).
  • Side buttons: BTN_SIDE 275 / BTN_EXTRA 276 (decoded from the kernel bitmap), no onboard remap possible; options are Hyprland mouse:275 binds or input-remapper (AUR). User decided to leave them as back/forward for now.

Repo reviews

  • koala73/worldmonitor — 89 k stars in 9.5 months, 13.7 k forks, AGPL-3.0, TS+JS ≈ 80 MB, 283 MB repo, ~150 npm scripts, custom security lint rules, e2e shards, Tauri desktop, MCP server. Assessment: real engineering discipline, but bus factor 1 (7962 commits by koala73 vs 84 next; bots claude/cursoragent contributing), scale far beyond one person's comprehension, freemium product with OSS core, stars = marketing curve. Read it for the lint-as-guardrail pattern; treat output as leads to verify, not conclusions.
  • PLAYER99-void/void — voice-first Claude Code companion (16 stars, MIT, 12 days old). Linux overlay = GTK + WebKit + layer shell; brain = claude -p --output-format stream-json; ears/mouth = faster-whisper + Kokoro. Inspired the overlay build below.

Built: ~/Projects/hal-overlay

  • Keyboard-only top-centre notch driving opencode, no voice.
  • Stack: GTK4 + WebKit 6.0 (webkitgtk-6.0, not webkit2gtk-4.1 which is GTK3) + gtk4-layer-shell; face is face.html with the Hal eye as inline SVG + CSS states.
  • Gotchas found:
  • LD_PRELOAD=/usr/lib/libgtk4-layer-shell.so is mandatory or the surface silently becomes a normal toplevel ("GTK4 Layer Shell may have been linked after libwayland"). Stripped from the child env before spawning opencode.
  • GTK4 has no Gtk.main() → GLib.MainLoop().
  • Hyprland places top-anchored layer surfaces 48 px down; HAL_TOP_MARGIN=-48 puts it at y=0.
  • webkit2gtk-4.1 + GTK4 = version conflict; use WebKit 6.0.
  • Agent: opencode run --format json --auto [--session sid] <prompt>, NDJSON parsed line by line; text events stream into the transcript, tool events become chips, sessionID reused.
  • Flags: default toggle, --show, --hide, --start-hidden (autostart dormant), --quit; single instance via $XDG_RUNTIME_DIR/hal-overlay.sock; appears on the focused monitor.
  • Verified end-to-end: layer at 920 0 720 128, wtype typed a prompt, OCR of a grim crop showed HAL / ❯ reply with exactly: HAL ONLINE / HAL ONLINE.
  • Not done: keybind (offered), approval bubble (--auto hardcoded), history, model switch, theme sync.

Tooling notes / mistakes

  • Interactive shell is fish: no heredocs (<<EOF → "Expected a string, but found a redirection"), unmatched globs are hard errors ("No matches for wildcard"). Use sudo sh -c 'printf ... > file' and fish nullglob (N), e.g. ls /dev/razer*(N).
  • Two self-inflicted errors worth remembering: pkill -f <pattern> matched my own bash -c command line and killed the shell (twice); pkill -x python3 was dangerously broad. Kill by PID from hyprctl layers / ss -xlp instead.
  • https://github.com/koala73/worldmonitor
  • https://github.com/PLAYER99-void/void
  • https://github.com/wmww/gtk4-layer-shell/blob/main/linking.md
  • https://gitlab.com/CalcProgrammer1/OpenRGB
  • https://github.com/openrazer/openrazer