Fix synthetic Command key equivalents

pull/12883/head
Andy wu 2026-06-01 06:19:44 +08:00
parent 3e83a54d08
commit f42229c512
2 changed files with 85 additions and 1 deletions

View File

@ -76,6 +76,32 @@ extension Ghostty {
// Cancellable for search state needle changes
private var searchNeedleCancellable: AnyCancellable?
// Tracks a real Command-key transition observed by this surface.
// Mouse/tracking events may carry synthetic or stale modifier flags, so
// they must not establish keyboard state for key-equivalent handling.
private var commandKeyDown = false
static func shouldIgnoreCommandKeyEquivalent(
_ event: NSEvent,
commandKeyDown: Bool
) -> Bool {
event.type == .keyDown &&
event.modifierFlags.contains(.command) &&
!commandKeyDown
}
private static func isCommandModifierKey(_ keyCode: UInt16) -> Bool {
keyCode == 0x37 || keyCode == 0x36
}
private func notePhysicalCommandState(_ event: NSEvent) {
guard event.type == .flagsChanged,
Self.isCommandModifierKey(event.keyCode)
else { return }
commandKeyDown = event.modifierFlags.contains(.command)
}
// Whether the pointer should be visible or not
@Published private(set) var pointerStyle: CursorStyle = .horizontalText
@ -410,6 +436,7 @@ extension Ghostty {
// sent to stop things like mouse selection.
if !focused {
suppressNextLeftMouseUp = false
commandKeyDown = false
}
// Notify libghostty
@ -1275,6 +1302,16 @@ extension Ghostty {
return false
}
// AppKit may deliver synthetic Command key-equivalents during mouse
// interactions. Only a Command-key transition is allowed to establish
// real keyboard state for terminal input.
if Self.shouldIgnoreCommandKeyEquivalent(
event,
commandKeyDown: commandKeyDown
) {
return true
}
// Get information about if this is a binding.
let bindingFlags = surfaceModel.flatMap { surface in
var ghosttyEvent = event.ghosttyKeyEvent(GHOSTTY_ACTION_PRESS)
@ -1384,6 +1421,8 @@ extension Ghostty {
}
override func flagsChanged(with event: NSEvent) {
notePhysicalCommandState(event)
let mod: UInt32
switch event.keyCode {
case 0x39: mod = GHOSTTY_MODS_CAPS.rawValue

View File

@ -1,5 +1,6 @@
@testable import Ghostty
import AppKit
import Testing
@testable import Ghostty
struct SurfaceViewAppKitTests {
@Test(arguments: [
@ -41,4 +42,48 @@ struct SurfaceViewAppKitTests {
) == false
)
}
@Test func ignoresCommandKeyEquivalentWithoutCommandKeyDown() throws {
let event = try #require(NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: .command,
timestamp: 1,
windowNumber: 1,
context: nil,
characters: "c",
charactersIgnoringModifiers: "c",
isARepeat: false,
keyCode: 8
))
#expect(Ghostty.SurfaceView.shouldIgnoreCommandKeyEquivalent(
event,
commandKeyDown: false
))
#expect(!Ghostty.SurfaceView.shouldIgnoreCommandKeyEquivalent(
event,
commandKeyDown: true
))
}
@Test func doesNotIgnoreNonCommandKeyEquivalent() throws {
let event = try #require(NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [],
timestamp: 1,
windowNumber: 1,
context: nil,
characters: "c",
charactersIgnoringModifiers: "c",
isARepeat: false,
keyCode: 8
))
#expect(!Ghostty.SurfaceView.shouldIgnoreCommandKeyEquivalent(
event,
commandKeyDown: false
))
}
}