From 253c1966848910f828ee38047e3e6d3df1c80ce8 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Sat, 28 Feb 2026 23:14:22 -0800 Subject: [PATCH] ok --- Sources/AppDelegate.swift | 44 +++++++++++++ cmuxTests/CmuxWebViewKeyEquivalentTests.swift | 65 +++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/Sources/AppDelegate.swift b/Sources/AppDelegate.swift index 9116527e040e..f272184ba8cc 100644 --- a/Sources/AppDelegate.swift +++ b/Sources/AppDelegate.swift @@ -803,6 +803,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 { @@ -7662,6 +7689,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. diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 3a90c8a116f9..98d63f8d6c53 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -2142,6 +2142,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 }