Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,25 @@ final class GhosttySurfaceView: NSView, Identifiable {
.fileURL,
.URL,
]
private static let layoutIndependentControlCharactersByKeyCode: [UInt16: String] = {
let candidates = "abcdefghijklmnopqrstuvwxyz0123456789[]\\;',./-=`"
var map: [UInt16: String] = [:]
for scalar in candidates.unicodeScalars {
guard let keyCode = AppShortcutOverride.keyCode(for: Character(scalar)) else { continue }
map[keyCode] = String(scalar)
}
map[UInt16(kVK_Space)] = " "
return map
}()

static func layoutIndependentControlCharacter(
keyCode: UInt16,
modifiers: NSEvent.ModifierFlags
) -> String? {
let shortcutModifiers = modifiers.intersection([.shift, .control, .option, .command])
guard shortcutModifiers == [.control] else { return nil }
return layoutIndependentControlCharactersByKeyCode[keyCode]
}

static func normalizedWorkingDirectoryPath(_ path: String) -> String {
var normalized = path
Expand Down Expand Up @@ -1514,7 +1533,10 @@ final class GhosttySurfaceView: NSView, Identifiable {
translationMods: resolvedMods,
composing: composing
)
let finalText = text ?? ghosttyCharacters(resolvedEvent)
let finalText =
Self.layoutIndependentControlCharacter(keyCode: event.keyCode, modifiers: event.modifierFlags)
?? text
?? ghosttyCharacters(resolvedEvent)
if let finalText, !finalText.isEmpty,
let codepoint = finalText.utf8.first, codepoint >= 0x20
{
Expand Down Expand Up @@ -1607,7 +1629,11 @@ final class GhosttySurfaceView: NSView, Identifiable {
keyEvent.consumed_mods = ghosttyMods(translationMods.subtracting([.control, .command]))
keyEvent.unshifted_codepoint = 0
if event.type == .keyDown || event.type == .keyUp {
if let chars = event.characters(byApplyingModifiers: []),
if let chars = Self.layoutIndependentControlCharacter(keyCode: event.keyCode, modifiers: originalMods),
let codepoint = chars.unicodeScalars.first
{
keyEvent.unshifted_codepoint = codepoint.value
} else if let chars = event.characters(byApplyingModifiers: []),
let codepoint = chars.unicodeScalars.first
{
keyEvent.unshifted_codepoint = codepoint.value
Expand Down
52 changes: 52 additions & 0 deletions supacodeTests/GhosttySurfaceViewTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import Carbon
import Foundation
import GhosttyKit
import SupacodeSettingsShared
Expand Down Expand Up @@ -80,6 +81,57 @@ struct GhosttySurfaceViewTests {
#expect(suppression.isExpired(at: 11.1))
}

@Test func layoutIndependentControlCharacterUsesPhysicalUSQwertyKey() {
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_C),
modifiers: [.control]
) == "c"
)
}

@Test func layoutIndependentControlCharacterIncludesTerminalPunctuationAndSpace() {
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_Slash),
modifiers: [.control]
) == "/"
)
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_Space),
modifiers: [.control]
) == " "
)
}

@Test func layoutIndependentControlCharacterRequiresPlainControl() {
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_C),
modifiers: []
) == nil
)
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_C),
modifiers: [.control, .shift]
) == nil
)
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_C),
modifiers: [.control, .command]
) == nil
)
#expect(
GhosttySurfaceView.layoutIndependentControlCharacter(
keyCode: UInt16(kVK_ANSI_C),
modifiers: [.control, .option]
) == nil
)
}

private static func keyEvent(
chars: String,
ignoringModifiers: String,
Expand Down
Loading