forked from unom/punktfunk
Align the macOS/iPad Stream menu on the cross-client Ctrl+Alt+Shift set the Windows and Linux clients reserve — Release Mouse (⌃⌥⇧Q), Disconnect (⌃⌥⇧D), HUD toggle (⌃⌥⇧S) — with ⌘⎋ kept as the macOS/iPad capture toggle, and surface them on a 6-second banner at stream start. Add an opt-in V-Sync present mode (punktfunk.vsync, default OFF = lowest-latency immediate present; PUNKTFUNK_PRESENT_MODE overrides for A/B), with the presenter reworked to a frame-arrival-triggered render thread across Stage2Pipeline / MetalVideoPresenter / SessionPresenter, plus the windowed title-bar safe-area handling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
64 lines
2.8 KiB
Swift
64 lines
2.8 KiB
Swift
// The app's "Stream" menu (macOS menu bar + iPad hardware-keyboard shortcuts). These live at
|
|
// the Scene level so they keep working when the HUD overlay is hidden. The shortcuts are the
|
|
// CROSS-CLIENT set every punktfunk client reserves — Ctrl+Alt+Shift+Q (release the captured
|
|
// mouse) / +D (disconnect) / +S (stats) — and the menu is their discoverable surface on macOS
|
|
// (the Linux client has its GTK Shortcuts window, Windows its start-of-stream banner). While
|
|
// input is CAPTURED these key equivalents never reach the menu (the stream view swallows
|
|
// keys); InputCapture's monitor detects the same combos there and performs the same actions —
|
|
// the menu covers the released state and discoverability. The stats toggle just flips the
|
|
// shared `hudEnabled` setting; ContentView reads the same @AppStorage and reacts.
|
|
//
|
|
// tvOS has no menu bar / hardware-keyboard command surface (disconnect there is the Siri
|
|
// Remote's Menu button, handled by ContentView's `.onExitCommand`), so this whole file is
|
|
// non-tvOS only.
|
|
|
|
#if !os(tvOS)
|
|
import PunktfunkKit
|
|
import SwiftUI
|
|
|
|
/// The live session's menu-reachable actions, published by ContentView via
|
|
/// `.focusedSceneValue` so the Scene-level commands can drive it.
|
|
struct SessionFocus {
|
|
var isStreaming: Bool
|
|
var disconnect: () -> Void
|
|
}
|
|
|
|
private struct SessionFocusKey: FocusedValueKey {
|
|
typealias Value = SessionFocus
|
|
}
|
|
|
|
extension FocusedValues {
|
|
var sessionFocus: SessionFocus? {
|
|
get { self[SessionFocusKey.self] }
|
|
set { self[SessionFocusKey.self] = newValue }
|
|
}
|
|
}
|
|
|
|
struct StreamCommands: Commands {
|
|
@FocusedValue(\.sessionFocus) private var session
|
|
@AppStorage(DefaultsKey.hudEnabled) private var hudEnabled = true
|
|
|
|
var body: some Commands {
|
|
CommandMenu("Stream") {
|
|
Button(hudEnabled ? "Hide Statistics" : "Show Statistics") {
|
|
hudEnabled.toggle()
|
|
}
|
|
.keyboardShortcut("s", modifiers: [.control, .option, .shift])
|
|
// Reaches the key window's stream view via NotificationCenter — capture is view
|
|
// state the Scene can't touch directly. (Captured, the combo is handled by
|
|
// InputCapture's monitor before menus see it; this item is the released-state
|
|
// path and the shortcut's menu-bar documentation.)
|
|
Button("Release Mouse") {
|
|
NotificationCenter.default.post(name: .punktfunkReleaseCapture, object: nil)
|
|
}
|
|
.keyboardShortcut("q", modifiers: [.control, .option, .shift])
|
|
.disabled(session?.isStreaming != true)
|
|
Divider()
|
|
Button("Disconnect") { session?.disconnect() }
|
|
.keyboardShortcut("d", modifiers: [.control, .option, .shift])
|
|
.disabled(session?.isStreaming != true)
|
|
}
|
|
}
|
|
}
|
|
#endif
|