Shared // Implementing KBEvent.
This commit is contained in:
parent
5e1208bc5e
commit
23ef3124d4
|
@ -13,14 +13,12 @@ let package = Package(
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_IMKUtils"),
|
|
||||||
.package(path: "../vChewing_SwiftExtension"),
|
.package(path: "../vChewing_SwiftExtension"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "CocoaExtension",
|
name: "CocoaExtension",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
|
|
||||||
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
// requirements defined in MIT License.
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
import AppKit
|
import AppKit
|
||||||
import InputMethodKit
|
|
||||||
|
|
||||||
public extension NSWindowController {
|
public extension NSWindowController {
|
||||||
func orderFront() {
|
func orderFront() {
|
||||||
|
|
|
@ -1076,7 +1076,7 @@ extension InputHandler {
|
||||||
// 另外,這裡不要用「!input.isFunctionKeyHold」,
|
// 另外,這裡不要用「!input.isFunctionKeyHold」,
|
||||||
// 否則會導致對上下左右鍵與翻頁鍵的判斷失效。
|
// 否則會導致對上下左右鍵與翻頁鍵的判斷失效。
|
||||||
let notEmpty = state.hasComposition && !compositor.isEmpty && isComposerOrCalligrapherEmpty
|
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
|
let noBannedModifiers = bannedModifiers.intersection(input.keyModifierFlags).isEmpty
|
||||||
var triggered = input.isCursorClockLeft || input.isCursorClockRight
|
var triggered = input.isCursorClockLeft || input.isCursorClockRight
|
||||||
triggered = triggered || (input.isSpace && prefs.chooseCandidateUsingSpace)
|
triggered = triggered || (input.isSpace && prefs.chooseCandidateUsingSpace)
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class SessionCtl: IMKInputController {
|
||||||
public static var areWeNerfing = false
|
public static var areWeNerfing = false
|
||||||
|
|
||||||
/// 上一個被處理過的鍵盤事件。
|
/// 上一個被處理過的鍵盤事件。
|
||||||
public var previouslyHandledEvents: [NSEvent] = .init()
|
public var previouslyHandledEvents = [KBEvent]()
|
||||||
|
|
||||||
/// 目前在用的的選字窗副本。
|
/// 目前在用的的選字窗副本。
|
||||||
public var candidateUI: CtlCandidateProtocol?
|
public var candidateUI: CtlCandidateProtocol?
|
||||||
|
|
|
@ -40,20 +40,19 @@ public extension SessionCtl {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = false
|
guard let newEvent = event.copyAsKBEvent else { return 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
guard ![.ofEmpty, .ofAbortion].contains(state.type) else { return false }
|
||||||
let codes = previouslyHandledEvents.map(\.keyCode)
|
let codes = previouslyHandledEvents.map(\.keyCode)
|
||||||
if codes.contains(event.keyCode) {
|
if codes.contains(event.keyCode) {
|
||||||
|
@ -65,7 +64,7 @@ public extension SessionCtl {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleKeyDown(event: NSEvent) -> Bool {
|
private func handleKeyDown(event: KBEvent) -> Bool {
|
||||||
// MARK: 前置處理
|
// MARK: 前置處理
|
||||||
|
|
||||||
// 先放過一些以 .command 觸發的熱鍵(包括剪貼簿熱鍵)。
|
// 先放過一些以 .command 觸發的熱鍵(包括剪貼簿熱鍵)。
|
||||||
|
@ -131,7 +130,7 @@ public extension SessionCtl {
|
||||||
// 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。
|
// 如果是方向鍵輸入的話,就想辦法帶上標記資訊、來說明當前是縱排還是橫排。
|
||||||
if event.isUp || event.isDown || event.isLeft || event.isRight {
|
if event.isUp || event.isDown || event.isLeft || event.isRight {
|
||||||
updateVerticalTypingStatus() // 檢查當前環境是否是縱排輸入。
|
updateVerticalTypingStatus() // 檢查當前環境是否是縱排輸入。
|
||||||
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal") ?? event
|
eventToDeal = event.reinitiate(charactersIgnoringModifiers: isVerticalTyping ? "Vertical" : "Horizontal")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
|
// 使 NSEvent 自翻譯,這樣可以讓 Emacs NSEvent 變成標準 NSEvent。
|
||||||
|
@ -158,12 +157,12 @@ public extension SessionCtl {
|
||||||
if eventToDeal.isNumericPadKey,
|
if eventToDeal.isNumericPadKey,
|
||||||
let eventCharConverted = eventToDeal.characters?.applyingTransformFW2HW(reverse: false)
|
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,
|
} else if [.ofEmpty, .ofInputting].contains(state.type), eventToDeal.isMainAreaNumKey,
|
||||||
!eventToDeal.isCommandHold, !eventToDeal.isControlHold, eventToDeal.isOptionHold
|
!eventToDeal.isCommandHold, !eventToDeal.isControlHold, eventToDeal.isOptionHold
|
||||||
{
|
{
|
||||||
// Alt(+Shift)+主鍵盤區數字鍵 預先處理
|
// Alt(+Shift)+主鍵盤區數字鍵 預先處理
|
||||||
eventToDeal = eventToDeal.reinitiate(characters: eventToDeal.mainAreaNumKeyChar) ?? eventToDeal
|
eventToDeal = eventToDeal.reinitiate(characters: eventToDeal.mainAreaNumKeyChar)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。
|
// 準備修飾鍵,用來判定要新增的詞彙是否需要賦以非常低的權重。
|
||||||
|
|
|
@ -14,6 +14,7 @@ let package = Package(
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(path: "../vChewing_CocoaExtension"),
|
.package(path: "../vChewing_CocoaExtension"),
|
||||||
|
.package(path: "../vChewing_IMKUtils"),
|
||||||
.package(path: "../vChewing_SwiftExtension"),
|
.package(path: "../vChewing_SwiftExtension"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
|
@ -21,6 +22,7 @@ let package = Package(
|
||||||
name: "Shared",
|
name: "Shared",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
|
.product(name: "CocoaExtension", package: "vChewing_CocoaExtension"),
|
||||||
|
.product(name: "IMKUtils", package: "vChewing_IMKUtils"),
|
||||||
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
.product(name: "SwiftExtension", package: "vChewing_SwiftExtension"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,42 +6,103 @@
|
||||||
// marks, or product names of Contributor, except as required to fulfill notice
|
// marks, or product names of Contributor, except as required to fulfill notice
|
||||||
// requirements defined in MIT License.
|
// requirements defined in MIT License.
|
||||||
|
|
||||||
import AppKit
|
import Foundation
|
||||||
import IMKUtils
|
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 {
|
public init(
|
||||||
func reinitiate(
|
with type: KBEvent.EventType? = nil,
|
||||||
with type: NSEvent.EventType? = nil,
|
modifierFlags: KBEvent.ModifierFlags? = nil,
|
||||||
location: NSPoint? = nil,
|
|
||||||
modifierFlags: NSEvent.ModifierFlags? = nil,
|
|
||||||
timestamp: TimeInterval? = nil,
|
timestamp: TimeInterval? = nil,
|
||||||
windowNumber: Int? = nil,
|
windowNumber: Int? = nil,
|
||||||
characters: String? = nil,
|
characters: String? = nil,
|
||||||
charactersIgnoringModifiers: String? = nil,
|
charactersIgnoringModifiers: String? = nil,
|
||||||
isARepeat: Bool? = nil,
|
isARepeat: Bool? = nil,
|
||||||
keyCode: UInt16? = 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
|
let oldChars: String = text
|
||||||
return NSEvent.keyEvent(
|
return KBEvent(
|
||||||
with: type ?? self.type,
|
with: type ?? .keyDown,
|
||||||
location: location ?? locationInWindow,
|
|
||||||
modifierFlags: modifierFlags ?? self.modifierFlags,
|
modifierFlags: modifierFlags ?? self.modifierFlags,
|
||||||
timestamp: timestamp ?? self.timestamp,
|
timestamp: timestamp ?? self.timestamp,
|
||||||
windowNumber: windowNumber ?? self.windowNumber,
|
windowNumber: windowNumber ?? self.windowNumber,
|
||||||
context: nil,
|
|
||||||
characters: characters ?? oldChars,
|
characters: characters ?? oldChars,
|
||||||
charactersIgnoringModifiers: charactersIgnoringModifiers ?? characters ?? oldChars,
|
charactersIgnoringModifiers: charactersIgnoringModifiers ?? characters ?? oldChars,
|
||||||
isARepeat: isARepeat ?? self.isARepeat,
|
isARepeat: isARepeat ?? self.isARepeat,
|
||||||
keyCode: keyCode ?? self.keyCode
|
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: 是否按照縱排來操作。
|
/// - Parameter isVerticalTyping: 是否按照縱排來操作。
|
||||||
/// - Returns: 翻譯結果。失敗的話則返回翻譯原文。
|
/// - Returns: 翻譯結果。失敗的話則返回翻譯原文。
|
||||||
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> NSEvent {
|
func convertFromEmacsKeyEvent(isVerticalContext: Bool) -> KBEvent {
|
||||||
guard isEmacsKey else { return self }
|
guard isEmacsKey else { return self }
|
||||||
let newKeyCode: UInt16 = {
|
let newKeyCode: UInt16 = {
|
||||||
switch isVerticalContext {
|
switch isVerticalContext {
|
||||||
|
@ -50,32 +111,15 @@ public extension NSEvent {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
guard newKeyCode != 0 else { return self }
|
guard newKeyCode != 0 else { return self }
|
||||||
let newCharScalar: Unicode.Scalar = {
|
return reinitiate(modifierFlags: [], characters: nil, charactersIgnoringModifiers: nil, keyCode: newKeyCode)
|
||||||
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
|
// MARK: - KBEvent Extension - InputSignalProtocol
|
||||||
|
|
||||||
public extension NSEvent {
|
public extension KBEvent {
|
||||||
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
|
var isTypingVertical: Bool { charactersIgnoringModifiers == "Vertical" }
|
||||||
/// NSEvent.characters 的類型安全版。
|
/// KBEvent.characters 的類型安全版。
|
||||||
/// - Remark: 注意:必須針對 event.type == .flagsChanged 提前返回結果,
|
/// - Remark: 注意:必須針對 event.type == .flagsChanged 提前返回結果,
|
||||||
/// 否則,每次處理這種判斷時都會因為讀取 event.characters? 而觸發 NSInternalInconsistencyException。
|
/// 否則,每次處理這種判斷時都會因為讀取 event.characters? 而觸發 NSInternalInconsistencyException。
|
||||||
var text: String { isFlagChanged ? "" : characters ?? "" }
|
var text: String { isFlagChanged ? "" : characters ?? "" }
|
||||||
|
@ -98,10 +142,6 @@ public extension NSEvent {
|
||||||
modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
|
modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
|
||||||
}
|
}
|
||||||
|
|
||||||
static var keyModifierFlags: ModifierFlags {
|
|
||||||
Self.modifierFlags.intersection(.deviceIndependentFlagsMask).subtracting(.capsLock)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isFlagChanged: Bool { type == .flagsChanged }
|
var isFlagChanged: Bool { type == .flagsChanged }
|
||||||
|
|
||||||
var isEmacsKey: Bool {
|
var isEmacsKey: Bool {
|
||||||
|
@ -204,6 +244,94 @@ public extension NSEvent {
|
||||||
|
|
||||||
// MARK: - Enums of Constants
|
// 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.
|
// 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
|
// KeyCodes: https://eastmanreference.com/complete-list-of-applescript-key-codes
|
||||||
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
|
// Also: HIToolbox.framework/Versions/A/Headers/Events.h
|
||||||
|
@ -265,14 +393,79 @@ public enum KeyCode: UInt16 {
|
||||||
case kDownArrow = 125
|
case kDownArrow = 125
|
||||||
case kUpArrow = 126
|
case kUpArrow = 126
|
||||||
|
|
||||||
public func toEvent() -> NSEvent? {
|
public func toKBEvent() -> KBEvent {
|
||||||
NSEvent.keyEvent(
|
.init(
|
||||||
with: .keyDown, location: .zero, modifierFlags: [],
|
modifierFlags: [],
|
||||||
timestamp: TimeInterval(), windowNumber: 0, context: nil,
|
timestamp: TimeInterval(), windowNumber: 0,
|
||||||
characters: "", charactersIgnoringModifiers: "",
|
characters: "", charactersIgnoringModifiers: "",
|
||||||
isARepeat: false, keyCode: rawValue
|
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 {
|
enum KeyCodeBlackListed: UInt16 {
|
||||||
|
@ -313,17 +506,6 @@ let mapMainAreaNumKey: [UInt16: String] = [
|
||||||
/// 注意:第 95 號 Key Code(逗號)為 JIS 佈局特有的數字小鍵盤按鍵。
|
/// 注意:第 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]
|
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.
|
// MARK: - Emacs CharCode-KeyCode translation tables.
|
||||||
|
|
||||||
public enum EmacsKey {
|
public enum EmacsKey {
|
||||||
|
@ -333,17 +515,17 @@ public enum EmacsKey {
|
||||||
|
|
||||||
// MARK: - Apple ABC Keyboard Mapping
|
// MARK: - Apple ABC Keyboard Mapping
|
||||||
|
|
||||||
public extension NSEvent {
|
public extension KBEvent {
|
||||||
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> NSEvent {
|
func layoutTranslated(to layout: LatinKeyboardMappings = .qwerty) -> KBEvent {
|
||||||
let mapTable = layout.mapTable
|
let mapTable = layout.mapTable
|
||||||
if isFlagChanged { return self }
|
if isFlagChanged { return self }
|
||||||
guard keyModifierFlags == .shift || keyModifierFlags.isEmpty else { return self }
|
guard keyModifierFlags == .shift || keyModifierFlags.isEmpty else { return self }
|
||||||
if !mapTable.keys.contains(keyCode) { return self }
|
if !mapTable.keys.contains(keyCode) { return self }
|
||||||
guard let dataTuplet = mapTable[keyCode] else { return self }
|
guard let dataTuplet = mapTable[keyCode] else { return self }
|
||||||
let result: NSEvent? = reinitiate(
|
let result: KBEvent = reinitiate(
|
||||||
characters: isShiftHold ? dataTuplet.1 : dataTuplet.0,
|
characters: isShiftHold ? dataTuplet.1 : dataTuplet.0,
|
||||||
charactersIgnoringModifiers: dataTuplet.0
|
charactersIgnoringModifiers: dataTuplet.0
|
||||||
)
|
)
|
||||||
return result ?? self
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,10 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
import CocoaExtension
|
import CocoaExtension
|
||||||
|
|
||||||
extension NSEvent: InputSignalProtocol {}
|
|
||||||
|
|
||||||
// MARK: - InputSignalProtocol
|
// MARK: - InputSignalProtocol
|
||||||
|
|
||||||
public protocol InputSignalProtocol {
|
public protocol InputSignalProtocol {
|
||||||
var modifierFlags: NSEvent.ModifierFlags { get }
|
var keyModifierFlags: KBEvent.ModifierFlags { get }
|
||||||
var keyModifierFlags: NSEvent.ModifierFlags { get }
|
|
||||||
var isTypingVertical: Bool { get }
|
var isTypingVertical: Bool { get }
|
||||||
var text: String { get }
|
var text: String { get }
|
||||||
var inputTextIgnoringModifiers: String? { get }
|
var inputTextIgnoringModifiers: String? { get }
|
||||||
|
@ -62,3 +59,9 @@ public protocol InputSignalProtocol {
|
||||||
var isSingleCommandBasedLetterHotKey: Bool { get }
|
var isSingleCommandBasedLetterHotKey: Bool { get }
|
||||||
var isSymbolMenuPhysicalKey: Bool { get }
|
var isSymbolMenuPhysicalKey: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension InputSignalProtocol {
|
||||||
|
var commonKeyModifierFlags: KBEvent.ModifierFlags {
|
||||||
|
keyModifierFlags.subtracting([.function, .numericPad, .help])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue