feat(apple): client-side cursor for gamescope sessions (toggle + shortcut)
ci / web (push) Successful in 28s
ci / docs-site (push) Successful in 31s
apple / swift (push) Successful in 1m14s
ci / rust (push) Successful in 2m9s
ci / bench (push) Successful in 1m42s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m17s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m51s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m24s
ci / web (push) Successful in 28s
ci / docs-site (push) Successful in 31s
apple / swift (push) Successful in 1m14s
ci / rust (push) Successful in 2m9s
ci / bench (push) Successful in 1m42s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m17s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 4m51s
docker / deploy-docs (push) Successful in 17s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 4m24s
gamescope's PipeWire capture carries no cursor (verified upstream — it never
composites the cursor or adds SPA_META_Cursor), so the cursor must be drawn on the
client. New macOS "cursor-visible" capture mode: instead of disassociating+hiding
the system cursor and sending relative deltas (the game path, unchanged), it keeps
the system cursor visible over the stream and sends ABSOLUTE positions
(MouseMoveAbs), mapped through the video's aspect-fit (AVMakeRect) to host pixels
with the letterbox bars dropped. The visible system cursor IS the client cursor —
zero added latency, no double cursor (gamescope draws none), accurate (the client
drives the host's absolute mouse).
- Default: on iff the session's resolved compositor is gamescope (via the new
punktfunk_connection_compositor getter, fc30307).
- Settings: "Cursor in stream" → Auto (gamescope) / Always / Never.
- Shortcut: ⌘⇧C toggles it live mid-session (re-engages capture so disassociation
+ abs/rel forwarding swap atomically); shown in the HUD.
macOS-only (the visible-cursor mode lives in the macOS StreamView). Verified to
compile + link via xcodebuild Release on the Mac; runtime behavior (cursor landing,
hover forwarding) to be confirmed live. Rust ABI side committed separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,12 @@ public final class InputCapture {
|
||||
/// event itself is swallowed). Main queue.
|
||||
public var onToggleCapture: (() -> Void)?
|
||||
|
||||
/// Fired on ⌘⇧C (the client-side-cursor toggle — flips between the captured/disassociated
|
||||
/// relative path and the visible-cursor absolute path; detected here, like ⌘⎋, so it works
|
||||
/// regardless of the current capture state and the event itself is swallowed). macOS only;
|
||||
/// the absolute-vs-relative forwarding lives entirely in StreamLayerView. Main queue.
|
||||
public var onToggleCursor: (() -> Void)?
|
||||
|
||||
/// Fired when a newer InputCapture takes the process-global GC handler slots (the
|
||||
/// singletons hold ONE handler each): the preempted owner must drop its capture
|
||||
/// state — its handlers are gone, so it would otherwise sit "captured" with dead
|
||||
@@ -203,6 +209,15 @@ public final class InputCapture {
|
||||
self.onToggleCapture?()
|
||||
return nil
|
||||
}
|
||||
// ⌘⇧C toggles the client-side cursor (visible-cursor absolute path vs the
|
||||
// captured relative path). keyCode 8 = kVK_ANSI_C; layout-independent so it
|
||||
// fires the same on any keyboard. Suppress the C (latched like ⌘⎋'s Esc) so it
|
||||
// doesn't type into the host, and swallow the event so it doesn't beep.
|
||||
if event.keyCode == 8 /* C */, flags == [.command, .shift] {
|
||||
self.suppressedVK = 0x43 // VK_C — the same physical C is en route via GC
|
||||
self.onToggleCursor?()
|
||||
return nil
|
||||
}
|
||||
return event
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user