Files
punktfunk/docs/releases/v0.19.1.md
T
enricobuehlerandClaude Opus 4.8 ebddffa4c8
apple / swift (push) Successful in 1m30s
android-screenshots / screenshots (push) Successful in 3m1s
audit / cargo-audit (push) Successful in 2m14s
audit / bun-audit (push) Successful in 15s
ci / web (push) Successful in 1m8s
ci / docs-site (push) Successful in 1m11s
release / apple (push) Successful in 9m36s
android / android (push) Successful in 14m52s
ci / bench (push) Successful in 6m31s
decky / build-publish (push) Successful in 22s
arch / build-publish (push) Successful in 14m40s
apple / screenshots (push) Successful in 6m40s
ci / rust (push) Successful in 19m21s
deb / build-publish-host (push) Successful in 10m36s
deb / build-publish (push) Successful in 9m18s
linux-client-screenshots / screenshots (push) Successful in 7m40s
docker / deploy-docs (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 13s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
web-screenshots / screenshots (push) Successful in 2m59s
windows-host / package (push) Successful in 9m59s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m8s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m58s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m42s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m17s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m43s
flatpak / build-publish (push) Failing after 8m0s
chore(release): bump workspace version to 0.19.1
An all-fixes patch on top of 0.19.0. The headline is session teardown — the
native stop flag made enforceable and bounded (ac3dc432) and GameStream ending
on the control peer's ENet disconnect (41fa25c4), so a session can no longer
outlive the client that started it on either plane.

Also: the Windows input-desktop pair (display writes b5fa878b + pen/touch
injection 24a24734 both surviving a UAC prompt), the Apple display-sleep guard
(bc5f6a38), the gamescope corner-parked cursor (4b2d2d1e), the KWin custom-mode
selection (3a33a694), the tvOS glob warnings (cc8cccbe), and the Linux
starter/env docs revamp (66172753).

Nothing moved on the wire (protocol 2) or at the C ABI (13), so no embedder
rebuild and 0.18/0.19 peers keep mixing freely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 02:15:40 +02:00

11 KiB
Raw Blame History

A maintenance update — nothing changes in how Punktfunk works, and you can update your app and the machine you stream from one at a time. Everything you've already paired stays paired.

This one is entirely about things that used to go wrong, and the big one is stuck sessions: a machine you streamed from could go on believing you were still connected long after you'd closed the app, walked out of Wi-Fi range or put the device to sleep — leaving it busy, refusing the next connection, and in the worst case unable to accept anyone at all until it was restarted. That's fixed on both sides. Also fixed: an admin prompt no longer locks you out of a Windows machine, your Apple device's screen no longer sleeps while you're playing with a controller, the mouse pointer behaves in Steam's Gaming Mode, and phone-shaped screens finally get the high refresh rate they asked for from a KDE Plasma machine.

Fixed: sessions no longer outlive the device that started them

If a session ended in any way other than pressing Disconnect, the machine you were streaming from often never found out. Quitting the app, closing your laptop, losing Wi-Fi, a device running out of battery, or simply switching to another app on a phone or TV box — all of these could leave a session running forever, encoding video and audio to nobody.

That had real consequences, and all of them are fixed:

  • You can reconnect and actually get a picture. A machine that still thought you were connected would accept your next connection and then show you nothing, because it believed it was already streaming to you.
  • Someone else can use the machine. A stuck session made it look busy, so the next person to connect was refused — indefinitely.
  • A machine no longer stops accepting connections entirely. A session that got wedged used to hold onto its slot permanently, and a handful of those would make the machine unreachable until it was restarted. Even the Stop button in the web console couldn't clear one. Stuck sessions are now cleaned up regardless, and the Stop button works.
  • Closing the app on your phone or TV box actually ends the session. On Android and on iPhone, iPad and Apple TV, switching away from Punktfunk now ends the stream instead of quietly keeping it alive in the background. Coming straight back is still quick.
  • Quitting deliberately is heard. Pressing Disconnect now reliably reaches the other machine, rather than looking like a device that vanished.
  • Streaming from Moonlight ends properly too. Moonlight sessions had the same problem — quitting Moonlight left the machine streaming into the void. It now notices a client that has gone away, typically within half a minute.
  • A busy machine tells you it's busy. If it's already at its limit, you now get a clear answer instead of a connection that hangs and eventually times out.

Fixed: an admin prompt on Windows no longer locks you out

If a Windows machine was left showing a User Account Control prompt (the "do you want to allow this app to make changes?" dialog), or was sitting on the lock or sign-in screen, connecting to it broke in two ways. Both are fixed.

  • Connecting works again. Every attempt used to show a black screen and then give up after about a minute, which meant an unattended machine had to be reached some other way just to click one button. A connection now comes up in about three seconds with the prompt still on screen.
  • Pen and touch reach the prompt. Mouse and keyboard already worked, but pen and touch input did nothing — so you could see the prompt but not dismiss it from a tablet. You can now dismiss a Windows admin prompt with an Apple Pencil from an iPad, or with a finger.

Fixed: your screen stays awake on iPhone, iPad, Apple TV and Mac

Playing with a controller, the screen would dim and sleep out from under you mid-session — because a game controller isn't something the operating system counts as "using the device". The display now stays awake for as long as the session lasts, and goes back to normal the moment you disconnect. On a Mac, the screen saver and the idle lock that follows it are held off for the session too. (The Android app has always done this; the Apple apps now match.)

Fixed: the stuck pointer in Steam's Gaming Mode

On a machine streaming from Steam's Gaming Mode — a Steam Deck, or a desktop in the same session — a fragment of the mouse pointer got welded to the bottom-right corner of the stream and stayed there for the rest of the session, while the real pointer went undrawn. It showed up in every game, because a game takes over the pointer.

The stream now follows what the machine itself decides to draw: the pointer appears where it really is, disappears when the machine hides it, and picks up Gaming Mode's own "hide the pointer after a few seconds of stillness" behaviour, which the stream never had before.

Fixed: high refresh rate on phone-shaped screens from KDE Plasma

Streaming from a KDE Plasma desktop to a screen with an unusual width — an iPhone 16 Pro Max, for instance — dropped to 60 Hz, even though the 120 Hz option was sitting right there in the machine's own display settings. Punktfunk was asking for the resolution it wanted, while the desktop had quietly built a mode a few pixels narrower, so the two never matched.

Punktfunk now picks whichever mode the desktop actually created and streams at its real refresh rate. Screens with common widths (1080p, 1440p, 4K) were never affected.

Also fixed: connecting no longer leaves a new leftover entry in your display settings every time. Previously each connection appended one more custom resolution to the list.

Improved: setup guides for Linux desktops

Following a field report, the Linux setup guides no longer tell you to lock Punktfunk to a specific desktop environment. That setting stops the stream from following you when you switch to Steam's Gaming Mode — the stream would end instead of coming with you. The example configuration also no longer hard-codes a user account ID, which broke audio for anyone who wasn't the first user created on the machine.

If you copied a Punktfunk configuration from the docs before this release, it's worth re-checking it — the current guides for KDE, GNOME, Hyprland and Sway are down to a single setting, and the troubleshooting page now has a section for sessions that break right after a config edit.

Under the hood (for developers)

  • Nothing changed on the wire or at the API boundary. The streaming protocol stays at version 2 and the embeddable core library stays at C ABI 13 — no embedder rebuild is required, and 0.18/0.19 hosts and clients still mix freely. The Windows virtual-display driver protocol is unchanged.
  • Native session teardown is now bounded and enforceable. stop was advisory: every teardown (conn.close, the joins, and the RAII drops of the session permit, admission entry and stream marker) sat after the stream thread's await, and the encode loop only checks the flag between iterations — so one unbounded syscall inside an iteration became a permanent zombie holding its semaphore slot and admission entry. The thread now gets STREAM_STOP_GRACE (90 s, past the 40 s capture-rebuild budget) and teardown runs regardless; the thread is detached rather than killed, since Rust cannot cancel a blocking thread. Audio/input joins are bounded too. The session permit is taken after the QUIC handshake rather than before accept(), so a host at its cap still accepts and the client sees a live path instead of a silent dial timeout. New pf-vdisplay proc::{status_within, output_within} kill a child that outruns its budget — kscreen-doctor is a Wayland client of the very compositor it configures and never returns against a wedged KWin.
  • Client-side close actually reaches the wire. conn.close() only queues the frame, and run_pump is the body of a block_on whose runtime is dropped the moment it returns, so a deliberate quit arrived as silence (8 s idle timeout, no quit code). The endpoint is now carried out of the handshake and flushed with wait_idle(). Android tears down on ON_STOP via the existing onDispose path; the Apple .background arm was iOS-only and gated on an opt-in defaulting off while the audio background mode kept the connection alive indefinitely — it now acts unconditionally and covers tvOS.
  • GameStream sessions end on ENet disconnect. The only automatic detector was a media-UDP send error, which needs an ICMP port-unreachable — so a true vanish left both planes encoding forever, and a plain Moonlight quit (no RTSP TEARDOWN, no nvhttp /cancel) leaked the session. The control peer's reliable-ping timeout fires Event::Disconnect within ~530 s and now runs the shared, idempotent AppState::end_session. Peer tracking is gated on the /launch owner's source IP so an unauthenticated LAN peer cannot end a live session and a stale peer cannot kill its successor. Unreachable-client UDP errors end the whole session via an OnSessionLost callback built at PLAY, instead of stopping only the plane that noticed.
  • Windows display and pointer-injection calls now follow the input desktop. SetDisplayConfig/ChangeDisplaySettingsEx and InjectSyntheticPointerInput are rejected with ERROR_ACCESS_DENIED when issued from a thread that isn't on the desktop currently receiving input — Winlogon, whenever UAC or the lock screen is up. Both paths retry once bound to the input desktop, with the binding scoped rather than persistent so a shared thread is never left parked on a desktop that disappears. The retry predicate stays narrow (ERROR_ACCESS_DENIED only) so the unrelated exclusive-mode 0x57 topology failure is not re-issued under a different name.
  • The X11 cursor source honours GAMESCOPE_CURSOR_VISIBLE_FEEDBACK. gamescope hides the pointer by warping it to the root window's bottom-right pixel rather than swapping in a transparent cursor, so XFixesGetCursorImage kept returning the last opaque arrow and the "whichever pointer moved last" heuristic froze on the parked Xwayland. The atom is read at connect and re-read on its root PropertyNotify, with a 250 ms resync; the motion heuristic remains the fallback for a gamescope that publishes no verdict (logged as cursor_feedback=false).
  • KWin custom modes are resolved from the output's own mode list and selected by mode id. libxcvt rounds the requested width down to a multiple of 8 (2868×1320@120 → 2864×1320@119.92), so the WxH@Hz name passed to kscreen-doctor's findMode matched nothing and the select silently no-op'd. set_custom_refresh now returns the achieved mode and it is reported as the output's preferred mode, so the renegotiation gate waits for the geometry KWin will really deliver. addCustomMode is skipped when a usable mode already exists, since kscreen-doctor appends and KWin persists per output name.
  • Apple display-sleep guard. UIApplication.isIdleTimerDisabled on iOS/iPadOS/tvOS; on macOS ProcessInfo.beginActivity(.idleDisplaySleepDisabled) plus a 30 s IOPMAssertionDeclareUserActivity heartbeat, because the screen saver runs off the HID idle timer independently of display sleep. Acquired in beginStreaming, released at the top of disconnect, so every teardown path releases it.
  • The xcframework builds warning-free again. Two redundant super::super::* globs in the client pump warned only on tvOS targets, which CI never builds (that needs BUILD_TVOS=1 and the Tier-3 build-std nightly path).