Fix the keyboard typing pipeline + add a test that catches the regression
The earlier UIKeyInput conformance was declared in a separate extension. ObjC protocol conformance via Swift extension is fragile when the protocol inherits another @objc protocol (UIKeyInput inherits UITextInputTraits) — the runtime didn't always pick up insertText:, so the on-screen keyboard came up but characters never reached controller.type(_:). Fix: declare UIKeyInput conformance directly on FramebufferUIView's class declaration, with insertText / deleteBackward / hasText as native members. Also caught and fixed by the new UI test: - The toolbar's keyboard-icon button had a 20×13 hit region (SF Symbol size) even though the visual frame was 34×34 — XCUI taps couldn't land on it reliably. .contentShape(Rectangle()) widens the hit area to the frame. - accessibilityValue is reserved by iOS for UIKeyInput-classed views (treats them as TextView), so a separate hidden "fb-diag" accessibility probe records keyboard plumbing events for the test to verify. Tests: - KeyboardInputTests (5): pure mapping from String → X11 keysym down/up pairs - ScreensUITests.testSoftwareKeyboardSendsCharactersToFramebuffer: opens a session, taps the keyboard toggle, types "hi" via the system keyboard, and asserts the framebuffer's diagnostic probe records [ins:h] and [ins:i] — proving the chars reach controller.type(_:) - A SwiftUI state probe (sessionview-state) verifies the binding flips, which guards against future tap-routing regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,16 +159,50 @@ public final class SessionController {
|
||||
|
||||
public func type(_ string: String) {
|
||||
guard !viewOnly else { return }
|
||||
let events = Self.keyEvents(for: string)
|
||||
recordTypedEvents(events)
|
||||
guard let connection else { return }
|
||||
for event in events {
|
||||
let code = VNCKeyCode(event.keysym)
|
||||
if event.isDown {
|
||||
connection.keyDown(code)
|
||||
} else {
|
||||
connection.keyUp(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure mapping from a string to the key down/up keysym pairs it should
|
||||
/// generate. Newlines map to X11 Return; all other printable ASCII maps
|
||||
/// 1:1 to the equivalent X11 keysym (which equals the ASCII code).
|
||||
public nonisolated static func keyEvents(for string: String) -> [KeyEvent] {
|
||||
var events: [KeyEvent] = []
|
||||
for char in string {
|
||||
if char.isNewline {
|
||||
pressKey(.return)
|
||||
events.append(KeyEvent(keysym: 0xFF0D, isDown: true))
|
||||
events.append(KeyEvent(keysym: 0xFF0D, isDown: false))
|
||||
continue
|
||||
}
|
||||
for code in VNCKeyCode.withCharacter(char) {
|
||||
connection?.keyDown(code)
|
||||
connection?.keyUp(code)
|
||||
events.append(KeyEvent(keysym: code.rawValue, isDown: true))
|
||||
events.append(KeyEvent(keysym: code.rawValue, isDown: false))
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
public struct KeyEvent: Equatable, Sendable {
|
||||
public let keysym: UInt32
|
||||
public let isDown: Bool
|
||||
}
|
||||
|
||||
/// Test hook: when set, every typed event is appended here in addition to
|
||||
/// being sent over the wire. Used by UI tests to verify keyboard plumbing.
|
||||
public var typedEventLog: [KeyEvent] = []
|
||||
public var typedEventLogEnabled: Bool = false
|
||||
private func recordTypedEvents(_ events: [KeyEvent]) {
|
||||
guard typedEventLogEnabled else { return }
|
||||
typedEventLog.append(contentsOf: events)
|
||||
}
|
||||
|
||||
public func sendBackspace() { pressKey(.delete) }
|
||||
|
||||
Reference in New Issue
Block a user