feat(client): cross-target input handling + LAN mDNS discovery
Input handling, building on macOS/iOS/tvOS: - macOS recapture after navigating out: engageCapture no longer latches captured=true when the cursor grab is refused mid app-activation (which left a free cursor that no later click could re-grab); cursorCapture.capture() now reports success. + canBecomeKeyView. - iOS/iPadOS recapture: restore the prior capture on didBecomeActive (nothing re-grabbed mouse/keyboard on return before). - iPad indirect pointer (no lock) is forwarded as an absolute MOUSE (move + buttons + scroll via hover / UITouch.indirectPointer), not as touch, with the local cursor visible; GCMouse owns the locked regime, gated so the two never double-send. Adds the MouseMoveAbs wire helper. - Trackpad scroll on iOS (was entirely missing): GCMouse scroll dpad when locked + a scroll-only UIPanGestureRecognizer otherwise. - tvOS: no focusable control during play (a focusable Disconnect button ate the controller's A in the focus engine); Siri Remote Menu disconnects. - Don't leak touch to the host under the TOFU trust prompt (gate on captureEnabled). LAN discovery: HostDiscovery (NWBrowser over _punktfunk._udp, the host's crate::discovery advert) resolves each service to IP:port and parses the TXT (fp advisory, pair, id); an "On this network" section in the grid (tap to save + connect, or pair if required). iOS/tvOS get NSBonjourServices via a merged Config/Info.plist. Integration-tested end to end against a fake NWListener advert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Advertise a fake punktfunk/1 host over real mDNS (NWListener) and assert HostDiscovery's
|
||||
// NWBrowser finds it, resolves an address+port, and parses the TXT (id / pair / fp). This
|
||||
// exercises the whole client discovery path on the loopback/LAN; it self-skips if the test
|
||||
// environment blocks Bonjour (sandboxed CI without local-network access).
|
||||
|
||||
import Network
|
||||
import PunktfunkKit
|
||||
import XCTest
|
||||
|
||||
final class HostDiscoveryTests: XCTestCase {
|
||||
func testFindsAdvertisedHost() async throws {
|
||||
let serviceName = "PunktfunkTest-\(UUID().uuidString.prefix(8))"
|
||||
let uniqueid = "test-\(UUID().uuidString)"
|
||||
|
||||
var txt = NWTXTRecord()
|
||||
txt["proto"] = "punktfunk/1"
|
||||
txt["fp"] = String(repeating: "ab", count: 32) // 64 hex chars, like a real cert SHA-256
|
||||
txt["pair"] = "required"
|
||||
txt["id"] = uniqueid
|
||||
|
||||
let listener = try NWListener(using: .udp)
|
||||
listener.service = NWListener.Service(
|
||||
name: String(serviceName), type: "_punktfunk._udp", txtRecord: txt)
|
||||
// The resolver opens a throwaway UDP flow to read the resolved endpoint — accept and
|
||||
// drop it so it doesn't linger.
|
||||
listener.newConnectionHandler = { connection in connection.cancel() }
|
||||
listener.start(queue: .global())
|
||||
defer { listener.cancel() }
|
||||
|
||||
let discovery = await HostDiscovery()
|
||||
await discovery.start()
|
||||
defer { Task { await discovery.stop() } }
|
||||
|
||||
// Poll up to ~10s for the advert to be browsed AND resolved.
|
||||
var found: DiscoveredHost?
|
||||
let deadline = Date().addingTimeInterval(10)
|
||||
while Date() < deadline {
|
||||
if let host = await discovery.hosts.first(where: { $0.name == String(serviceName) }) {
|
||||
found = host
|
||||
break
|
||||
}
|
||||
try await Task.sleep(nanoseconds: 200_000_000)
|
||||
}
|
||||
|
||||
guard let host = found else {
|
||||
throw XCTSkip("mDNS discovery unavailable in this environment (no local network).")
|
||||
}
|
||||
XCTAssertEqual(host.id, uniqueid, "the stable mDNS id should key the host")
|
||||
XCTAssertTrue(host.requiresPairing, "pair=required must surface as requiresPairing")
|
||||
XCTAssertEqual(host.fingerprintHex, String(repeating: "ab", count: 32))
|
||||
XCTAssertFalse(host.host.isEmpty, "a resolved address is required to connect")
|
||||
XCTAssertGreaterThan(host.port, 0, "a resolved port is required to connect")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user