Shared // Implementing KBEvent.

This commit is contained in:
ShikiSuen 2024-01-18 20:48:20 +08:00
parent 5e1208bc5e
commit 23ef3124d4
10 changed files with 575 additions and 82 deletions

View File

@ -13,14 +13,12 @@ let package = Package(
),
],
dependencies: [
.package(path: "../vChewing_IMKUtils"),
.package(path: "../vChewing_SwiftExtension"),
],
targets: [
.target(
name: "CocoaExtension",
dependencies: [
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
]
),

View File

@ -7,7 +7,6 @@
// requirements defined in MIT License.
import AppKit
import InputMethodKit
public extension NSWindowController {
func orderFront() {

View File

@ -1076,7 +1076,7 @@ extension InputHandler {
// !input.isFunctionKeyHold
//
let notEmpty = state.hasComposition && !compositor.isEmpty && isComposerOrCalligrapherEmpty
let bannedModifiers: NSEvent.ModifierFlags = [.option, .shift, .command, .control]
let bannedModifiers: KBEvent.ModifierFlags = [.option, .shift, .command, .control]
let noBannedModifiers = bannedModifiers.intersection(input.keyModifierFlags).isEmpty
var triggered = input.isCursorClockLeft || input.isCursorClockRight
triggered = triggered || (input.isSpace && prefs.chooseCandidateUsingSpace)

View File

@ -32,7 +32,7 @@ public class SessionCtl: IMKInputController {
public static var areWeNerfing = false
///
public var previouslyHandledEvents: [NSEvent] = .init()
public var previouslyHandledEvents = [KBEvent]()
///
public var candidateUI: CtlCandidateProtocol?

View File

@ -40,20 +40,19 @@ public extension SessionCtl {
return true
}
var result = false
if [.keyDown, .flagsChanged].contains(event.type) {
result = handleKeyDown(event: event)
if result, event.type == .keyDown {
previouslyHandledEvents.append(event)
}
} else if event.type == .keyUp {
result = handleKeyUp(event: event)
}
guard let newEvent = event.copyAsKBEvent else { return false }
return result
switch newEvent.type {
case .flagsChanged: return handleKeyDown(event: newEvent)
case .keyDown:
let result = handleKeyDown(event: newEvent)
if result { previouslyHandledEvents.append(newEvent) }
return result
case .keyUp: return handleKeyUp(event: newEvent)
}
}
private func handleKeyUp(event: NSEvent) -> Bool {
private func handleKeyUp(event: KBEvent) -> Bool {
guard ![.ofEmpty, .ofAbortion].contains(state.type) else { return false }
let codes = previouslyHandledEvents.map(\.keyCode)
if codes.contains(event.keyCode) {
@ -65,7 +64,7 @@ public extension SessionCtl {
return false
}
private func handleKeyDown(event: NSEvent) -> Bool {
private func handleKeyDown(event: KBEvent) -> Bool {
// MARK:
// .command 簿
@ -131,7 +130,7 @@ public extension SessionCtl {
//
if event.isUp || event.isDown || event.isLeft || event.isRight {
updateVerticalTypingStatus() //
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal")
}
// 使 NSEvent Emacs NSEvent NSEvent
@ -158,12 +157,12 @@ public extension SessionCtl {
if eventToDeal.isNumericPadKey,
let eventCharConverted = eventToDeal.characters?.applyingTransformFW2HW(reverse: false)
{
eventToDeal = eventToDeal.reinitiate(characters: eventCharConverted) ?? eventToDeal
eventToDeal = eventToDeal.reinitiate(characters: eventCharConverted)
} else if [.ofEmpty, .ofInputting].contains(state.type), eventToDeal.isMainAreaNumKey,
!eventToDeal.isCommandHold, !eventToDeal.isControlHold, eventToDeal.isOptionHold
{
// Alt(+Shift)+
eventToDeal = eventToDeal.reinitiate(characters: eventToDeal.mainAreaNumKeyChar) ?? eventToDeal
eventToDeal = eventToDeal.reinitiate(characters: eventToDeal.mainAreaNumKeyChar)
}
//

View File

@ -14,6 +14,7 @@ let package = Package(
],
dependencies: [
.package(path: "../vChewing_CocoaExtension"),
.package(path: "../vChewing_IMKUtils"),
.package(path: "../vChewing_SwiftExtension"),
],
targets: [
@ -21,6 +22,7 @@ let package = Package(
name: "Shared",
dependencies: [
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
]
),

View File

@ -6,42 +6,103 @@
// marks, or product names of Contributor, except as required to fulfill notice
// requirements defined in MIT License.
import AppKit
import Foundation
import IMKUtils
// MARK: - NSEvent Extension - Reconstructors
public struct KBEvent: InputSignalProtocol, Hashable {
public private(set) var type: EventType
public private(set) var modifierFlags: ModifierFlags
public private(set) var timestamp: TimeInterval
public private(set) var windowNumber: Int
public private(set) var characters: String?
public private(set) var charactersIgnoringModifiers: String?
public private(set) var isARepeat: Bool
public private(set) var keyCode: UInt16
public extension NSEvent {
func reinitiate(
with type: NSEvent.EventType? = nil,
location: NSPoint? = nil,
modifierFlags: NSEvent.ModifierFlags? = nil,
public init(
with type: KBEvent.EventType? = nil,
modifierFlags: KBEvent.ModifierFlags? = nil,
timestamp: TimeInterval? = nil,
windowNumber: Int? = nil,
characters: String? = nil,
charactersIgnoringModifiers: String? = nil,
isARepeat: Bool? = nil,
keyCode: UInt16? = nil
) -> NSEvent? {
) {
var characters = characters
checkSpecialKey: if let matchedKey = KeyCode(rawValue: keyCode ?? 0), let flags = modifierFlags {
let scalar = matchedKey.correspondedSpecialKeyScalar(flags: flags)
guard let scalar = scalar else { break checkSpecialKey }
characters = .init(scalar)
}
self.type = type ?? .keyDown
self.modifierFlags = modifierFlags ?? []
self.timestamp = timestamp ?? Date().timeIntervalSince1970
self.windowNumber = windowNumber ?? 0
self.characters = characters ?? ""
self.charactersIgnoringModifiers = charactersIgnoringModifiers ?? characters ?? ""
self.isARepeat = isARepeat ?? false
self.keyCode = keyCode ?? KeyCode.kNone.rawValue
}
public func reinitiate(
with type: KBEvent.EventType? = nil,
modifierFlags: KBEvent.ModifierFlags? = nil,
timestamp: TimeInterval? = nil,
windowNumber: Int? = nil,
characters: String? = nil,
charactersIgnoringModifiers: String? = nil,
isARepeat: Bool? = nil,
keyCode: UInt16? = nil
) -> KBEvent {
let oldChars: String = text
return NSEvent.keyEvent(
with: type ?? self.type,
location: location ?? locationInWindow,
return KBEvent(
with: type ?? .keyDown,
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
// MARK: - KBEvent Extension - SubTypes
public extension KBEvent {
struct ModifierFlags: OptionSet, Hashable {
public init(rawValue: UInt) {
self.rawValue = rawValue
}
public let rawValue: UInt
public static let capsLock = ModifierFlags(rawValue: 1 << 16) // Set if Caps Lock key is pressed.
public static let shift = ModifierFlags(rawValue: 1 << 17) // Set if Shift key is pressed.
public static let control = ModifierFlags(rawValue: 1 << 18) // Set if Control key is pressed.
public static let option = ModifierFlags(rawValue: 1 << 19) // Set if Option or Alternate key is pressed.
public static let command = ModifierFlags(rawValue: 1 << 20) // Set if Command key is pressed.
public static let numericPad = ModifierFlags(rawValue: 1 << 21) // Set if any key in the numeric keypad is pressed.
public static let help = ModifierFlags(rawValue: 1 << 22) // Set if the Help key is pressed.
public static let function = ModifierFlags(rawValue: 1 << 23) // Set if any function key is pressed.
public static let deviceIndependentFlagsMask = ModifierFlags(rawValue: 0xFFFF_0000)
}
enum EventType: UInt8 {
case keyDown = 10
case keyUp = 11
case flagsChanged = 12
}
}
// MARK: - KBEvent Extension - Emacs Key Conversions
public extension KBEvent {
/// Emacs KBEvent KBEvent KBEvent
/// - Parameter isVerticalTyping:
/// - Returns:
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> KBEvent {
guard isEmacsKey else { return self }
let newKeyCode: UInt16 = {
switch isVerticalContext {
@ -50,32 +111,15 @@ public extension NSEvent {
}
}()
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
return reinitiate(modifierFlags: [], characters: nil, charactersIgnoringModifiers: nil, keyCode: newKeyCode)
}
}
// MARK: - NSEvent Extension - InputSignalProtocol
// MARK: - KBEvent Extension - InputSignalProtocol
public extension NSEvent {
public extension KBEvent {
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
/// NSEvent.characters
/// KBEvent.characters
/// - Remark: event.type == .flagsChanged
/// event.characters? NSInternalInconsistencyException
var text: String { isFlagChanged ? "" : characters ?? "" }
@ -98,10 +142,6 @@ public extension NSEvent {
modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
static var keyModifierFlags: ModifierFlags {
Self.modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
var isFlagChanged: Bool { type == .flagsChanged }
var isEmacsKey: Bool {
@ -204,6 +244,94 @@ public extension NSEvent {
// MARK: - Enums of Constants
public extension KBEvent {
enum SpecialKey: UInt16 {
var unicodeScalar: Unicode.Scalar { .init(rawValue) ?? .init(0) }
case upArrow = 0xF700
case downArrow = 0xF701
case leftArrow = 0xF702
case rightArrow = 0xF703
case f1 = 0xF704
case f2 = 0xF705
case f3 = 0xF706
case f4 = 0xF707
case f5 = 0xF708
case f6 = 0xF709
case f7 = 0xF70A
case f8 = 0xF70B
case f9 = 0xF70C
case f10 = 0xF70D
case f11 = 0xF70E
case f12 = 0xF70F
case f13 = 0xF710
case f14 = 0xF711
case f15 = 0xF712
case f16 = 0xF713
case f17 = 0xF714
case f18 = 0xF715
case f19 = 0xF716
case f20 = 0xF717
case f21 = 0xF718
case f22 = 0xF719
case f23 = 0xF71A
case f24 = 0xF71B
case f25 = 0xF71C
case f26 = 0xF71D
case f27 = 0xF71E
case f28 = 0xF71F
case f29 = 0xF720
case f30 = 0xF721
case f31 = 0xF722
case f32 = 0xF723
case f33 = 0xF724
case f34 = 0xF725
case f35 = 0xF726
case insert = 0xF727
case deleteForward = 0xF728
case home = 0xF729
case begin = 0xF72A
case end = 0xF72B
case pageUp = 0xF72C
case pageDown = 0xF72D
case printScreen = 0xF72E
case scrollLock = 0xF72F
case pause = 0xF730
case sysReq = 0xF731
case `break` = 0xF732
case reset = 0xF733
case stop = 0xF734
case menu = 0xF735
case user = 0xF736
case system = 0xF737
case print = 0xF738
case clearLine = 0xF739
case clearDisplay = 0xF73A
case insertLine = 0xF73B
case deleteLine = 0xF73C
case insertCharacter = 0xF73D
case deleteCharacter = 0xF73E
case prev = 0xF73F
case next = 0xF740
case select = 0xF741
case execute = 0xF742
case undo = 0xF743
case redo = 0xF744
case find = 0xF745
case help = 0xF746
case modeSwitch = 0xF747
case enter = 0x03
case backspace = 0x08
case tab = 0x09
case newline = 0x0A
case formFeed = 0x0C
case carriageReturn = 0x0D
case backTab = 0x19
case delete = 0x7F
case lineSeparator = 0x2028
case paragraphSeparator = 0x2029
}
}
// 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
@ -265,14 +393,79 @@ public enum KeyCode: UInt16 {
case kDownArrow = 125
case kUpArrow = 126
public func toEvent() -> NSEvent? {
NSEvent.keyEvent(
with: .keyDown, location: .zero, modifierFlags: [],
timestamp: TimeInterval(), windowNumber: 0, context: nil,
public func toKBEvent() -> KBEvent {
.init(
modifierFlags: [],
timestamp: TimeInterval(), windowNumber: 0,
characters: "", charactersIgnoringModifiers: "",
isARepeat: false, keyCode: rawValue
)
}
public func correspondedSpecialKeyScalar(flags: KBEvent.ModifierFlags) -> Unicode.Scalar? {
var rawData: KBEvent.SpecialKey? {
switch self {
case .kNone: return nil
case .kCarriageReturn: return .carriageReturn
case .kTab:
return flags.contains(.shift) ? .backTab : .tab
case .kSpace: return nil
case .kSymbolMenuPhysicalKeyIntl: return nil
case .kBackSpace: return .backspace
case .kEscape: return nil
case .kCommand: return nil
case .kShift: return nil
case .kCapsLock: return nil
case .kOption: return nil
case .kControl: return nil
case .kRightShift: return nil
case .kRightOption: return nil
case .kRightControl: return nil
case .kFunction: return nil
case .kF17: return .f17
case .kVolumeUp: return nil
case .kVolumeDown: return nil
case .kMute: return nil
case .kLineFeed: return nil // TODO: return
case .kF18: return .f18
case .kF19: return .f19
case .kF20: return .f20
case .kYen: return nil
case .kSymbolMenuPhysicalKeyJIS: return nil
case .kJISNumPadComma: return nil
case .kF5: return .f5
case .kF6: return .f6
case .kF7: return .f7
case .kF3: return .f7
case .kF8: return .f8
case .kF9: return .f9
case .kJISAlphanumericalKey: return nil
case .kF11: return .f11
case .kJISKanaSwappingKey: return nil
case .kF13: return .f13
case .kF16: return .f16
case .kF14: return .f14
case .kF10: return .f10
case .kContextMenu: return .menu
case .kF12: return .f12
case .kF15: return .f15
case .kHelp: return .help
case .kHome: return .home
case .kPageUp: return .pageUp
case .kWindowsDelete: return .deleteForward
case .kF4: return .f4
case .kEnd: return .end
case .kF2: return .f2
case .kPageDown: return .pageDown
case .kF1: return .f1
case .kLeftArrow: return .leftArrow
case .kRightArrow: return .rightArrow
case .kDownArrow: return .downArrow
case .kUpArrow: return .upArrow
}
}
return rawData?.unicodeScalar
}
}
enum KeyCodeBlackListed: UInt16 {
@ -313,17 +506,6 @@ let mapMainAreaNumKey: [UInt16: String] = [
/// 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]
// 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.
}
// MARK: - Emacs CharCode-KeyCode translation tables.
public enum EmacsKey {
@ -333,17 +515,17 @@ public enum EmacsKey {
// MARK: - Apple ABC Keyboard Mapping
public extension NSEvent {
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> NSEvent {
public extension KBEvent {
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> KBEvent {
let mapTable = layout.mapTable
if isFlagChanged { return self }
guard keyModifierFlags == .shift || keyModifierFlags.isEmpty else { return self }
if !mapTable.keys.contains(keyCode) { return self }
guard let dataTuplet = mapTable[keyCode] else { return self }
let result: NSEvent? = reinitiate(
let result: KBEvent = reinitiate(
characters: isShiftHold ? dataTuplet.1 : dataTuplet.0,
charactersIgnoringModifiers: dataTuplet.0
)
return result ?? self
return result
}
}

View File

@ -0,0 +1,211 @@
// (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 AppKit
import IMKUtils
// MARK: - NSEvent Extension - Reconstructors
public extension NSEvent {
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 = text
var characters = characters
checkSpecialKey: if let matchedKey = KeyCode(rawValue: keyCode ?? self.keyCode) {
let scalar = matchedKey.correspondedSpecialKeyScalar(flags: (modifierFlags ?? self.modifierFlags).toKB)
guard let scalar = scalar else { break checkSpecialKey }
characters = .init(scalar)
}
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:
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
guard isEmacsKey else { return self }
let newKeyCode: UInt16 = {
switch isVerticalContext {
case false: return EmacsKey.charKeyMapHorizontal[charCode] ?? 0
case true: return EmacsKey.charKeyMapVertical[charCode] ?? 0
}
}()
guard newKeyCode != 0 else { return self }
return reinitiate(modifierFlags: [], characters: nil, charactersIgnoringModifiers: nil, keyCode: newKeyCode)
?? self
}
}
// MARK: - NSEvent Extension - InputSignalProtocol
public extension NSEvent {
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
/// NSEvent.characters
/// - Remark: event.type == .flagsChanged
/// event.characters? NSInternalInconsistencyException
var text: String { isFlagChanged ? "" : characters ?? "" }
var inputTextIgnoringModifiers: String? {
guard charactersIgnoringModifiers != nil else { return nil }
return charactersIgnoringModifiers ?? characters ?? ""
}
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
}
internal var keyModifierFlagsNS: ModifierFlags {
modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
static var keyModifierFlags: ModifierFlags {
Self.modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
}
var isFlagChanged: Bool { type == .flagsChanged }
var isEmacsKey: Bool {
// isControlHold
[6, 2, 1, 5, 4, 22].contains(charCode) && keyModifierFlagsNS == .control
}
// Alt+Shift+ macOS
// KeyCode
//
var mainAreaNumKeyChar: String? { mapMainAreaNumKey[keyCode] }
// ANSI charCode InputHandler
var isInvalid: Bool {
(0x20 ... 0xFF).contains(charCode) ? false : !(isReservedKey && !isKeyCodeBlacklisted)
}
var isKeyCodeBlacklisted: Bool {
guard let code = KeyCodeBlackListed(rawValue: keyCode) else { return false }
return code.rawValue != KeyCode.kNone.rawValue
}
var isReservedKey: Bool {
guard let code = KeyCode(rawValue: keyCode) else { return false }
return code.rawValue != KeyCode.kNone.rawValue
}
/// flags KeyCode
var isJISAlphanumericalKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISAlphanumericalKey }
var isJISKanaSwappingKey: Bool { KeyCode(rawValue: keyCode) == KeyCode.kJISKanaSwappingKey }
var isNumericPadKey: Bool { arrNumpadKeyCodes.contains(keyCode) }
var isMainAreaNumKey: Bool { mapMainAreaNumKey.keys.contains(keyCode) }
var isShiftHold: Bool { keyModifierFlagsNS.contains(.shift) }
var isCommandHold: Bool { keyModifierFlagsNS.contains(.command) }
var isControlHold: Bool { keyModifierFlagsNS.contains(.control) }
var beganWithLetter: Bool { text.first?.isLetter ?? false }
var isOptionHold: Bool { keyModifierFlagsNS.contains(.option) }
var isOptionHotKey: Bool { keyModifierFlagsNS.contains(.option) && text.first?.isLetter ?? false }
var isCapsLockOn: Bool { modifierFlags.intersection(.deviceIndependentFlagsMask).contains(.capsLock) }
var isFunctionKeyHold: Bool { keyModifierFlagsNS.contains(.function) }
var isNonLaptopFunctionKey: Bool { keyModifierFlagsNS.contains(.numericPad) && !isNumericPadKey }
var isEnter: Bool { [KeyCode.kCarriageReturn, KeyCode.kLineFeed].contains(KeyCode(rawValue: keyCode)) }
var isTab: Bool { KeyCode(rawValue: keyCode) == KeyCode.kTab }
var isUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kUpArrow }
var isDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kDownArrow }
var isLeft: Bool { KeyCode(rawValue: keyCode) == KeyCode.kLeftArrow }
var isRight: Bool { KeyCode(rawValue: keyCode) == KeyCode.kRightArrow }
var isPageUp: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageUp }
var isPageDown: Bool { KeyCode(rawValue: keyCode) == KeyCode.kPageDown }
var isSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kSpace }
var isBackSpace: Bool { KeyCode(rawValue: keyCode) == KeyCode.kBackSpace }
var isEsc: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEscape }
var isHome: Bool { KeyCode(rawValue: keyCode) == KeyCode.kHome }
var isEnd: Bool { KeyCode(rawValue: keyCode) == KeyCode.kEnd }
var isDelete: Bool { KeyCode(rawValue: keyCode) == KeyCode.kWindowsDelete }
var isCursorBackward: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kUpArrow
: KeyCode(rawValue: keyCode) == .kLeftArrow
}
var isCursorForward: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kDownArrow
: KeyCode(rawValue: keyCode) == .kRightArrow
}
var isCursorClockRight: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kRightArrow
: KeyCode(rawValue: keyCode) == .kUpArrow
}
var isCursorClockLeft: Bool {
isTypingVertical
? KeyCode(rawValue: keyCode) == .kLeftArrow
: KeyCode(rawValue: keyCode) == .kDownArrow
}
var isASCII: Bool { charCode < 0x80 }
// flags == .shift Shift
var isUpperCaseASCIILetterKey: Bool {
(65 ... 90).contains(charCode) && keyModifierFlagsNS == .shift
}
// .command 簿
var isSingleCommandBasedLetterHotKey: Bool {
((65 ... 90).contains(charCode) && keyModifierFlagsNS == [.shift, .command])
|| ((97 ... 122).contains(charCode) && keyModifierFlagsNS == .command)
}
// KeyCode macOS Apple
// ![input isShiftHold] 使 Shift
var isSymbolMenuPhysicalKey: Bool {
[KeyCode.kSymbolMenuPhysicalKeyIntl, KeyCode.kSymbolMenuPhysicalKeyJIS].contains(KeyCode(rawValue: keyCode))
}
}
// MARK: - Apple ABC Keyboard Mapping
public extension NSEvent {
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> NSEvent {
let mapTable = layout.mapTable
if isFlagChanged { return self }
guard keyModifierFlagsNS == .shift || keyModifierFlagsNS.isEmpty else { return self }
if !mapTable.keys.contains(keyCode) { return self }
guard let dataTuplet = mapTable[keyCode] else { return self }
let result: NSEvent? = reinitiate(
characters: isShiftHold ? dataTuplet.1 : dataTuplet.0,
charactersIgnoringModifiers: dataTuplet.0
)
return result ?? self
}
}

View File

@ -0,0 +1,99 @@
// (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 AppKit
// MARK: - NSEvent - Conforming to InputSignalProtocol
extension NSEvent: InputSignalProtocol {
public var keyModifierFlags: KBEvent.ModifierFlags {
.init(rawValue: keyModifierFlagsNS.rawValue)
}
}
// MARK: - NSEvent - Translating to KBEvent
public extension NSEvent? {
var copyAsKBEvent: KBEvent? {
self?.copyAsKBEvent ?? nil
}
}
public extension NSEvent {
var copyAsKBEvent: KBEvent? {
guard let typeKB = type.toKB else { return nil }
return .init(
with: typeKB,
modifierFlags: modifierFlags.toKB,
timestamp: timestamp,
windowNumber: windowNumber,
characters: characters,
charactersIgnoringModifiers: charactersIgnoringModifiers,
isARepeat: isARepeat,
keyCode: keyCode
)
}
}
public extension NSEvent.EventType {
var toKB: KBEvent.EventType? {
switch self {
case .flagsChanged: return .flagsChanged
case .keyDown: return .keyDown
case .keyUp: return .keyUp
default: return nil
}
}
}
public extension NSEvent.ModifierFlags {
var toKB: KBEvent.ModifierFlags {
.init(rawValue: rawValue)
}
}
// MARK: - KBEvent - Translating to NSEvent
public extension KBEvent? {
var copyAsNSEvent: NSEvent? {
self?.copyAsNSEvent ?? nil
}
}
public extension KBEvent {
var copyAsNSEvent: NSEvent? {
NSEvent.keyEvent(
with: type.toNS,
location: .zero,
modifierFlags: modifierFlags.toNS,
timestamp: timestamp,
windowNumber: windowNumber,
context: nil,
characters: characters ?? "",
charactersIgnoringModifiers: charactersIgnoringModifiers ?? "",
isARepeat: isARepeat,
keyCode: keyCode
)
}
}
public extension KBEvent.EventType {
var toNS: NSEvent.EventType {
switch self {
case .flagsChanged: return .flagsChanged
case .keyDown: return .keyDown
case .keyUp: return .keyUp
}
}
}
public extension KBEvent.ModifierFlags {
var toNS: NSEvent.ModifierFlags {
.init(rawValue: rawValue)
}
}

View File

@ -9,13 +9,10 @@
import AppKit
import CocoaExtension
extension NSEvent: InputSignalProtocol {}
// MARK: - InputSignalProtocol
public protocol InputSignalProtocol {
var modifierFlags: NSEvent.ModifierFlags { get }
var keyModifierFlags: NSEvent.ModifierFlags { get }
var keyModifierFlags: KBEvent.ModifierFlags { get }
var isTypingVertical: Bool { get }
var text: String { get }
var inputTextIgnoringModifiers: String? { get }
@ -62,3 +59,9 @@ public protocol InputSignalProtocol {
var isSingleCommandBasedLetterHotKey: Bool { get }
var isSymbolMenuPhysicalKey: Bool { get }
}
public extension InputSignalProtocol {
var commonKeyModifierFlags: KBEvent.ModifierFlags {
keyModifierFlags.subtracting([.function, .numericPad, .help])
}
}