Skip to content

Repository files navigation

Glasshouse

A native Linux Qt 6 desktop client for PiKVM. Presents N PiKVMs as N independently-movable, resizable windows on a KDE desktop, replacing the pattern of N Chrome tabs pointed at N PiKVM web UIs. One PiKVM acts as the USB HID master for a shared target host; the others are video-only. A single local keyboard and mouse are shared between the windows via session-wide click-to-capture.

The full architecture, empirically-verified coordinate model, and the list of PiKVM API quirks Glasshouse works around live in specs/DESIGN.md. Release-by-release changes live in CHANGELOG.md.

Status

v0.1.3 — current. First public release was v0.1.0 in April 2026; three patch releases since landed input fixes surfaced by daily use: shifted-symbol keys (?, (, ), …) routed via the physical-key MDN code, mouse-wheel sub-notch accumulation for high-resolution wheels and Wayland touchpads, and an ergonomic release-hotkey default (Ctrl+Alt+Shift+Backspace). All Phase 0–8 work in DESIGN.md §9 is complete. The viewer is in daily use against a live two-PiKVM (PiKVM 4 Plus + PiKVM 3) setup.

What works end-to-end today:

  • One independently-movable, resizable Qt window per configured PiKVM.
  • MJPEG transport (/streamer/stream) — recommended for any long-running session. Leak-free, works on every PiKVM model.
  • WebRTC over Janus (/janus/ws, plugin janus.plugin.ustreamer) with hardware H.264 decode preferred (nvh264dec / vah264dec), avdec_h264 fallback. Note: the underlying webrtcbin element has a long-running memory leak (~8 MB/s/stream); only suitable for short sessions or until you can restart the viewer. See DESIGN.md §10.5 for the investigation. MJPEG is the path to use in production for now.
  • Session-wide click-to-capture: cursor walks continuously across windows, with the active coordinate transform following it.
  • Mouse, keyboard, scroll wheel, and curated/custom shortcut chords routed through one configured HID master to the target.
  • Special Keys palette (Ctrl+Alt+K) for chords the local compositor swallows (PrintScreen, Super-key combos, …) plus paste-as-keystrokes for picky BIOS prompts.
  • Mass Storage upload / mount / eject / present-as-CDROM.
  • Target ATX power short-press, long-press (force-off), reset — each gated by a confirmation dialog.
  • Per-PiKVM window geometry / fullscreen / maximized state persists across sessions.
  • .deb packaging built by GitHub Actions on tag push, linked against system Qt 6.4 from the noble apt archive (no bundled Qt).

Deliberately out of scope for the 0.1.x line: clipboard sync, audio, macro keypad daemon. See CHANGELOG.md for the full list.

Install (from a release .deb)

Tagged releases publish a .deb to the Releases page. Download glasshouse-viewer_<version>_amd64.deb and install with:

sudo apt install ./glasshouse-viewer_<version>_amd64.deb

apt install (vs. dpkg -i) resolves Qt 6, GStreamer, and libnice runtime dependencies from the stock Ubuntu 24.04 archive in one step. No per-user Qt setup required.

The package drops an example config at /etc/xdg/glasshouse/glasshouse-viewer.example.yaml — copy it to ~/.config/glasshouse/config.yaml and edit:

mkdir -p ~/.config/glasshouse
cp /etc/xdg/glasshouse/glasshouse-viewer.example.yaml \
   ~/.config/glasshouse/config.yaml
$EDITOR ~/.config/glasshouse/config.yaml

The schema is documented in DESIGN.md §8; the validator enforces the rules in §8.1 at startup.

Configure secrets

PiKVM passwords (and optional TOTP seeds) live outside the config file. The config refers to them by secret://<name> references which resolve in this order:

  1. Environment variable GLASSHOUSE_SECRET_<NAME> — uppercased, non-alphanumerics replaced with _. secret://pikvm-1-passwd → GLASSHOUSE_SECRET_PIKVM_1_PASSWD.
  2. ~/.config/glasshouse/secrets.yaml — YAML map, used when the env var is unset. This is the path that survives KDE-menu / .desktop launches where shell rc files don't get sourced; prefer it for desktop launches.

Either form works on its own. Env wins when both are set.

mkdir -p ~/.config/glasshouse
cat > ~/.config/glasshouse/secrets.yaml <<'EOF'
pikvm-71-passwd:  hunter2
pikvm-144-passwd: hunter2
# pikvm-71-totp:    JBSWY3DPEHPK3PXP   # optional base32 TOTP seed
EOF
chmod 0600 ~/.config/glasshouse/secrets.yaml

The viewer warns at startup if the file's permissions allow group or other access. KWallet / libsecret integration is a future enhancement.

Run

# Bring up every window in the config.
glasshouse-viewer

# Bring up only one window (e.g. for ad-hoc testing).
glasshouse-viewer --only 192.168.1.71

# Append all log messages to a file in addition to stderr. Honours
# QT_LOGGING_RULES — pair with one to dump verbose categories.
glasshouse-viewer --log-file /tmp/glasshouse.log
QT_LOGGING_RULES='glasshouse.video.debug=true' \
    glasshouse-viewer --log-file /tmp/glasshouse.log

Default hotkeys (configurable per-PiKVM in YAML):

Hotkey Action
Ctrl+Alt+Shift+Backspace Release session-wide capture
F11 Toggle fullscreen on this window
Ctrl+Alt+K Show / hide Special Keys palette

Prep on the PiKVMs

  • HID master (the one named in hid_master: in the config): USB OTG cable plugged to the target host; mouse must be in absolute mode. Flip it via the web UI (System → Mouse) or:

    curl -k -X POST -u admin:PASS \
      'https://<hid-master>/api/hid/set_params?mouse_output=usb'
  • Video-only PiKVMs: HID gadget disabled in /etc/kvmd/override.yaml (see DESIGN.md §4.3) — or simply leave the OTG cable unplugged. Same effect.

No PiKVM firmware modifications are required. Glasshouse works against stock kvmd / ustreamer; cold-start quirks (encoder warmup, older- firmware pre-IDR 503s) are handled client-side.

Build from source

Build dependencies (apt)

sudo apt install \
    cmake ninja-build pkg-config \
    qt6-base-dev qt6-multimedia-dev qt6-websockets-dev \
    libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
    libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-bad \
    gstreamer1.0-nice gstreamer1.0-libav libnice-dev
Dependency Version Notes
OS Ubuntu / Kubuntu 24.04+ other distros should work; only tested on 24.04
Compiler GCC 13.3+ C++20 required
CMake 3.22+
Qt 6.4+ system Qt from qt6-* apt packages is enough; nothing post-6.4 is in use
yaml-cpp 0.8.0 vendored via FetchContent — no system package

Configure and build

cmake --preset default
cmake --build build -j
ctest --test-dir build --output-on-failure

Release build:

cmake --preset release
cmake --build build-release -j

The first configure downloads yaml-cpp 0.8.0 from GitHub via FetchContent. Subsequent builds use the cached source.

Run from the build tree

./build/bin/glasshouse-viewer
./build/bin/glasshouse-viewer --only <host>

# Phase 1 CLI harness — state-WS smoke driver, no video, no Qt windows.
./build/bin/glasshouse-cli

# Janus signalling probe — drives info → create → attach → watch and
# logs the SDP offer; useful when diagnosing transport failures.
./build/bin/glasshouse-janusprobe --host <pikvm>

Project layout

glasshouse/
├── CMakeLists.txt              top-level build; declares yaml-cpp via FetchContent
├── CMakePresets.json           `default` = Debug in ./build; `release` in ./build-release
├── CHANGELOG.md                release-by-release changes
├── README.md                   you are here
├── CONTRIBUTING.md             how to file issues, send patches, cut releases
├── SECURITY.md                 how to report security-affecting bugs
├── LICENSE                     LGPL-3.0 (top-level for GitHub auto-detection)
├── LICENSES/                   LGPL-3.0 + MDI icon NOTICE
├── specs/DESIGN.md             architectural source of truth
├── pikvm_coord_verify.py       Phase 0 coord-space regression script
├── config/example.yaml         reference config matching DESIGN.md §8
├── dist/
│   ├── nfpm.yaml               .deb metadata; consumed by the Release workflow
│   ├── glasshouse-viewer.desktop
│   ├── glasshouse-viewer.svg
│   └── glasshouse-viewer.example.yaml
├── .github/workflows/          GH Actions: build + tag-driven release
├── src/core/                   auth + state WS + reconnect + secrets + config + logging
├── src/video/                  Janus client, GStreamer pipelines, VideoWindow,
│                               Special Keys & Mass Storage dialogs
├── src/input/                  CoordTransform, InputRouter, MDN keymap
├── src/cli/                    glasshouse-cli (Phase 1 harness)
├── src/viewer/                 glasshouse-viewer (the GUI; ties everything together)
└── tests/                      QTest: config validator, coord transform, keymap

Troubleshooting

config load failed: ... — the validator rejected the config. Read the error list; it names the specific §8.1 rule that was violated.

login failed: Host requires authentication — wrong user / password, or the secret didn't resolve. Check that GLASSHOUSE_SECRET_<NAME> matches what the loader expects, or that the entry in ~/.config/glasshouse/secrets.yaml matches the secret://<name> reference. The loader logs the expected env-var name when a secret can't be resolved.

Blank video, ICE reaches connected, no pad-added — usually means the PiKVM's H.264 encoder isn't running. Glasshouse opens the state WS with ?stream=1 to wake the encoder; if you've patched a fork to use ?stream=0, the encoder won't spawn until something else watches. See DESIGN.md §10.5.

Older PiKVM 3 returns 503 "Haven't received SPS/PPS from memsink yet" — expected on the first watch; JanusClient retries automatically (up to 5 attempts, 2 s apart) until the encoder produces its first IDR. If retries exhaust, the outer reconnect ladder takes over.

[host] error: SSL handshake failed — the example config sets insecure_tls: true per-PiKVM because PiKVMs ship self-signed certs. If you disabled that opt-in, you need to install the PiKVM's CA locally or re-enable the flag.

Verbose logs — every subsystem has its own Q_LOGGING_CATEGORY so you can isolate noise:

QT_LOGGING_RULES='glasshouse.janus.debug=true;glasshouse.video.debug=true' \
    glasshouse-viewer

Categories: glasshouse.{pikvm,ws,hid,video,janus,config}.

Phase 0 regression

pikvm_coord_verify.py walks the PiKVM absolute coordinate space with a deterministic probe sequence so cursor placement on the target can be observed via libinput debug-events. Re-run after any PiKVM firmware upgrade:

pip install requests
python3 pikvm_coord_verify.py --host <pikvm> --user admin

Results are recorded in DESIGN.md §10.1.

Contributing

See CONTRIBUTING.md for build/test, where to file issues, conventions, and how releases are cut. Three load-bearing rules summarised here:

  • Empirical verifications matter. When implementation contradicts the design (latency, API behaviour, Qt quirks, PiKVM firmware surprises), update specs/DESIGN.md §10 in the same commit that reveals it.
  • Tests for pure logic. Pure-function code — config validation, coord transform, keymap — must have QTest coverage in tests/. Network-heavy behaviour is exercised by the harnesses against real hardware.
  • Don't modify the PiKVM. Glasshouse runs against stock firmware; workarounds for firmware quirks live client-side.

Security-affecting bugs go via SECURITY.md — please don't open public issues for those.

Design and references

License

Glasshouse is licensed under LGPL-3.0-or-later, matching the dynamic Qt 6 linkage requirement (Qt 6 is LGPL v3 with The Qt Company's GPLv3 exception). Full text lives in LICENSE at the repo root and at LICENSES/LGPL-3.0.txt.

The MDI greenhouse glyph used as the application icon is distributed under the Pictogrammers Free License — see LICENSES/material-design-icons-NOTICE.txt.

About

A native Linux Qt 6 desktop client for PiKVM.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages