vChewing-macOS/Source/Modules/ControllerModules/NSEventExtension.swift

353 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// (c) 2022 and onwards The vChewing Project (MIT-NTL License).
// ====================
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
// ... with NTL restriction stating that:
// No trademark license is granted to use the trade names, trademarks, service
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import Cocoa
// MARK: - NSEvent Extension - Reconstructors
extension NSEvent {
public func reinitiate(
with type: NSEvent.EventType? = nil,
location: NSPoint? = nil,
modifierFlags: NSEvent.ModifierFlags? = nil,
timestamp: TimeInterval? = nil,
windowNumber: Int? = nil,
characters: String? = nil,
charactersIgnoringModifiers: String? = nil,
isARepeat: Bool? = nil,
keyCode: UInt16? = nil
) -> NSEvent? {
let oldChars: String = {
if self.type == .flagsChanged { return "" }
return self.characters ?? ""
}()
return NSEvent.keyEvent(
with: type ?? self.type,
location: location ?? locationInWindow,
modifierFlags: modifierFlags ?? self.modifierFlags,
timestamp: timestamp ?? self.timestamp,
windowNumber: windowNumber ?? self.windowNumber,
context: nil,
characters: characters ?? oldChars,
charactersIgnoringModifiers: charactersIgnoringModifiers ?? characters ?? oldChars,
isARepeat: isARepeat ?? self.isARepeat,
keyCode: keyCode ?? self.keyCode
)
}
/// Emacs NSEvent NSEvent NSEvent
/// - Parameter isVerticalTyping:
/// - Returns:
public func convertFromEmacKeyEvent(isVerticalContext: Bool) -> NSEvent {
guard isEmacsKey else { return self }
let newKeyCode: UInt16 = {
switch isVerticalContext {
case false: return IME.vChewingEmacsKey.charKeyMapHorizontal[charCode] ?? 0
case true: return IME.vChewingEmacsKey.charKeyMapVertical[charCode] ?? 0
}
}()
guard newKeyCode != 0 else { return self }
let newCharScalar: Unicode.Scalar = {
switch charCode {
case 6:
return isVerticalContext
? NSEvent.SpecialKey.downArrow.unicodeScalar : NSEvent.SpecialKey.rightArrow.unicodeScalar
case 2:
return isVerticalContext
? NSEvent.SpecialKey.upArrow.unicodeScalar : NSEvent.SpecialKey.leftArrow.unicodeScalar
case 1: return NSEvent.SpecialKey.home.unicodeScalar
case 5: return NSEvent.SpecialKey.end.unicodeScalar
case 4: return NSEvent.SpecialKey.deleteForward.unicodeScalar // Use "deleteForward" for PC delete.
case 22: return NSEvent.SpecialKey.pageDown.unicodeScalar
default: return .init(0)
}
}()
let newChar = String(newCharScalar)
return reinitiate(modifierFlags: [], characters: newChar, charactersIgnoringModifiers: newChar, keyCode: newKeyCode)
?? self
}
}
// MARK: - NSEvent Extension - InputSignalProtocol
extension NSEvent: InputSignalProtocol {
public var isASCIIModeInput: Bool { ctlInputMethod.isASCIIModeSituation }
public var isTypingVertical: Bool { ctlInputMethod.isVerticalTypingSituation }
public var text: String { AppleKeyboardConverter.cnvStringApple2ABC(characters ?? "") }
public var inputTextIgnoringModifiers: String? {
guard let charIgnoringModifiers = charactersIgnoringModifiers else { return nil }
return AppleKeyboardConverter.cnvStringApple2ABC(charIgnoringModifiers)
}
public var charCode: UInt16 {
guard type != .flagsChanged else { return 0 }
guard characters != nil else { return 0 }
// count > 0!isEmpty滿
guard !text.isEmpty else { return 0 }
let scalars = text.unicodeScalars
let result = scalars[scalars.startIndex].value
return result <= UInt16.max ? UInt16(result) : UInt16.max
}
public var isFlagChanged: Bool { type == .flagsChanged }
public var isEmacsKey: Bool {
// isControlHold
[6, 2, 1, 5, 4, 22].contains(charCode) && modifierFlags == .control
}
// Alt+Shift+ macOS
// KeyCode
//
public var mainAreaNumKeyChar: String? { mapMainAreaNumKey[keyCode] }
// ANSI charCode Swift KeyHandler
public var isInvalid: Bool {
(0x20...0xFF).contains(charCode) ? false : !(isReservedKey && !isKeyCodeBlacklisted)
}
public var isKeyCodeBlacklisted: Bool {
guard let code = KeyCodeBlackListed(rawValue: keyCode) else { return false }
return code.rawValue != KeyCode.kNone.rawValue
}
public var isReservedKey: Bool {
guard let code = KeyCode(rawValue: keyCode) else { return false }
return code.rawValue != KeyCode.kNone.rawValue
}
public var isCandidateKey: Bool {
mgrPrefs.candidateKeys.contains(text)
|| mgrPrefs.candidateKeys.contains(inputTextIgnoringModifiers ?? "114514")
}
/// flags KeyCode
public var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
public var isMainAreaNumKey: Bool { arrMainAreaNumKey.contains(keyCode) }
public var isShiftHold: Bool { modifierFlags.contains([.shift]) }
public var isCommandHold: Bool { modifierFlags.contains([.command]) }
public var isControlHold: Bool { modifierFlags.contains([.control]) }
public var isControlHotKey: Bool { modifierFlags.contains([.control]) && text.first?.isLetter ?? false }
public var isOptionHold: Bool { modifierFlags.contains([.option]) }
public var isOptionHotKey: Bool { modifierFlags.contains([.option]) && text.first?.isLetter ?? false }
public var isCapsLockOn: Bool { modifierFlags.contains([.capsLock]) }
public var isFunctionKeyHold: Bool { modifierFlags.contains([.function]) }
public var isNonLaptopFunctionKey: Bool { modifierFlags.contains([.numericPad]) && !isNumericPadKey }
public var isEnter: Bool { [KeyCode.kCarriageReturn, KeyCode.kLineFeed].contains(KeyCode(rawValue: keyCode)) }
public var isTab: Bool { KeyCode(rawValue: keyCode) == KeyCode.kTab }
public var isUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kUpArrow }
public var isDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kDownArrow }
public var isLeft: Bool { KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow }
public var isRight: Bool { KeyCode(rawValue: keyCode) == KeyCode.kRightArrow }
public var isPageUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageUp }
public var isPageDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageDown }
public var isSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kSpace }
public var isBackSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kBackSpace }
public var isEsc: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEscape }
public var isHome: Bool { KeyCode(rawValue: keyCode) == KeyCode.kHome }
public var isEnd: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEnd }
public var isDelete: Bool { KeyCode(rawValue: keyCode) == KeyCode.kWindowsDelete }
public var isCursorBackward: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kUpArrow
: KeyCode(rawValue: keyCode) == .kLeftArrow
}
public var isCursorForward: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kDownArrow
: KeyCode(rawValue: keyCode) == .kRightArrow
}
public var isCursorClockRight: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kRightArrow
: KeyCode(rawValue: keyCode) == .kUpArrow
}
public var isCursorClockLeft: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kLeftArrow
: KeyCode(rawValue: keyCode) == .kDownArrow
}
public var isASCII: Bool { charCode < 0x80 }
// flags == .shift Shift
public var isUpperCaseASCIILetterKey: Bool {
(65...90).contains(charCode) && modifierFlags == .shift
}
// KeyCode macOS Apple
// ![input isShiftHold] 使 Shift
public var isSymbolMenuPhysicalKey: Bool {
[KeyCode.kSymbolMenuPhysicalKeyIntl, KeyCode.kSymbolMenuPhysicalKeyJIS].contains(KeyCode(rawValue: keyCode))
}
}
// MARK: - InputSignalProtocol
public protocol InputSignalProtocol {
var isASCIIModeInput: Bool { get }
var isTypingVertical: Bool { get }
var text: String { get }
var inputTextIgnoringModifiers: String? { get }
var charCode: UInt16 { get }
var keyCode: UInt16 { get }
var isFlagChanged: Bool { get }
var mainAreaNumKeyChar: String? { get }
var isASCII: Bool { get }
var isInvalid: Bool { get }
var isKeyCodeBlacklisted: Bool { get }
var isReservedKey: Bool { get }
var isCandidateKey: Bool { get }
var isNumericPadKey: Bool { get }
var isMainAreaNumKey: Bool { get }
var isShiftHold: Bool { get }
var isCommandHold: Bool { get }
var isControlHold: Bool { get }
var isControlHotKey: Bool { get }
var isOptionHold: Bool { get }
var isOptionHotKey: Bool { get }
var isCapsLockOn: Bool { get }
var isFunctionKeyHold: Bool { get }
var isNonLaptopFunctionKey: Bool { get }
var isEnter: Bool { get }
var isTab: Bool { get }
var isUp: Bool { get }
var isDown: Bool { get }
var isLeft: Bool { get }
var isRight: Bool { get }
var isPageUp: Bool { get }
var isPageDown: Bool { get }
var isSpace: Bool { get }
var isBackSpace: Bool { get }
var isEsc: Bool { get }
var isHome: Bool { get }
var isEnd: Bool { get }
var isDelete: Bool { get }
var isCursorBackward: Bool { get }
var isCursorForward: Bool { get }
var isCursorClockRight: Bool { get }
var isCursorClockLeft: Bool { get }
var isUpperCaseASCIILetterKey: Bool { get }
var isSymbolMenuPhysicalKey: Bool { get }
}
// MARK: - Enums of Constants
// Use KeyCodes as much as possible since its recognition won't be affected by macOS Base Keyboard Layouts.
// KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
public enum KeyCode: UInt16 {
case kNone = 0
case kCarriageReturn = 36 // Renamed from "kReturn" to avoid nomenclatural confusions.
case kTab = 48
case kSpace = 49
case kSymbolMenuPhysicalKeyIntl = 50 // vChewing Specific (Non-JIS)
case kBackSpace = 51 // Renamed from "kDelete" to avoid nomenclatural confusions.
case kEscape = 53
case kCommand = 55
case kShift = 56
case kCapsLock = 57
case kOption = 58
case kControl = 59
case kRightShift = 60
case kRightOption = 61
case kRightControl = 62
case kFunction = 63
case kF17 = 64
case kVolumeUp = 72
case kVolumeDown = 73
case kMute = 74
case kLineFeed = 76 // Another keyCode to identify the Enter Key, typable by Fn+Enter.
case kF18 = 79
case kF19 = 80
case kF20 = 90
case kSymbolMenuPhysicalKeyJIS = 94 // vChewing Specific (JIS)
case kF5 = 96
case kF6 = 97
case kF7 = 98
case kF3 = 99
case kF8 = 100
case kF9 = 101
case kF11 = 103
case kF13 = 105 // PrtSc
case kF16 = 106
case kF14 = 107
case kF10 = 109
case kF12 = 111
case kF15 = 113
case kHelp = 114 // Insert
case kHome = 115
case kPageUp = 116
case kWindowsDelete = 117 // Renamed from "kForwardDelete" to avoid nomenclatural confusions.
case kF4 = 118
case kEnd = 119
case kF2 = 120
case kPageDown = 121
case kF1 = 122
case kLeftArrow = 123
case kRightArrow = 124
case kDownArrow = 125
case kUpArrow = 126
}
enum KeyCodeBlackListed: UInt16 {
case kF17 = 64
case kVolumeUp = 72
case kVolumeDown = 73
case kMute = 74
case kF18 = 79
case kF19 = 80
case kF20 = 90
case kF5 = 96
case kF6 = 97
case kF7 = 98
case kF3 = 99
case kF8 = 100
case kF9 = 101
case kF11 = 103
case kF13 = 105 // PrtSc
case kF16 = 106
case kF14 = 107
case kF10 = 109
case kF12 = 111
case kF15 = 113
case kHelp = 114 // Insert
case kF4 = 118
case kF2 = 120
case kF1 = 122
}
// Alt+Shift+ macOS
// KeyCode
let mapMainAreaNumKey: [UInt16: String] = [
18: "1", 19: "2", 20: "3", 21: "4", 23: "5", 22: "6", 26: "7", 28: "8", 25: "9", 29: "0",
]
/// KeyCode
///
/// 95 Key Code JIS
let arrNumpadKeyCodes: [UInt16] = [65, 67, 69, 71, 75, 78, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 92, 95]
/// KeyCode
let arrMainAreaNumKey: [UInt16] = [18, 19, 20, 21, 22, 23, 25, 26, 28, 29]
// CharCodes: https://theasciicode.com.ar/ascii-control-characters/horizontal-tab-ascii-code-9.html
enum CharCode: UInt16 {
case yajuusenpaiA = 114
case yajuusenpaiB = 514
case yajuusenpaiC = 1919
case yajuusenpaiD = 810
// CharCode is not reliable at all. KeyCode is the most appropriate choice due to its accuracy.
// KeyCode doesn't give a phuque about the character sent through macOS keyboard layouts ...
// ... but only focuses on which physical key is pressed.
}