Created
March 18, 2024 18:46
-
-
Save stephancasas/c19d9800776983143d2d30b901808926 to your computer and use it in GitHub Desktop.
A convenience extension for CGEventSupervisor which offers built-in discriminator types for keyboard events.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CGEventSupervisor+Convenience.swift | |
// | |
// @see: https://github.com/stephancasas/CGEventSupervisor | |
// | |
// @note: This is provided as an extension because it is poorly-structured as a result of | |
// backward-compatibility consideration. There are several ways it can be improved. | |
// | |
// Created by Stephan Casas on 3/18/24. | |
// | |
import Foundation; | |
import Cocoa; | |
import CGEventSupervisor; | |
// MARK: - Key Event Types | |
extension CGEventSupervisor { | |
/// A type which describes a discriminated keyboard event passed to a subscriber. | |
protocol KeyEventDescriptorProtocol<EventType, ModifierFlags>: RawRepresentable where RawValue == (eventType: EventType, modifiers: [ModifierFlags], character: Character) { | |
associatedtype EventType; | |
associatedtype ModifierFlags; | |
associatedtype CallbackType; | |
var eventDescriptor: EventDescriptor<EventType> { get } | |
static var keyDown: EventType { get } | |
static var keyUp: EventType { get } | |
} | |
/// A type which describes a keyboard event, the event-messaging type, and the subscriber's discriminating criteria. | |
struct KeyEvent<KeyEventDescriptor: KeyEventDescriptorProtocol>: RawRepresentable { | |
var rawValue: KeyEventDescriptor; | |
var baseEventDescriptor: EventDescriptor<KeyEventDescriptor.EventType> { | |
self.rawValue.eventDescriptor | |
} | |
var character: Character { | |
self.rawValue.character | |
} | |
var string: String { | |
self.character.lowercased() | |
} | |
var modifiers: [KeyEventDescriptor.ModifierFlags] { | |
self.rawValue.modifiers | |
} | |
init(rawValue: KeyEventDescriptor) { | |
self.rawValue = rawValue; | |
} | |
} | |
} | |
// MARK: - Key Event Descriptor Convenience | |
extension CGEventSupervisor.KeyEventDescriptorProtocol { | |
var eventType: EventType { self.rawValue.eventType } | |
var modifiers: [ModifierFlags] { self.rawValue.modifiers } | |
var character: Character { self.rawValue.character } | |
/// Return a key event descriptor which matches on key-down of the given character. | |
static func keyDown(_ character: Character) -> Self { | |
.init(rawValue: ( | |
eventType: Self.keyDown, | |
modifiers: [], | |
character: character | |
))! | |
} | |
/// Return a key event descriptor which matches on key-down of the given character with the given modifiers. | |
static func keyDown(_ character: Character, modifiers: ModifierFlags...) -> Self { | |
.init(rawValue: ( | |
eventType: Self.keyDown, | |
modifiers: modifiers, | |
character: character | |
))! | |
} | |
/// Return a key event descriptor which matches on key-up of the given character. | |
static func keyUp(_ character: Character) -> Self { | |
.init(rawValue: ( | |
eventType: Self.keyUp, | |
modifiers: [], | |
character: character | |
))! | |
} | |
/// Return a key event descriptor which matches on key-up of the given character with the given modifiers. | |
static func keyUp(_ character: Character, modifiers: ModifierFlags...) -> Self { | |
.init(rawValue: ( | |
eventType: Self.keyUp, | |
modifiers: modifiers, | |
character: character | |
))! | |
} | |
} | |
extension CGEventSupervisor { | |
/// A type describing a discriminated keyboard event which is provided to a subscriber as an `NSEvent`. | |
struct NSEventKeyEventDescriptor: CGEventSupervisor.KeyEventDescriptorProtocol { | |
var rawValue: (eventType: EventType, modifiers: Array<ModifierFlags>, character: Character); | |
var eventDescriptor: EventDescriptor<NSEvent.EventType> { | |
.nsEvents(.init(rawValue: .init(self.eventType.rawValue))!) | |
} | |
typealias EventType = NSEvent.EventType | |
typealias ModifierFlags = NSEvent.ModifierFlags | |
typealias CallbackType = NSEventCallback | |
static var keyDown: EventType = .keyDown | |
static var keyUp: EventType = .keyUp; | |
} | |
/// A type describing a discriminated keyboard event which is provided to a subscriber as a `CGEvent`. | |
struct CGEventKeyEventDescriptor: CGEventSupervisor.KeyEventDescriptorProtocol { | |
var rawValue: (eventType: EventType, modifiers: Array<ModifierFlags>, character: Character); | |
var eventDescriptor: EventDescriptor<CGEventType> { | |
.cgEvents(self.eventType) | |
} | |
typealias EventType = CGEventType | |
typealias ModifierFlags = CGEventFlags | |
typealias CallbackType = CGEventCallback | |
static var keyDown: EventType = .keyDown | |
static var keyUp: EventType = .keyUp; | |
} | |
} | |
// MARK: - Key Event Convenience | |
extension CGEventSupervisor.KeyEvent where KeyEventDescriptor == CGEventSupervisor.NSEventKeyEventDescriptor { | |
/// Returns a key event descriptor for providing events to a subscriber as`NSEvent` instances. | |
static func nsEvent(_ keyEvent: KeyEventDescriptor) -> Self { | |
.init(rawValue: keyEvent); | |
} | |
} | |
extension CGEventSupervisor.KeyEvent where KeyEventDescriptor == CGEventSupervisor.CGEventKeyEventDescriptor { | |
/// Returns a key event descriptor for providing events to a subscriber as `CGEvent` instances. | |
static func cgEvent(_ keyEvent: KeyEventDescriptor) -> Self { | |
.init(rawValue: keyEvent); | |
} | |
} | |
// MARK: - Subscriber Key Event Convenience | |
extension CGEventSupervisor { | |
/// Register a subscription to global `NSEvent` keyboard events of the given type matching the given discriminating criteria. | |
/// - Parameters: | |
/// - subscriber: The unique name of the subscriber. | |
/// - keyEvent: The key event type and discriminator to which the subscriber will subscribe. | |
/// - callback: The callback which will handle the subscribed event. | |
func subscribe( | |
as subscriber: String, | |
to keyEvent: KeyEvent<NSEventKeyEventDescriptor>, | |
using callback: @escaping NSEventCallback | |
) { | |
self.subscribe( | |
as: subscriber, | |
to: keyEvent.baseEventDescriptor, | |
using: { nsEvent in | |
guard | |
let eventChars = nsEvent.charactersIgnoringModifiers, | |
keyEvent.string.elementsEqual( | |
eventChars), | |
keyEvent.modifiers.allSatisfy({ | |
nsEvent.modifierFlags.contains($0) | |
}) | |
else { | |
return; | |
} | |
callback(nsEvent); | |
}); | |
} | |
/// Register a subscription to global `CGEvent` keyboard events of the given type matching the given discriminating criteria. | |
/// - Parameters: | |
/// - subscriber: The unique name of the subscriber. | |
/// - keyEvent: The key event type and discriminator to which the subscriber will subscribe. | |
/// - callback: The callback which will handle the subscribed event. | |
func subscribe( | |
as subscriber: String, | |
to keyEvent: KeyEvent<CGEventKeyEventDescriptor>, | |
using callback: @escaping CGEventCallback | |
) { | |
self.subscribe( | |
as: subscriber, | |
to: keyEvent.baseEventDescriptor, | |
using: { cgEvent in | |
var keyChar: UniChar = .zero; | |
cgEvent.keyboardGetUnicodeString( | |
maxStringLength: 1, | |
actualStringLength: nil, | |
unicodeString: &keyChar); | |
guard | |
keyEvent.string.elementsEqual( | |
keyChar.formatted()), | |
keyEvent.modifiers.allSatisfy({ | |
cgEvent.flags.contains($0) | |
}) | |
else { | |
return; | |
} | |
callback(cgEvent); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment