Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Created March 18, 2024 18:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephancasas/c19d9800776983143d2d30b901808926 to your computer and use it in GitHub Desktop.
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.
//
// 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