vChewing-macOS/Source/KeyHandler.swift

201 lines
6.6 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Cocoa
@objc enum KeyCode: UInt16 {
case none = 0
case enter = 76
case up = 126
case down = 125
case left = 123
case right = 124
case pageUp = 116
case pageDown = 121
case home = 115
case end = 119
case delete = 117
}
class KeyHandlerInput: NSObject {
private (set) var event: NSEvent
private (set) var useVerticalMode: Bool
var inputText: String? {
event.characters
}
var charCode: UInt16 {
guard let inputText = inputText, inputText.count > 0 else {
return 0
}
let first = inputText[inputText.startIndex].utf16.first!
return first
}
var keyCode: UInt16 {
event.keyCode
}
var flags: NSEvent.ModifierFlags {
event.modifierFlags
}
var cursorForwardKey: KeyCode {
useVerticalMode ? .down : .right
}
var cursorBackwardKey: KeyCode {
useVerticalMode ? .up : .left
}
var extraChooseCandidateKey: KeyCode {
useVerticalMode ? .left : .down
}
var absorbedArrowKey: KeyCode {
useVerticalMode ? .right : .up
}
var verticalModeOnlyChooseCandidateKey: KeyCode {
useVerticalMode ? absorbedArrowKey : .none
}
init(event: NSEvent, isVerticalMode: Bool) {
self.event = event
self.useVerticalMode = isVerticalMode
}
}
typealias KeyHandlerStateCallback = (InputState) -> ()
typealias KeyHandlerErrorCallback = () -> ()
@objc protocol KeyHandlerDelegate: AnyObject {
func keyHandlerRequestCurrentInputtingState(_ handler: KeyHandler) -> InputStateInputting
func keyHandler(_ handler: KeyHandler, requestWriteUserPhrase state: InputStateMarking ) -> Bool
func keyHandler(_ handler: KeyHandler, isCharCodeValidBmpfReading charCode:UInt16 ) -> Bool
func keyHandler(_ handler: KeyHandler, insertCharCodeToBmpfReading charCode:UInt16 ) -> Void
}
class KeyHandler: NSObject {
var delegate: KeyHandlerDelegate?
@objc func handle(_ input: KeyHandlerInput,
currentState: InputState,
stateCallback: @escaping KeyHandlerStateCallback,
errorCallback: @escaping KeyHandlerErrorCallback
) -> Bool {
guard let delegate = delegate else {
return false
}
// if the inputText is empty, it's a function key combination, we ignore it
guard let inputText = input.inputText else {
return false
}
if inputText.isEmpty {
return false
}
let flags = input.flags
let charCode = input.charCode
let keyCode = input.keyCode
let emacsKey = EmacsKeyHelper.detect(charCode: charCode, flags: flags)
// if the composing buffer is empty and there's no reading, and there is some function key combination, we ignore it
let isFunctionKey = flags.contains(.command) || flags.contains(.control) || flags.contains(.option) || flags.contains(.numericPad)
if currentState is InputStateInputting == false && isFunctionKey {
return false
}
// Caps Lock processing : if Caps Lock is on, temporarily disable bopomofo.
if charCode == 8 ||
charCode == 13 ||
keyCode == input.absorbedArrowKey.rawValue ||
keyCode == input.cursorForwardKey.rawValue ||
keyCode == input.cursorBackwardKey.rawValue {
// do nothing if backspace is pressed -- we ignore the key
} else if flags.contains(.capsLock) {
// process all possible combination, we hope.
stateCallback(InputStateEmpty())
// first commit everything in the buffer.
if flags.contains(.shift) {
return false
}
// if ASCII but not printable, don't use insertText:replacementRange: as many apps don't handle non-ASCII char insertions.
if charCode < 0x80 && isprint(Int32(charCode)) == 0 {
return false
}
stateCallback(InputStateCommitting(poppedText: inputText.lowercased()))
stateCallback(InputStateEmpty())
return true
}
if flags.contains(.numericPad) {
if keyCode != KeyCode.left.rawValue &&
keyCode != KeyCode.right.rawValue &&
keyCode != KeyCode.down.rawValue &&
keyCode != KeyCode.up.rawValue &&
charCode != 32 &&
isprint(Int32(charCode)) != 0 {
stateCallback(InputStateEmpty())
stateCallback(InputStateCommitting(poppedText: inputText.lowercased()))
stateCallback(InputStateEmpty())
return true
}
}
// candidates?
if let state = currentState as? InputStateMarking {
// ESC
if charCode == 27 {
let inputting = InputStateInputting(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex)
stateCallback(inputting)
return true
}
// Enter
if charCode == 13 {
if delegate.keyHandler(self, requestWriteUserPhrase: state) == false {
errorCallback()
return true
}
let inputting = InputStateInputting(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex)
stateCallback(inputting)
return true
}
// Shift + left
if (keyCode == input.cursorBackwardKey.rawValue || emacsKey == .backward) && flags.contains(.shift) {
var index = state.markerIndex
if index > 0 {
index -= 1
let marking = InputStateMarking(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, markerIndex: state.markerIndex)
stateCallback(marking)
} else {
errorCallback()
}
return true
}
if (keyCode == input.cursorForwardKey.rawValue || emacsKey == .forward) && flags.contains(.shift) {
var index = state.markerIndex
if index < state.composingBuffer.count {
index += 1
let marking = InputStateMarking(composingBuffer: state.composingBuffer, cursorIndex: state.cursorIndex, markerIndex: state.markerIndex)
stateCallback(marking)
} else {
errorCallback()
}
return true
}
}
return false
}
}