Files
enricobuehler b2146f33fe
ci / rust (pull_request) Successful in 6m40s
ci / web (pull_request) Successful in 1m11s
ci / docs-site (pull_request) Successful in 2m13s
ci / bun-nix (pull_request) Successful in 46s
apple / swift (pull_request) Successful in 2m5s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 1m58s
fix(apple): the app menu ate ⌘Q, so the host's compositor never saw the chord
With the default modifier layout ⌘ is Super on the host, which makes ⌘Q the chord
a Hyprland/KDE/GNOME user reaches for first. AppKit dispatches menu key
equivalents before the stream view ever sees a keyDown, so it quit the client
instead. Not Hyprland-specific.

`InputCapture`'s local keyDown monitor now claims every ⌘ chord while input is
captured and forwards it to the host itself. It has to send from there: the
monitor runs ahead of BOTH the menu and `StreamLayerView.keyDown`, and on macOS
that second one is the host's only key path (the GCKeyboard send has been
iOS-only since e414ec0) — so returning nil to keep the menu out takes the host's
copy with it. The file's own comment said the opposite, that swallowing keys
"risks starving GC's own delivery"; on macOS there is no GC delivery to starve,
which is why this could never have been a one-line `return nil`.

Verified against AppKit rather than assumed: a standalone harness posting a
synthetic ⌘Q confirms the monitor sees it first and that returning nil stops the
menu item firing, with a passed-through ⌘W as the control.

⌘⎋ and ⌃⌘F stay client-side whatever the setting says — forward those and a
captured stream is a room with no door. ⌘Tab, ⌘Space and Mission Control are out
of reach for a local monitor: macOS claims them before any app sees them, and
catching them needs a CGEventTap and an Accessibility prompt, which is a product
decision rather than a code one.

This answers to the cross-client "Capture system shortcuts"
(`Settings::inhibit_shortcuts`), which the Apple client had no answer to because
SDL's keyboard grab is what implements it everywhere else. Default on,
profileable like its siblings, and — matching the SDL clients — inert under the
desktop mouse model, which is something you ⌘Tab away from.

Two adjacent defects fixed on the way, both the same root cause. macOS stops
delivering keyUp while ⌘ is held, so a forwarded chord key is released when the
last ⌘ comes up rather than waiting for an up that may never arrive, and the
one-shot `suppressedVK` latch is cleared in the same place — left pending (⌃⌘F's
F, ⌘⎋'s Esc) it would go on to eat the next press of that key. Chord matching
also stopped comparing the raw `deviceIndependentFlagsMask`, which carries Caps
Lock and the arrows' .function/.numericPad bits: with Caps Lock on, ⌘⎋ and
⌃⌥⇧Q — both escape hatches — were not recognized at all.

On glass still owed: a clean compile proves nothing for an input grab.
2026-08-14 22:04:30 +02:00

16 KiB

title, description
title description
Mouse, touch and pen The in-stream keyboard shortcuts that give your mouse back, the two mouse modes, the three touch modes, and full-fidelity stylus input.

A stream takes your mouse and keyboard the moment you click into it. This page starts with how to get them back, then covers driving the host with a mouse, a touchscreen and a pen. The rows that pick these modes sit in your client's Input settings; the toggles that share that page with them are in Client settings.

Getting your input back

On the Linux and Windows clients the stream runs in its own session window. Input is captured when the stream starts and whenever you click the video: your local cursor disappears and keys go to the host instead of your desktop. In the default mouse mode the pointer is also locked to the window — see Mouse modes below.

Shortcut What it does
Ctrl+Alt+Shift+Q Release captured input (press again, or click the stream, to take it back)
Ctrl+Alt+Shift+M Switch the mouse mode (capture ⇄ desktop)
Ctrl+Alt+Shift+D Disconnect
Ctrl+Alt+Shift+S Cycle the stats overlay — off · compact · normal · detailed
Ctrl+Alt+Shift+V Mute or unmute your microphone
F11 or Alt+Enter Toggle fullscreen

While input is released the session window prints the shortest list over the stream:

Click the stream to capture input · Ctrl+Alt+Shift+Q releases · Ctrl+Alt+Shift+M mouse mode ·
Ctrl+Alt+Shift+D disconnects · Ctrl+Alt+Shift+S stats

With a controller in use the same hint names the controller chord instead of the mouse-mode and stats entries. The full list is always available without a stream running — see below.

Muting your microphone

Ctrl+Alt+Shift+V stops sending your microphone to the host, and pressing it again resumes. The uplink itself keeps running underneath, so unmuting is instant rather than a second of the device warming back up.

While you are muted a Microphone muted badge sits in the top-right corner of the stream. It is deliberately separate from the stats overlay: it shows even with stats off, because "am I still muted?" is a question you ask ten minutes later.

The mute lasts for that stream only — the next session starts unmuted, and nothing is written to your settings. If the stream isn't sending a microphone at all (Stream microphone off in client settings) the shortcut does nothing and no badge appears, rather than pretending to mute something.

This is on the Linux and Windows clients — including a Steam Deck stream, which is the Linux client, so an attached keyboard gets the chord. The Apple and Android clients have no mute shortcut yet; turn Stream microphone off in their settings instead.

Alt-Tabbing away releases input on its own and takes it back when you return. A release you asked for with the chord stays released until you opt back in. Either way, keys and buttons you were holding are released on the host, so nothing sticks down.

You can look the shortcuts up again without a stream running: the Linux client has Keyboard Shortcuts in its main menu, and the Windows client has a Shortcuts screen reached from its host list. Both list the microphone mute; the in-stream hint over the video doesn't, to keep it to one readable line.

On the other clients

  • macOS honours the release, mouse-mode, disconnect and stats combos, written ⌃⌥⇧Q / M / D / S — but not the microphone mute. ⌘⎋ also toggles capture, ⌃⌘F toggles fullscreen, and ⌃⌥⇧C starts or stops clipboard sharing. The Stream menu lists them all except the mouse-mode combo, which works but has no menu item. Every other ⌘ chord goes to the host while input is captured — ⌘Q reaches the host's compositor rather than quitting the app — unless you turn Capture system shortcuts off in client settings. ⌘⎋ and ⌃⌘F are held back either way, so there is always a way out.
  • iPhone and iPad with a hardware keyboard: ⌃⌥⇧Q releases input while it is captured, and ⌘⎋ toggles capture in either direction. ⌃⌥⇧D (disconnect) and ⌃⌥⇧S (stats) come from the app's Stream shortcuts rather than from the stream itself; if they don't respond while you're captured, release first or use the on-screen controls.
  • Android and Android TV honour Ctrl+Alt+Shift+Q only; it toggles pointer capture. The system Back button leaves the stream.
  • Apple TV has no keyboard path, and a short press of the Siri Remote's Back button deliberately does nothing — so a controller's B button can't end your session by accident. To leave, hold Back for about a second and let go. During a session the remote's touch surface drives the host cursor, a press is a left click, and Play/Pause is a right click — hold Play/Pause instead and it cycles the stats overlay. With a controller in hand, Select + X does the same on every Apple client.

Leaving with a controller

Every client reserves one controller chord: L1 + R1 + Start + Select (LB + RB + Start + Back on an Xbox pad), held on any connected pad.

  • Linux, Windows — a press releases captured input, and leaves fullscreen if you didn't start fullscreen. Keep it held about 1.5 seconds and it disconnects.
  • Steam Deck — a press releases captured input only. The Decky plugin always launches the client fullscreen, and a stream that started fullscreen stays that way. Holding disconnects, as above.
  • macOS, iPhone/iPad, Apple TV — holding it about 1.5 seconds disconnects. There is no quick-press step.
  • Android — holding it about a second disconnects. A quick press does nothing; the moment the chord completes a Hold to quit… cue appears so you know it registered.

The chord is read off the pads a client forwards, so turning Forward controllers off takes it away on Linux and Windows — there the client stops opening the controller at all, which is the point of the setting. Use Ctrl+Alt+Shift+D or the client's own UI to leave instead. The Apple and Android apps keep watching for the chord either way.

Statistics with a controller

The Apple apps reserve a second chord: Select + X, which cycles the stats overlay one level each time you complete it. It is for the moment your hands are on a controller and the usual routes aren't — no keyboard for ⌃⌥⇧S, no free screen for the three-finger tap — and on Apple TV it is the only way there with a pad. X is deliberately none of the four leave-chord buttons, so reaching for one chord never trips the other. Both buttons still reach the game; only the overlay changes locally.

On the Siri Remote, hold Play/Pause for about half a second instead. A quick tap of that button is still a right click — the click is simply sent when you let go, so the hold has something to be.

The guide button (Xbox / PS / Steam) and Quick Access

A controller's guide button — the Xbox logo, the PS button, the Deck's Steam button — is meant to open menus on the host: the Steam overlay, or a Gaming-Mode host's Steam menu. Some devices want that button for themselves, so every client also carries a gesture that works everywhere:

Hold Select (Back / View) on its own for about a third of a second. The host sees its guide button go down, and it stays down for as long as you hold — so keeping it held reads as a long press on the host, which is how SteamOS opens the Quick Access Menu for a regular pad. A quick tap of Select still reaches the game, delivered when you let go (a beat late). Select pressed as part of a combo — including the leave chord above — passes through untouched.

What the raw button does, per client:

  • Linux & Windows desktop, macOS, Android — the guide press is forwarded to the host. If Steam Big Picture or the Xbox Game Bar is also watching for it on the device in your hands, both may react — that's a local setting on that device, not something the stream can suppress.
  • Steam Deck / Gaming Mode — the Steam and buttons stay with the Deck by default: SteamOS always opens its own menus for them, so forwarding the raw press as well opened BOTH menus at once, the Deck's on top of the stream. Reach the host's menus with hold-Select, or with the Punktfunk panel's Host menus buttons (Steam Deck page). The old behavior is one setting away: Steam / guide button → Send to host.
  • iPhone / iPad — iOS reserves the Home press for its own Game Overlay, so hold-Select is the reliable route to the host's overlay. On iOS 27 or later you can also hand the button to the app yourself, in the system's per-controller Home-button setting.
  • Apple TV — tvOS never delivers the Home press to apps; hold-Select is the only route.

Both halves are settings, per profile like everything else: Steam / guide button (Automatic / Send to host / This device) and Hold Select for guide (Automatic / On / Off). Automatic picks the behavior above for each platform — the gesture stays off where the raw button already works, so games that use a held Select keep it.

Mouse modes

There are two, and they are a per-client setting called Mouse input:

  • Capture (games) — the pointer locks to the stream and only relative movement is sent. The only cursor you see is the host's. This is what mouse-look in a game needs. The session window also grabs the keyboard here, so Alt+Tab and the Windows key (Super on Linux) reach the host rather than your own desktop — on macOS that is the ⌘ chords, ⌘Q included, with ⌘⎋ kept back as the way out. Turn Capture system shortcuts off in client settings to keep them local.
  • Desktop (absolute) — the pointer is not locked. It moves in and out of the stream freely and its position is sent as an absolute point — what you want for remote desktop work. Your local cursor is hidden over the stream; the one you see there is the host's.

Capture is the default on the Linux, Windows and macOS clients. Android defaults to Desktop — a phone or TV is more often driven by touch or a pad than by a locked mouse.

Switch live with Ctrl+Alt+Shift+M (⌃⌥⇧M on macOS), whether input is captured or not. On Android, Ctrl+Alt+Shift+Q flips the capture instead. The picker is macOS-only among the Apple apps; on iPad the equivalent is the Capture pointer for games toggle (on by default), which needs the stream fullscreen and frontmost.

Two things can override your choice. gamescope hosts can't take absolute pointer input: ask for desktop mode against one and the session quietly stays captured, and the chord has nothing to offer (see gamescope). And against a host that forwards its cursor separately instead of drawing it into the video, the Linux and Windows clients flip to relative motion by themselves when an app on the host grabs or hides the pointer, then back when it lets go. Using the chord yourself overrides that until the host's intent next changes. The macOS client ignores the signal on purpose.

Touch modes

On a touchscreen client the Touch input setting picks one of three models. All three exist on Android, iPhone/iPad, Linux and Windows.

  • Trackpad (the default) — your finger drives the host cursor like a laptop touchpad. The cursor stays put when you touch down and moves by your finger's travel, so you can lift and re-swipe to walk it across a screen far larger than your own.
  • Direct pointer — the cursor jumps to your finger and follows it.
  • Touch passthrough — every finger is forwarded as a real touch contact, with no gesture interpretation at all. Only useful for apps and games that genuinely understand touch.

Trackpad and Direct pointer share one gesture vocabulary: tap = left click, two-finger tap = right click, two-finger drag = scroll, tap-then-press-and-drag = a held left drag, three-finger tap = cycle the stats overlay. On Android and iPhone/iPad a three-finger swipe up or down summons or dismisses the local on-screen keyboard for typing on the host; the Linux and Windows clients have no such keyboard, and there any two-or-more-finger drag scrolls.

Touch passthrough depends on the host being able to inject touch, and that varies:

Host Touch passthrough
KDE Plasma (KWin), GNOME Full multi-touch
Windows 10 1809 and newer Full multi-touch
Sway, Hyprland and other wlroots compositors Not injected — contacts are dropped
gamescope Gaming Mode Degraded to a single absolute pointer — see gamescope

The gamescope row is a rule, not an exception: wherever the compositor offers no touchscreen device to drive, only the first finger is used, as an absolute pointer. Tapping still clicks; pinches and other multi-finger gestures do not survive.

The trackpad and pointer models are unaffected by all of this: they send ordinary mouse events.

Pen and stylus

A stylus is not treated as a finger. Punktfunk carries position, tip pressure, tilt angle and tilt direction, barrel roll, hover distance, the eraser end, and two barrel buttons on their own input plane, so drawing and handwriting behave the way they do locally.

Clients that send pen input:

  • iPhone and iPad with an Apple Pencil, including hover. The Pencil has no hardware eraser or barrel buttons, so a double-tap is sent as barrel button 2, and on iOS 17.5 or newer a squeeze is sent as barrel button 1. Pencil Pro's barrel roll also needs iOS 17.5.
  • Android phones and tablets with an active stylus — pressure, tilt, hover, the eraser tool and both barrel buttons. Android exposes no barrel-roll axis, so roll is not sent from there.
  • Moonlight clients that send pen events reach the same host-side pen.

The Linux, Windows, macOS and Apple TV clients do not send stylus input.

What the host presents it as:

  • On Linux, a virtual tablet named Punktfunk Pen appears the first time you use the stylus and is removed when the session ends. Applications see a real pen through the usual tablet path, so Krita, GIMP and Xournal++ treat it as a graphics tablet. It is a screen tablet, mapped by your compositor's own default tablet mapping — correct on a single output; multi-monitor pinning is up to the compositor.
  • On Windows, a per-session synthetic pen pointer feeds Windows' normal pen system: pressure, tilt, rotation, the barrel button and the eraser. This needs Windows 10 1809 or newer.

Before it can work on Linux, the host needs access to /dev/uinput — the same input group step the virtual gamepads need, covered under After installing. Without it the host never offers pen at all.

If the host is too old, or pen is switched off, nothing breaks: the client keeps folding the stylus into its ordinary touch or pointer path. You can still draw — just without pressure and tilt. Whether pen splits out is decided by the host, not by your touch mode: you can be in Trackpad mode and still draw with full fidelity.

Operators can turn the whole feature off by setting PUNKTFUNK_PEN=0 in the host's host.env (see Configuration). The host then stops advertising pen to Punktfunk and Moonlight clients alike, and every client falls back to touch.