native macOS codings agent orchestrator
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #190 from supabitapp/bugfix/keyboard-layout-keyup-only

fix ghostty keyboard layout keyup leak

authored by

khoi and committed by
GitHub
ff39aa6b fb093ac4

+70
+39
supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift
··· 11 11 let length: UInt64 12 12 } 13 13 14 + struct KeyboardLayoutChangeKeyUpSuppression: Equatable { 15 + static let lifetime: TimeInterval = 1 16 + 17 + let keyCode: UInt16 18 + let expiresAt: TimeInterval 19 + 20 + init(keyCode: UInt16, timestamp: TimeInterval) { 21 + self.keyCode = keyCode 22 + expiresAt = timestamp + Self.lifetime 23 + } 24 + 25 + func suppresses(keyCode: UInt16, timestamp: TimeInterval) -> Bool { 26 + timestamp <= expiresAt && self.keyCode == keyCode 27 + } 28 + 29 + func isExpired(at timestamp: TimeInterval) -> Bool { 30 + timestamp > expiresAt 31 + } 32 + } 33 + 14 34 private final class CachedValue<T> { 15 35 private var value: T? 16 36 private let fetch: () -> T ··· 60 80 private var currentCursor: NSCursor = .iBeam 61 81 private var focused = false 62 82 private var markedText = NSMutableAttributedString() 83 + private var keyboardLayoutChangeKeyUpSuppression: KeyboardLayoutChangeKeyUpSuppression? 63 84 private var keyTextAccumulator: [String]? 64 85 private var cellSize: CGSize = .zero 65 86 private var lastScrollbar: ScrollbarState? ··· 539 560 lastPerformKeyEvent = nil 540 561 interpretKeyEvents([translationEvent]) 541 562 if !markedTextBefore, keyboardIdBefore != keyboardLayoutId() { 563 + keyboardLayoutChangeKeyUpSuppression = KeyboardLayoutChangeKeyUpSuppression( 564 + keyCode: event.keyCode, 565 + timestamp: event.timestamp 566 + ) 542 567 return 543 568 } 544 569 syncPreedit(clearIfNeeded: markedTextBefore) ··· 566 591 } 567 592 568 593 override func keyUp(with event: NSEvent) { 594 + if suppressKeyboardLayoutChangeKeyUp(event) { return } 569 595 sendKey(action: GHOSTTY_ACTION_RELEASE, event: event) 570 596 } 571 597 ··· 1242 1268 } 1243 1269 } 1244 1270 return keyEvent 1271 + } 1272 + 1273 + private func suppressKeyboardLayoutChangeKeyUp(_ event: NSEvent) -> Bool { 1274 + guard let suppression = keyboardLayoutChangeKeyUpSuppression else { return false } 1275 + if suppression.isExpired(at: event.timestamp) { 1276 + keyboardLayoutChangeKeyUpSuppression = nil 1277 + return false 1278 + } 1279 + if suppression.suppresses(keyCode: event.keyCode, timestamp: event.timestamp) { 1280 + keyboardLayoutChangeKeyUpSuppression = nil 1281 + return true 1282 + } 1283 + return false 1245 1284 } 1246 1285 1247 1286 private func ghosttyCharacters(_ event: NSEvent) -> String? {
+31
supacodeTests/GhosttySurfaceViewTests.swift
··· 45 45 ) == nil 46 46 ) 47 47 } 48 + 49 + @Test func keyboardLayoutChangeKeyUpSuppressionSuppressesMatchingKeyUp() { 50 + let suppression = GhosttySurfaceView.KeyboardLayoutChangeKeyUpSuppression( 51 + keyCode: 49, 52 + timestamp: 10 53 + ) 54 + 55 + #expect(suppression.suppresses(keyCode: 49, timestamp: 10.1)) 56 + #expect(!suppression.isExpired(at: 10.1)) 57 + } 58 + 59 + @Test func keyboardLayoutChangeKeyUpSuppressionIgnoresDifferentKeyUp() { 60 + let suppression = GhosttySurfaceView.KeyboardLayoutChangeKeyUpSuppression( 61 + keyCode: 49, 62 + timestamp: 10 63 + ) 64 + 65 + #expect(!suppression.suppresses(keyCode: 50, timestamp: 10.1)) 66 + #expect(suppression.suppresses(keyCode: 49, timestamp: 10.2)) 67 + #expect(!suppression.isExpired(at: 10.1)) 68 + } 69 + 70 + @Test func keyboardLayoutChangeKeyUpSuppressionExpires() { 71 + let suppression = GhosttySurfaceView.KeyboardLayoutChangeKeyUpSuppression( 72 + keyCode: 49, 73 + timestamp: 10 74 + ) 75 + 76 + #expect(!suppression.suppresses(keyCode: 49, timestamp: 11.1)) 77 + #expect(suppression.isExpired(at: 11.1)) 78 + } 48 79 }