This commit is contained in:
Austin Wang 2026-03-03 15:53:39 -08:00 committed by GitHub
parent 4af2e6be30
commit a3681ede5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 109 additions and 0 deletions

View file

@ -1176,6 +1176,33 @@ func shouldRouteTerminalFontZoomShortcutToGhostty(
) != nil
}
func shouldRouteTerminalCommandShortcutToGhostty(
flags: NSEvent.ModifierFlags,
chars: String,
keyCode: UInt16,
terminalHasSelection: Bool
) -> Bool {
let normalizedFlags = flags
.intersection(.deviceIndependentFlagsMask)
.subtracting([.numericPad, .function, .capsLock])
guard normalizedFlags.contains(.command) else { return false }
let normalizedChars = chars.lowercased()
if normalizedFlags == [.command] {
// Keep Preferences (Cmd+,) menu-routed even when a terminal is focused.
if normalizedChars == "," || keyCode == 43 {
return false
}
// Preserve standard copy behavior when text is selected in the terminal.
if (normalizedChars == "c" || keyCode == 8), terminalHasSelection {
return false
}
}
return true
}
func cmuxOwningGhosttyView(for responder: NSResponder?) -> GhosttyNSView? {
guard let responder else { return nil }
if let ghosttyView = responder as? GhosttyNSView {
@ -8428,6 +8455,23 @@ private extension NSWindow {
return true
}
// Support custom tmux prefixes (for example Cmd+C): when the terminal is focused
// and no app-level shortcut matched, prefer forwarding Command-key input to the
// terminal rather than consuming it as a menu key equivalent.
if let ghosttyView = firstResponderGhosttyView,
shouldRouteTerminalCommandShortcutToGhostty(
flags: event.modifierFlags,
chars: event.charactersIgnoringModifiers ?? "",
keyCode: event.keyCode,
terminalHasSelection: ghosttyView.terminalSurface?.hasSelection() ?? false
) {
ghosttyView.keyDown(with: event)
#if DEBUG
dlog(" → ghostty command passthrough")
#endif
return true
}
// When the terminal is focused, skip the full NSWindow.performKeyEquivalent
// (which walks the SwiftUI content view hierarchy) and dispatch Command-key
// events directly to the main menu. This avoids the broken SwiftUI focus path.

View file

@ -2228,6 +2228,71 @@ final class BrowserZoomShortcutRoutingPolicyTests: XCTestCase {
}
}
final class TerminalCommandShortcutRoutingPolicyTests: XCTestCase {
func testRoutesCommandCToTerminalWhenNoSelection() {
XCTAssertTrue(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.command],
chars: "c",
keyCode: 8, // kVK_ANSI_C
terminalHasSelection: false
)
)
}
func testKeepsCommandCCopyMenuRoutedWhenSelectionExists() {
XCTAssertFalse(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.command],
chars: "c",
keyCode: 8, // kVK_ANSI_C
terminalHasSelection: true
)
)
}
func testKeepsCommandCommaMenuRoutedForPreferences() {
XCTAssertFalse(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.command],
chars: ",",
keyCode: 43, // kVK_ANSI_Comma
terminalHasSelection: false
)
)
}
func testRequiresCommandModifier() {
XCTAssertFalse(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.control],
chars: "c",
keyCode: 8,
terminalHasSelection: false
)
)
}
func testRoutesOtherCommandShortcutsToTerminal() {
XCTAssertTrue(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.command, .option],
chars: "c",
keyCode: 8,
terminalHasSelection: false
)
)
XCTAssertTrue(
shouldRouteTerminalCommandShortcutToGhostty(
flags: [.command],
chars: "v",
keyCode: 9, // kVK_ANSI_V
terminalHasSelection: false
)
)
}
}
final class GhosttyResponderResolutionTests: XCTestCase {
private final class FocusProbeView: NSView {
override var acceptsFirstResponder: Bool { true }