Skip to content
Closed
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
4 changes: 3 additions & 1 deletion Core/Sources/ConverterServer/ConverterServer+Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ extension ConverterServer {
.init(title: "British", value: .string(Config.KeyboardLayout.Value.british.rawValue)),
.init(title: "Colemak", value: .string(Config.KeyboardLayout.Value.colemak.rawValue)),
.init(title: "Dvorak", value: .string(Config.KeyboardLayout.Value.dvorak.rawValue)),
.init(title: "Dvorak - QWERTY Command", value: .string(Config.KeyboardLayout.Value.dvorakQwertyCommand.rawValue))
.init(title: "Dvorak - QWERTY Command", value: .string(Config.KeyboardLayout.Value.dvorakQwertyCommand.rawValue)),
.init(title: "ProgrammerDvorak_JIS - QWERTY Command", value: .string(Config.KeyboardLayout.Value.programmerDvorakJIS.rawValue)),
.init(title: "ProgrammerDvorak_US - QWERTY Command", value: .string(Config.KeyboardLayout.Value.programmerDvorakUS.rawValue))
]),
value: .string(Config.KeyboardLayout().value.rawValue)
),
Expand Down
122 changes: 122 additions & 0 deletions Core/Sources/Core/Configs/CustomCodableConfigItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,22 @@ extension Config {
case colemak
case dvorak
case dvorakQwertyCommand
case programmerDvorakJIS
case programmerDvorakUS

public var layoutIdentifier: String {
/// 【独自の keyRemapTable を持つ配列を追加する際の注意】
/// 1. 対象キーボードに応じた識別子を返却し、それを基準に辞書を作成すること
/// OSはここで指定された識別子に従って論理キーを生成し、azooKeyに渡します。
/// JISキーボード用として追加する場合は「com.apple.keylayout.Romaji」を、
/// USキーボード用として追加する場合は「com.apple.keylayout.US」を必ず返却してください。
/// その上で、追加する配列の変換辞書(keyRemapTable)は、
/// この戻り値によってOSから送られてくる論理文字を前提として記述する必要があります。
/// 2. 必ずJIS用とUS用の2つの独立した case を用意し、1つに統合しないこと
/// 独自の変換マップ(keyRemapTable)を持つ配列は、記号出力をOSに委ねる標準配列とは異なり、
/// ベースとなるJIS/USの前提が一致しないと出力が完全に破綻します。
/// OSによるキーボードのJIS/US誤認識が発生した際、ユーザーが手動で正しい配列を
/// 選択して回避できる手段を残しておく必要があるため、内部での自動判定は避けてください。
switch self {
case .qwerty:
return "com.apple.keylayout.US"
Expand All @@ -197,6 +211,114 @@ extension Config {
return "com.apple.keylayout.Dvorak"
case .dvorakQwertyCommand:
return "com.apple.keylayout.DVORAK-QWERTYCMD"
case .programmerDvorakJIS:
return "com.apple.keylayout.Romaji"
case .programmerDvorakUS:
return "com.apple.keylayout.US"
}
}

public var hasDistinctBackSlashAndYen: Bool {
/// 【独自の keyRemapTable を追加した配列専用の設定】
/// 「¥」と「\」が両方存在する場合にtrueを返却する処理を追加すること
switch self {
case .programmerDvorakJIS:
return true
default:
return false
}
}

public func jisZeroKeyOutput(isShiftPressed: Bool) -> String? {
/// 【独自の keyRemapTable を追加した配列専用の設定】
/// JISに於いて「0」はShift押下有無にかかわらず常に "0" が送られる。
/// Shiftの有無を区別して変換できないため、isShiftPressedに応じた文字返却をして下さい。
switch self {
case .programmerDvorakJIS:
if isShiftPressed {
return "6"
} else {
return "]"
}
default:
// QWERTYなど対象外の配列は nil を返し、元の処理(default: での辞書翻訳など)へ流す
return nil
}
}

public func jisUnderscoreKeyOutput(isShiftPressed: Bool, isOptionPressed: Bool, typeBackSlash: Bool) -> String? {
/// 【独自の keyRemapTable を追加した配列専用の設定】
/// JISに於いて「_」はShift押下有無にかかわらず常に "_" が送られる。
/// Shiftの有無を区別して変換できないため、isShiftPressedに応じた文字返却をして下さい。
switch self {
case .programmerDvorakJIS:
if isShiftPressed {
return "|"
} else if isOptionPressed {
// Option押下時は標準仕様との整合性を取るため、通常時と出力を逆転させる
if typeBackSlash {
// 設定ONの時、通常時は「¥」だが、Option時は本来の「\」を出力
return "\\"
} else {
// 設定OFFの時、通常時は「\」だが、Option時は「¥」を出力
return "¥"
}
} else {
// 通常時 (Unshifted)
if typeBackSlash {
// 「¥」と「\」の専用キーが両方存在するため、
// 設定がONの場合は、本来の文字「\」ではなく「¥」を出力
return "¥"
} else {
// 設定がOFFの場合は、本来の文字「\」を出力
return "\\"
}
}
default:
// QWERTYなど対象外の配列は nil を返し、元の処理(default: での辞書翻訳など)へ流す
return nil
}
}

public var keyRemapTable: [Character: Character]? {
/// 【独自配列用の論理キー変換辞書】
/// 論理キーを別の文字に変換するマッピング辞書です。
/// OSから送られてくる論理文字をキー(Key)とし、配列特有の変換後の文字を値(Value)として定義してください。
/// ※ JISの「_」や「0」など、OS仕様でShiftの区別ができないキーはここでは変換できません。専用関数で処理してください。
switch self {
case .programmerDvorakJIS:
return [
// JIS配列 Unshifted
"1":"&", "2":"[", "3":"{", "4":"}", "5":"(", "6":"=", "7":"*", "8":")", "9":"+", "0":"]",
"q":";", "w":",", "e":".", "r":"p", "t":"y", "y":"f", "u":"g", "i":"c", "o":"r", "p":"l", "@":"/", "[":"@",
"a":"a", "s":"o", "d":"e", "f":"u", "g":"i", "h":"d", "j":"h", "k":"t", "l":"n", ";":"s", ":":"-", "]":"$",
"z":"'", "x":"q", "c":"j", "v":"k", "b":"x", "n":"b", "m":"m", ",":"w", ".":"v", "/":"z", "\\":"\\",
"-":"!", "^":"#", "¥":"¥",

// JIS配列 Shifted
"!":"%", "\"":"7", "#":"5", "$":"3", "%":"1", "&":"9", "'":"0", "(":"2", ")":"4", "=":"8", "~":"`", "|":"|",
"Q":":", "W":"<", "E":">", "R":"P", "T":"Y", "Y":"F", "U":"G", "I":"C", "O":"R", "P":"L", "`":"?", "{":"^",
"A":"A", "S":"O", "D":"E", "F":"U", "G":"I", "H":"D", "J":"H", "K":"T", "L":"N", "+":"S", "*":"_", "}":"~",
"Z":"\"", "X":"Q", "C":"J", "V":"K", "B":"X", "N":"B", "M":"M", "<":"W", ">":"V", "?":"Z"
]
case .programmerDvorakUS:
return [
// US配列 Unshifted
"1":"&", "2":"[", "3":"{", "4":"}", "5":"(", "6":"=", "7":"*", "8":")", "9":"+", "0":"]",
"q":";", "w":",", "e":".", "r":"p", "t":"y", "y":"f", "u":"g", "i":"c", "o":"r", "p":"l", "[":"/", "]":"@",
"a":"a", "s":"o", "d":"e", "f":"u", "g":"i", "h":"d", "j":"h", "k":"t", "l":"n", ";":"s", "'":"-",
"z":"'", "x":"q", "c":"j", "v":"k", "b":"x", "n":"b", "m":"m", ",":"w", ".":"v", "/":"z", "\\":"\\",
"-":"!", "=":"#",

// US配列 Shifted
"!":"%", "@":"7", "#":"5", "$":"3", "%":"1", "^":"9", "&":"0", "*":"2", "(":"4", ")":"6", "_":"8", "+":"`",
"Q":":", "W":"<", "E":">", "R":"P", "T":"Y", "Y":"F", "U":"G", "I":"C", "O":"R", "P":"L", "{":"?", "}":"^",
"A":"A", "S":"O", "D":"E", "F":"U", "G":"I", "H":"D", "J":"H", "K":"T", "L":"N", ":":"S", "\"":"_",
"Z":"\"", "X":"Q", "C":"J", "V":"K", "B":"X", "N":"B", "M":"M", "<":"W", ">":"V", "?":"Z", "|":"|"
]
default:
// QWERTYなどの標準配列
return nil
}
}
}
Expand Down
143 changes: 109 additions & 34 deletions Core/Sources/Core/InputUtils/Actions/UserAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,35 +47,68 @@ public enum UserAction {
}
}

private var currentLayout: Config.KeyboardLayout.Value {
return Config.KeyboardLayout().value
}

public var inputPiece: InputPiece {
switch self {
case .one: .character("1")
case .two: .character("2")
case .three: .character("3")
case .four: .character("4")
case .five: .character("5")
case .six: .character("6")
case .seven: .character("7")
case .eight: .character("8")
case .nine: .character("9")
case .zero: .character("0")
case .shiftZero: .key(intention: "0", input: "0", modifiers: [.shift])
switch currentLayout {
case .programmerDvorakJIS, .programmerDvorakUS:
switch self {
case .one: return .character("&")
case .two: return .character("[")
case .three: return .character("{")
case .four: return .character("}")
case .five: return .character("(")
case .six: return .character("=")
case .seven: return .character("*")
case .eight: return .character(")")
case .nine: return .character("+")
case .zero: return .character("]")
case .shiftZero: return .key(intention: "6", input: "6", modifiers: [.shift])
}
default:
switch self {
case .one: return .character("1"); case .two: return .character("2")
case .three: return .character("3"); case .four: return .character("4")
case .five: return .character("5"); case .six: return .character("6")
case .seven: return .character("7"); case .eight: return .character("8")
case .nine: return .character("9"); case .zero: return .character("0")
case .shiftZero: return .key(intention: "0", input: "0", modifiers: [.shift])
}
}
}

public var inputString: String {
switch self {
case .one: "1"
case .two: "2"
case .three: "3"
case .four: "4"
case .five: "5"
case .six: "6"
case .seven: "7"
case .eight: "8"
case .nine: "9"
case .zero: "0"
case .shiftZero: "0"
switch currentLayout {
case .programmerDvorakJIS, .programmerDvorakUS:
switch self {
case .one: "&"
case .two: "["
case .three: "{"
case .four: "}"
case .five: "("
case .six: "="
case .seven: "*"
case .eight: ")"
case .nine: "+"
case .zero: "]"
case .shiftZero: "6"
}
default:
switch self {
case .one: "1"
case .two: "2"
case .three: "3"
case .four: "4"
case .five: "5"
case .six: "6"
case .seven: "7"
case .eight: "8"
case .nine: "9"
case .zero: "0"
case .shiftZero: "0"
}
}
}
}
Expand Down Expand Up @@ -104,7 +137,6 @@ public enum UserAction {
return KeyMap.h2zMap(c)
}
}

// この種のコードは複雑にしかならないので、lintを無効にする
// swiftlint:disable:next cyclomatic_complexity
public static func getUserAction(
Expand All @@ -114,6 +146,7 @@ public enum UserAction {
) -> UserAction {
let typeBackSlash = typeBackSlash ?? Config.TypeBackSlash().value
// see: https://developer.mozilla.org/ja/docs/Web/API/UI_Events/Keyboard_event_code_values#mac_%E3%81%A7%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E5%80%A4
let currentLayout = Config.KeyboardLayout().value
func keyMap(_ string: String, invertPunctuation: Bool = false) -> [InputPiece] {
switch inputLanguage {
case .english:
Expand All @@ -126,10 +159,16 @@ public enum UserAction {
}

// Resolve action based on logical key character (ignoring modifiers)
if let logicalKey = eventCore.charactersIgnoringModifiers?.lowercased() {
if let originalLogicalKey = eventCore.charactersIgnoringModifiers?.lowercased() {
let logicalKey: String
if let firstChar = originalLogicalKey.first, let mappedChar = currentLayout.keyRemapTable?[firstChar] {
logicalKey = String(mappedChar).lowercased()
} else {
logicalKey = originalLogicalKey
}
switch (logicalKey, eventCore.modifierFlags) {
case (let key, [.option])
where DiacriticAttacher.deadKeyList.contains(key) && inputLanguage == .english:
where DiacriticAttacher.deadKeyList.contains(key) && inputLanguage == .english:
return .deadKey(key)

case ("h", [.control]): // Control + h
Expand Down Expand Up @@ -164,18 +203,28 @@ public enum UserAction {
return .startUnicodeInput

case ("¥", [.shift, .option]), ("¥", [.shift]), ("\\", [.shift, .option]), ("\\", [.shift]):
if currentLayout.keyRemapTable != nil { break }
return .input(keyMap("|"))
case ("¥", []), ("\\", []):
return if typeBackSlash {
.input(keyMap("\\"))
if currentLayout.hasDistinctBackSlashAndYen {
// 「¥」と「\」が両方存在する場合は、
// typeBackSlash設定がONの時のみ「¥」「\」のキーを入れ替える
let isYen = (logicalKey == "¥")
return typeBackSlash ? .input(keyMap(isYen ? "\\" : "¥")) : .input(keyMap(isYen ? "¥" : "\\"))
} else {
.input(keyMap("¥"))
// typeBackSlash設定がONの場合は「\」 OFFの場合は「¥」
return typeBackSlash ? .input(keyMap("\\")) : .input(keyMap("¥"))
}

case ("¥", [.option]), ("\\", [.option]):
return if typeBackSlash {
.input(keyMap("¥"))
if currentLayout.hasDistinctBackSlashAndYen {
// Option押下時は標準の挙動に合わせてUnshifted時と出力を逆転させる。
// typeBackSlash設定がOFFの時のみ「¥」「\」のキーを入れ替える
let isYen = (logicalKey == "¥")
return typeBackSlash ? .input(keyMap(isYen ? "¥" : "\\")) : .input(keyMap(isYen ? "\\" : "¥"))
} else {
.input(keyMap("\\"))
// typeBackSlash設定がONの場合は「¥」 OFFの場合は「\」
return typeBackSlash ? .input(keyMap("¥")) : .input(keyMap("\\"))
}

case ("/", [.shift, .option]) where inputLanguage == .japanese:
Expand Down Expand Up @@ -208,6 +257,26 @@ public enum UserAction {
return .unknown
}

if eventCore.keyCode == 29, let text = currentLayout.jisZeroKeyOutput(isShiftPressed:eventCore.modifierFlags.contains(.shift)) {
// 29番キー(JIS ゼロ)のオーバーライド処理
// OS仕様(シフト有無によらず常に「0」)を回避し、対象配列のみ出力を上書きする。
// switch内の fallthrough (case 18...29) との干渉を避け、
// 対象外の配列を安全にdefaultへ流すためswitch手前でフックする。
return .input(keyMap(text))
}

if eventCore.keyCode == 94, let text = currentLayout.jisUnderscoreKeyOutput(
isShiftPressed: eventCore.modifierFlags.contains(.shift),
isOptionPressed: eventCore.modifierFlags.contains(.option),
typeBackSlash: typeBackSlash
) {
// 94番キー(JIS アンダースコア)のオーバーライド処理
// OS仕様(シフト有無によらず常に「_」)を回避し、対象配列のみ出力を上書きする。
// switch内の fallthrough (case 18...29) との干渉を避け、
// 対象外の配列を安全にdefaultへ流すためswitch手前でフックする。
return .input(keyMap(text))
}

// Resolve action based on physical key code
switch eventCore.keyCode {
case 0x24, 0x4C: // Enter (0x24) and Numpad Enter (0x4C)
Expand Down Expand Up @@ -287,7 +356,13 @@ public enum UserAction {
}
default:
if let text = eventCore.characters, isPrintable(text) {
return .input(keyMap(text))
let mappedText: String
if let remapTable = currentLayout.keyRemapTable {
mappedText = String(text.map { remapTable[$0] ?? $0 })
} else {
mappedText = text
}
return .input(keyMap(mappedText))
} else {
return .unknown
}
Expand Down