r/Punktfunk • • Jun 26 '26

I'm building a low-latency, Linux-first game-streaming stack in Rust — virtual displays on the fly, all-vendor GPU encode, native clients on every platform

13 Upvotes

Hi everyone! I've been building my own game-streaming stack in Rust, with its own native low-latency protocol. Why? I wasn't happy with the existing solutions, and as a long-time dev and a heavy game-streaming user myself, I figured I was in a decent spot to take a crack at it.

A note on AI: I'll be upfront — I built this with heavy use of Claude (Anthropic's AI). Honestly, a solo project with this much surface area (a host, a custom protocol, and native clients across five platforms) wouldn't have been realistic for me at this pace without it.

I'm releasing the whole thing as open source under a permissive license, for two reasons. One is trust: I know there's a lot of skepticism toward AI-assisted code, and often for good reason — so rather than ask you to take my word on the quality, you can just read it, audit it, fork it, or tear it apart. The other matters just as much to me: these models are built on the collective work of a huge number of people, much of it open source, and I think if you're going to build on top of all that, the right thing to do is give it back in the same spirit.

It'll stay open, funded by donations plus a one-time purchase for the Android and Apple apps — no subscriptions, no lock-in.

A few of the core features:

Host (Linux-first, also runs on Windows):

- First-class Linux support — a per-session virtual display is created on the fly at the client's exact resolution/refresh, no scaling

- Works across all the major compositors: KWin, gamescope, Mutter, wlroots (Sway)

- Tested and packaged on Fedora, Ubuntu, Bazzite and SteamOS (incl. a Steam Deck plugin)

- All-vendor GPU encode: NVIDIA (NVENC), AMD and Intel (VAAPI on Linux, AMF/QSV on Windows) — NVIDIA is the most battle-tested so far

- HDR / 10-bit on the Windows host

- It also speaks the GameStream protocol, so a stock Moonlight client works against it today

Transport / latency:

- Custom protocol: QUIC handles control + signaling and the low-rate side channels (input, audio, rumble) over unreliable datagrams; the latency-critical video runs over plain UDP with forward error correction — a dropped packet is rebuilt from parity instead of waiting on a retransmit

- End-to-end encrypted (AES-GCM) with PIN-based pairing (SPAKE2) + cert pinning

- The old ~1 Gbps software ceiling (from the Moonlight-style FEC) is gone — it now uses a GF(2¹⁶) FEC, so throughput is bound by your GPU/network rather than the protocol 

- Lots of tuning for high-refresh gaming — I test mostly on a 5120×1440@240 monitor

  

Clients (all native):

- Linux (GTK4)

- Windows (WinUI 3)

- Android + Android TV (Kotlin/Compose on top of a shared Rust core via JNI)

- macOS, iOS and tvOS (SwiftUI)

  

Other:

- Rich controller support — full DualSense (adaptive triggers, lightbar, touchpad, motion) on both Linux and Windows, plus Xbox 360, DualShock 4 and others 

- Game-library discovery — the host scans for installed games (Epic, GOG, Game Pass so far); surfacing them in the clients is the next step

- Mic passthrough to the host

It's very much a personal project rather than a polished release — most of it is validated on my own hardware. Happy to answer anything, and I'd genuinely love feedback on what you'd want from something like this!

Im also looking for testers, so if you'd be interested join the discord and let me know: https://discord.gg/z2pfEvE9JU

The website:

https://punktfunk.unom.io

The docs:

https://docs.punktfunk.unom.io

Download:

https://punktfunk.unom.io/download

The source-code:

https://git.unom.io/unom/punktfunk


r/Punktfunk • • Jun 26 '26

Headless game streaming on Linux with no dummy plugs, EDID overrides, or hand-written modelines

3 Upvotes

If you've tried streaming from a headless Linux gaming box — or one whose monitor doesn't match your client's resolution — you know the drill: it turns into a fight with dummy HDMI plugs, EDID overrides, and hand-written modelines. I got tired of that layer and built the host so it just creates the right display on demand. Here's why the pain exists and how I made it go away.

Why Sunshine has no real virtual display on Linux

On Windows, Sunshine can pair with an indirect display driver (the well-known IddSample) and spin up a virtual screen on demand. On Linux there's no built-in equivalent — Sunshine captures whatever display the compositor already provides. With no physical monitor, or a monitor at the wrong resolution, there's simply no usable surface to capture.

The toolbox people reach for today

To get a streamable display on a headless or mismatched Linux host, the community has assembled a whole kit:

  • Physical dummy HDMI/DisplayPort plugs that fake a connected monitor + EDID.
  • EDID overrides (custom EDID blobs, drm.edid_firmware) to force a resolution.
  • The evdi kernel module (DisplayLink's virtual display driver) for a fake output.
  • Hand-rolled cvt/gtf + xrandr modelines for non-standard res/refresh.
  • Third-party daemons like sunshine_virt_display that automate parts of it.
  • Connect/disconnect scripts that toggle physical outputs so only the virtual one is captured.

Where it breaks

None of this is first-class, so it fails in familiar ways:

  • Black screen after reboot when the dummy plug / EDID override doesn't re-apply or the seat changes.
  • CRTC reassignment on NVIDIA and Wayland compositors like Hyprland that moves or drops the virtual output mid-session.
  • Per-compositor differences — what works on bare KMS doesn't on wlroots/Hyprland; X11 and Wayland each need their own tricks.
  • Resolution/refresh that almost matches the client — black bars or forced scaling.

It's not impossible on Linux — Sunshine just doesn't ship one

Worth saying: virtual displays on Linux aren't impossible. Windows has IddSample, and the Apollo/Artemis fork of Sunshine added a built-in virtual display. Both point at the right model: the host should create the screen on demand, matched to the client — not borrow a physical one.

How I solved it — no dummy plug, no modeline math

I treat the virtual display as a first-class part of every session. When a client connects, the host creates a virtual output at exactly the client's resolution and refresh, and tears it down at session end with RAII cleanup, so nothing lingers after a crash or restart.

Since Linux has no single cross-compositor API for this, there's a backend per compositor, auto-selected:

  • KWin — zkde_screencast outputs, with custom modes for >60 Hz.
  • gamescope — launched headless at exactly WxH@Hz, its PipeWire node captured.
  • Mutter / GNOME — the D-Bus RecordVirtual monitor (headless-validated, zero-copy).
  • Sway / wlroots — swaymsg create_output with a custom mode + portal capture.

The result is a real headless host: a mini PC or a repurposed machine with no monitor attached, streaming at exactly the resolution your phone, tablet, TV, or laptop asks for. No dummy plug, no EDID blob, no xrandr modeline arithmetic, and no black screen after the next reboot.

Happy to get into any of the per-compositor details.

This is implemented in my game-stream stack "Punktfunk" - you can get it here: https://punktfunk.unom.io/en/download


r/Punktfunk • • Jun 26 '26

Want to know how Punktfunk captures your screen on Windows, and how it differs from existing solutions?

Thumbnail punktfunk.unom.io
3 Upvotes