133e25849d
Sources reorganized (client: Home/Session/Settings/Stores/Support/Trust; kit: Audio/Connection/Gamepad/Input/Support/Video/Views) with the big files split along the same seams. The gamepad mode is couch-complete, and now on macOS too (the living-room Mac case), not just iOS/iPadOS: - GamepadSettingsView: a console-style, fully controller-navigable settings screen (X from the launcher) — up/down moves focus, left/right steps values (clamped, boundary thud), A cycles/toggles, B closes; the focused row shows a one-line description. Backed by GamepadMenuList, the vertical sibling of GamepadCarousel, and SettingsOptions — the option lists hoisted out of SettingsView statics and shared by the touch, tvOS and gamepad settings. - GamepadAddHostView + GamepadKeyboard: register a host end to end with a pad — field rows open an on-screen controller keyboard (dpad grid, A types, X backspaces, B done); the launcher carousel ends in an Add Host tile, so the dead-end "add one with touch first" empty state is gone. - Launcher polish: contextual hint bar with the pad's real button glyphs, controller name + battery chip, one shared console chrome. - GamepadScreenBackground: an animated aurora (TimelineView-driven drifting blobs in the brand's violet family, breathing radii, slow hue shift, legibility scrim; freezes under Reduce Motion). Pure SwiftUI on purpose — a .metal library only bundles reliably in one of the two build systems (SPM vs the xcodeproj's synced folders) these sources compile under. - macOS port: settings/add-host/library present as sized sheets (a macOS sheet takes its content's IDEAL size, and the GeometryReader-driven screens collapsed to nothing), NSScreen-based mode lists, scroll indicators .never (the "always show scroll bars" setting overrides .hidden), tray scrims so scrolled rows dim under the pinned title/hints, extra title clearance, and a PUNKTFUNK_FORCE_GAMEPAD_UI=1 dev hook — launcher/settings/add-host/keyboard/ library render-verified live on a real Mac + LAN hosts. - GamepadMenuInput: X button support, and (re)start now snapshots held buttons so a controller handoff press never fires twice (the B that closed the keyboard no longer also cancels the screen underneath). - Cleanups: one "Connection failed" alert in ContentView instead of one per home screen; HostDiscovery.advertises/unsaved shared by both home screens. - host: can_encode_444 stub for the non-Linux/Windows host build (the macOS synthetic-source loopback used by the Swift tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
4.5 KiB
Swift
88 lines
4.5 KiB
Swift
// Convenience constructors for the wire input events (field semantics match
|
||
// punktfunk_core::input::InputEvent; see punktfunk_core.h).
|
||
|
||
import Foundation
|
||
import PunktfunkCore
|
||
|
||
public extension PunktfunkInputEvent {
|
||
private static func make(
|
||
_ kind: UInt32, code: UInt32, x: Int32, y: Int32, flags: UInt32 = 0
|
||
) -> PunktfunkInputEvent {
|
||
PunktfunkInputEvent(kind: UInt8(kind), _pad: (0, 0, 0), code: code, x: x, y: y, flags: flags)
|
||
}
|
||
static func mouseMove(dx: Int32, dy: Int32) -> PunktfunkInputEvent {
|
||
make(PUNKTFUNK_INPUT_KIND_MOUSE_MOVE.rawValue, code: 0, x: dx, y: dy)
|
||
}
|
||
/// Absolute cursor position in client-surface pixels — the host places its cursor
|
||
/// there (same letterbox mapping and `flags` surface-dims packing as the touch events).
|
||
/// Used by the iPad pointer fallback when the scene can't pointer-lock and GCMouse's
|
||
/// relative deltas aren't available; the surface dimensions must each fit in 16 bits.
|
||
static func mouseMoveAbs(
|
||
x: Int32, y: Int32, surfaceWidth: UInt32, surfaceHeight: UInt32
|
||
) -> PunktfunkInputEvent {
|
||
make(
|
||
PUNKTFUNK_INPUT_KIND_MOUSE_MOVE_ABS.rawValue, code: 0, x: x, y: y,
|
||
flags: ((surfaceWidth & 0xFFFF) << 16) | (surfaceHeight & 0xFFFF))
|
||
}
|
||
/// GameStream button ids: 1=left 2=middle 3=right 4=X1 5=X2 (host maps to evdev BTN_*).
|
||
static func mouseButton(_ button: UInt32, down: Bool) -> PunktfunkInputEvent {
|
||
make(
|
||
(down ? PUNKTFUNK_INPUT_KIND_MOUSE_BUTTON_DOWN : PUNKTFUNK_INPUT_KIND_MOUSE_BUTTON_UP).rawValue,
|
||
code: button, x: 0, y: 0)
|
||
}
|
||
/// `vk` is a Windows virtual-key code (the host's vk_to_evdev table consumes these).
|
||
static func key(_ vk: UInt32, down: Bool) -> PunktfunkInputEvent {
|
||
make((down ? PUNKTFUNK_INPUT_KIND_KEY_DOWN : PUNKTFUNK_INPUT_KIND_KEY_UP).rawValue, code: vk, x: 0, y: 0)
|
||
}
|
||
/// WHEEL_DELTA(120)-scaled; positive = up (vertical) / right (horizontal) — the
|
||
/// convention Moonlight/SDL use; the host maps onto the ei/wl axes.
|
||
static func scroll(_ delta: Int32, horizontal: Bool = false) -> PunktfunkInputEvent {
|
||
make(PUNKTFUNK_INPUT_KIND_MOUSE_SCROLL.rawValue, code: horizontal ? 1 : 0, x: delta, y: 0)
|
||
}
|
||
|
||
// Gamepad (wire contract in punktfunk_core::input::gamepad): one transition per event,
|
||
// `pad` = controller index, accumulated host-side into a virtual Xbox 360 or DualSense
|
||
// pad (the session's negotiated `GamepadType`).
|
||
|
||
/// `button` is a GameStream buttonFlags bit (A=0x1000 B=0x2000 X=0x4000 Y=0x8000,
|
||
/// dpad=0x1/2/4/8, start=0x10 back=0x20 LS=0x40 RS=0x80 LB=0x100 RB=0x200 guide=0x400,
|
||
/// touchpad click=0x100000 — DualSense sessions only, the xpad has no such button).
|
||
static func gamepadButton(_ button: UInt32, down: Bool, pad: UInt32 = 0) -> PunktfunkInputEvent {
|
||
make(
|
||
PUNKTFUNK_INPUT_KIND_GAMEPAD_BUTTON.rawValue,
|
||
code: button, x: down ? 1 : 0, y: 0, flags: pad)
|
||
}
|
||
|
||
/// Axis ids: 0=LSX 1=LSY 2=RSX 3=RSY (−32768...32767, XInput convention: +y = UP —
|
||
/// `GCControllerDirectionPad.yAxis` already matches, no flip), 4=LT 5=RT (0...255).
|
||
static func gamepadAxis(_ axis: UInt32, value: Int32, pad: UInt32 = 0) -> PunktfunkInputEvent {
|
||
make(PUNKTFUNK_INPUT_KIND_GAMEPAD_AXIS.rawValue, code: axis, x: value, y: 0, flags: pad)
|
||
}
|
||
|
||
// Touch (host-side: libei ei_touchscreen on the virtual output). `id` distinguishes
|
||
// fingers and is reusable after touchUp; coordinates are absolute pixels on the
|
||
// client's touch surface, whose size rides in `flags` so the host can rescale —
|
||
// the surface dimensions must each fit in 16 bits. Built for the iOS variant
|
||
// (UITouch → these); nothing on macOS emits them yet.
|
||
|
||
static func touchDown(
|
||
id: UInt32, x: Int32, y: Int32, surfaceWidth: UInt32, surfaceHeight: UInt32
|
||
) -> PunktfunkInputEvent {
|
||
make(
|
||
PUNKTFUNK_INPUT_KIND_TOUCH_DOWN.rawValue, code: id, x: x, y: y,
|
||
flags: ((surfaceWidth & 0xFFFF) << 16) | (surfaceHeight & 0xFFFF))
|
||
}
|
||
|
||
static func touchMove(
|
||
id: UInt32, x: Int32, y: Int32, surfaceWidth: UInt32, surfaceHeight: UInt32
|
||
) -> PunktfunkInputEvent {
|
||
make(
|
||
PUNKTFUNK_INPUT_KIND_TOUCH_MOVE.rawValue, code: id, x: x, y: y,
|
||
flags: ((surfaceWidth & 0xFFFF) << 16) | (surfaceHeight & 0xFFFF))
|
||
}
|
||
|
||
static func touchUp(id: UInt32) -> PunktfunkInputEvent {
|
||
make(PUNKTFUNK_INPUT_KIND_TOUCH_UP.rawValue, code: id, x: 0, y: 0)
|
||
}
|
||
}
|