You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Musubi/App/Sources/TalkKey.swift

64 lines
2.3 KiB

import Carbon.HIToolbox
import Foundation
import Observation
/// Global talk key (works while Musubi is in the background). Default: ⌃⌥ Space.
///
/// Uses Carbon hot keys, which need no Accessibility permission and report press and release.
@MainActor @Observable
final class TalkKey {
enum Mode: String, CaseIterable { case hold, toggle }
var mode: Mode = Mode(rawValue: UserDefaults.standard.string(forKey: "talkMode") ?? "") ?? .hold {
didSet { UserDefaults.standard.set(mode.rawValue, forKey: "talkMode") }
}
private(set) var isActive = false
var onChange: ((Bool) -> Void)?
let displayName = "⌃⌥ Leertaste"
private var hotKey: EventHotKeyRef?
private var handler: EventHandlerRef?
func register() {
var types = [
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased)),
]
InstallEventHandler(GetApplicationEventTarget(), talkKeyHandler, types.count, &types,
Unmanaged.passUnretained(self).toOpaque(), &handler)
let id = EventHotKeyID(signature: OSType(0x4D534249), id: 1) // 'MSBI'
RegisterEventHotKey(UInt32(kVK_Space), UInt32(controlKey | optionKey), id, GetApplicationEventTarget(), 0, &hotKey)
}
/// Button or hot key pressed.
func press() {
switch mode {
case .hold: set(true)
case .toggle: set(!isActive)
}
}
/// Button or hot key released.
func release() {
if mode == .hold { set(false) }
}
func set(_ active: Bool) {
guard active != isActive else { return }
isActive = active
onChange?(active)
}
fileprivate func handle(kind: UInt32) {
if kind == UInt32(kEventHotKeyPressed) { press() } else { release() }
}
}
private func talkKeyHandler(_: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {
guard let event, let userData else { return OSStatus(eventNotHandledErr) }
let kind = GetEventKind(event)
let key = Unmanaged<TalkKey>.fromOpaque(userData).takeUnretainedValue()
MainActor.assumeIsolated { key.handle(kind: kind) }
return noErr
}

Powered by TurnKey Linux.