Fix synthetic Command key equivalents
parent
3e83a54d08
commit
f42229c512
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue