Skip to content

Commit

Permalink
Merge pull request #22 from creasty/superkey
Browse files Browse the repository at this point in the history
Improve implementation of SuperKey
  • Loading branch information
creasty authored Jun 2, 2020
2 parents 2896d68 + 2901f55 commit d4f87d5
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 68 deletions.
6 changes: 3 additions & 3 deletions keyboard/AppComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ final class AppComponent {

func eventManager() -> EventManagerType {
let eventManager: EventManagerType = EventManager(emitter: emitter)
eventManager.register(handler: navigationHandler())
eventManager.register(handler: emacsHandler())
eventManager.register(handler: wordMotionHandler())
eventManager.register(handler: escapeHandler())
eventManager.register(handler: windowResizeHandler())
eventManager.register(handler: navigationHandler())
eventManager.register(handler: mouseHandler())
eventManager.register(handler: wordMotionHandler())
eventManager.register(handler: windowResizeHandler())
eventManager.register(handler: appSwitchHandler())
eventManager.register(handler: inputMethodHandler())
eventManager.register(handler: appQuithHandler())
Expand Down
93 changes: 45 additions & 48 deletions keyboard/Core/EventManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ protocol EventManagerType {
final class EventManager: EventManagerType {
private let emitter: EmitterType

private var handlers: [Handler] = []
private var superKeys: [KeyCode: SuperKey] = [:]
private var handlers = [Handler]()
private let superKey = SuperKey()
private var superKeyPrefixes = Set<KeyCode>()

init(emitter: EmitterType) {
self.emitter = emitter
Expand All @@ -19,9 +20,7 @@ final class EventManager: EventManagerType {
handlers.append(handler)

handler.activateSuperKeys().forEach {
if superKeys[$0] == nil {
superKeys[$0] = SuperKey(prefix: $0)
}
superKeyPrefixes.insert($0)
}
}

Expand All @@ -35,7 +34,10 @@ final class EventManager: EventManagerType {
return Unmanaged.passRetained(cgEvent)
}

let action = handle(keyEvent: keyEvent) ?? .passThrough
let action = updateSuperKey(keyEvent: keyEvent)
?? handleSuperKey(keyEvent: keyEvent)
?? handle(keyEvent: keyEvent)
?? .passThrough

switch action {
case .prevent:
Expand All @@ -48,21 +50,11 @@ final class EventManager: EventManagerType {

extension EventManager: Handler {
func handle(keyEvent: KeyEvent) -> HandlerAction? {
for (_, superKey) in superKeys {
if let action = updateState(superKey: superKey, keyEvent: keyEvent) {
return action
}
if let action = execute(superKey: superKey, keyEvent: keyEvent) {
return action
}
}

for handler in handlers {
if let action = handler.handle(keyEvent: keyEvent) {
return action
}
}

return nil
}

Expand All @@ -73,70 +65,75 @@ extension EventManager: Handler {
return true
}
}

return false
}
}

extension EventManager {
private func updateState(superKey: SuperKey, keyEvent: KeyEvent) -> HandlerAction? {
private func updateSuperKey(keyEvent: KeyEvent) -> HandlerAction? {
guard keyEvent.match() else {
superKey.state = .inactive
return nil
}

if keyEvent.code == superKey.prefix {
guard !keyEvent.isARepeat else {
return .prevent
}
guard !keyEvent.isDown else {
if superKeyPrefixes.contains(keyEvent.code) {
// Activte the mode
if keyEvent.isDown, superKey.state == .inactive {
superKey.prefixKey = keyEvent.code
superKey.state = .activated
return .prevent
}

switch superKey.state {
case .activated:
emitter.emit(keyCode: superKey.prefix, flags: [], action: .both)
case .used, .enabled:
if let key = superKey.cancel() {
emitter.emit(keyCode: superKey.prefix, flags: [], action: .both)
emitter.emit(keyCode: key, flags: [], action: .both)
} else {
emitter.emit(keyCode: .command, flags: [], action: .both)
if let prefixKey = superKey.prefixKey, prefixKey == keyEvent.code {
// Cancel on the final keyup
if !keyEvent.isDown && !keyEvent.isARepeat {
switch superKey.state {
case .activated:
emitter.emit(keyCode: prefixKey, flags: [], action: .both)
case .used, .enabled:
// Abort a pending operation, if any
if let pendingKey = superKey.cancel() {
emitter.emit(keyCode: prefixKey, flags: [], action: .both)
emitter.emit(keyCode: pendingKey, flags: [], action: .both)
} else {
// Trigger any key events to clean up
emitter.emit(keyCode: .command, flags: [], action: .both)
}
default: break
}

// Restore the state
superKey.state = .inactive
}
default: break
}

superKey.state = .inactive
return .prevent
}

guard keyEvent.isDown else {
return nil
// Always ignore the prefix key
return .prevent
}
}

guard superKey.enable() else {
superKey.state = .disabled

emitter.emit(keyCode: superKey.prefix, flags: [], action: .both)
emitter.emit(keyCode: keyEvent.code, flags: [], action: (keyEvent.isDown ? .down : .up))
// Disable when another key was pressed immediately after the activation
if keyEvent.isDown, !superKey.enable() {
if let prefixKey = superKey.prefixKey {
emitter.emit(keyCode: prefixKey, flags: [], action: .both)
}
emitter.emit(keyCode: keyEvent.code, flags: [], action: .down)

return .prevent
}

return nil
}

private func execute(superKey: SuperKey, keyEvent: KeyEvent) -> HandlerAction? {
guard superKey.isEnabled else {
private func handleSuperKey(keyEvent: KeyEvent) -> HandlerAction? {
guard superKey.isEnabled, let prefixKey = superKey.prefixKey else {
return nil
}
guard keyEvent.match() else {
return nil
}

superKey.perform(key: keyEvent.code, isKeyDown: keyEvent.isDown) { [weak self] (keys) in
self?.handleSuperKey(prefix: superKey.prefix, keys: keys)
self?.handleSuperKey(prefix: prefixKey, keys: keys)
}

return .prevent
Expand Down
28 changes: 11 additions & 17 deletions keyboard/Core/SuperKey.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Foundation

final class SuperKey {
let prefix: KeyCode
struct Const {
static let downThresholdMs: Double = 50
static let dispatchDelayMs: Int = 150
}

enum State {
case inactive
Expand All @@ -11,12 +14,10 @@ final class SuperKey {
case disabled
}

private let downThreshold: Double = 50 // ms
private let dispatchDelay: Int = 150 // ms

private var activatedAt: Double = 0
private var current: (key: KeyCode, time: DispatchTime, work: DispatchWorkItem?)?

var prefixKey: KeyCode?
private var pressedKeys: Set<KeyCode> = []

var state: State = .inactive {
Expand All @@ -35,15 +36,12 @@ final class SuperKey {
return [.enabled, .used].contains(state)
}

init(prefix: KeyCode) {
self.prefix = prefix
}

func enable() -> Bool {
guard state == .activated else {
return true
}
guard DispatchTime.uptimeNanoseconds() - activatedAt > downThreshold * 1e6 else {
guard DispatchTime.uptimeNanoseconds() - activatedAt > Const.downThresholdMs * 1e6 else {
state = .disabled
return false
}
state = .enabled
Expand All @@ -68,23 +66,19 @@ final class SuperKey {
let work = DispatchWorkItem() {
block(keys)
}
let dispatchTime = DispatchTime.now() + DispatchTimeInterval.milliseconds(dispatchDelay)
let dispatchTime = DispatchTime.now() + DispatchTimeInterval.milliseconds(Const.dispatchDelayMs)
current = (key: key, time: dispatchTime, work: work)
DispatchQueue.main.asyncAfter(deadline: dispatchTime, execute: work)
}

func cancel() -> KeyCode? {
guard let current = current else {
return nil
}
guard let current = current else { return nil }
self.current = nil

prefixKey = nil
pressedKeys = []

guard current.time > DispatchTime.now() else {
return nil
}

guard current.time > DispatchTime.now() else { return nil }
current.work?.cancel()

return current.key
Expand Down

0 comments on commit d4f87d5

Please sign in to comment.