diff options
Diffstat (limited to 'menubar/hotkey.swift')
| -rw-r--r-- | menubar/hotkey.swift | 70 |
1 files changed, 52 insertions, 18 deletions
diff --git a/menubar/hotkey.swift b/menubar/hotkey.swift index e6a5aa7..c5ab178 100644 --- a/menubar/hotkey.swift +++ b/menubar/hotkey.swift @@ -11,6 +11,7 @@ import Carbon.HIToolbox struct Settings: Equatable { var hotkey = "ctrl+opt+cmd+e" + var jumpHotkey = "ctrl+opt+cmd+u" static var directory: String { environment["DINGGER_CONFIG_DIR"] ?? (NSHomeDirectory() + "/.config/dingger") @@ -19,6 +20,20 @@ struct Settings: Equatable { static let template = """ # dingger settings. The menu bar app reloads this file when you save it. + # Shortcuts: modifiers ctrl, opt, cmd, shift; keys a-z, 0-9, f1-f12, space. + + [shortcuts] + # Show or hide the floating panel. + hotkey = "ctrl+opt+cmd+e" + # Jump to the agent at the top of the waiting list (blocked first, then + # the longest done), like clicking the tmux status line. + jump_hotkey = "ctrl+opt+cmd+u" + + """ + + /// The first template, replaced on upgrade if the user never edited it. + private static let firstTemplate = """ + # dingger settings. The menu bar app reloads this file when you save it. [panel] # Global shortcut that shows or hides the floating panel. @@ -27,9 +42,11 @@ struct Settings: Equatable { """ - /// Create the file with defaults and comments if it does not exist yet. + /// Create the file with defaults and comments if it does not exist yet; + /// upgrade it if it is still an unedited older template. static func ensureFile() { - guard !FileManager.default.fileExists(atPath: path) else { return } + let existing = try? String(contentsOfFile: path, encoding: .utf8) + guard existing == nil || existing == firstTemplate else { return } try? FileManager.default.createDirectory(atPath: directory, withIntermediateDirectories: true) try? template.write(toFile: path, atomically: true, encoding: .utf8) } @@ -44,7 +61,11 @@ struct Settings: Equatable { } guard parts.count == 2, !parts[0].hasPrefix("#") else { continue } let value = parts[1].trimmingCharacters(in: CharacterSet(charactersIn: "\"")) - if parts[0] == "hotkey" { settings.hotkey = value } + switch parts[0] { + case "hotkey": settings.hotkey = value + case "jump_hotkey": settings.jumpHotkey = value + default: break + } } return settings } @@ -113,34 +134,47 @@ struct Hotkey: Equatable { // MARK: registration -/// One process-wide global shortcut. +/// Process-wide global shortcuts, each with its own id. enum GlobalHotkey { - private static var reference: EventHotKeyRef? - private static var action: (() -> Void)? + enum Id: UInt32 { + case panel = 1 + case jump = 2 + } + + private static var references: [UInt32: EventHotKeyRef] = [:] + private static var actions: [UInt32: () -> Void] = [:] private static var handlerInstalled = false - /// Register `hotkey`, replacing any previous one. Returns false if another - /// app already owns the combination. - static func register(_ hotkey: Hotkey, action newAction: @escaping () -> Void) -> Bool { - unregister() - action = newAction + /// Register `hotkey` under `id`, replacing what was there. Returns false + /// if another app already owns the combination. + static func register(_ id: Id, _ hotkey: Hotkey, action: @escaping () -> Void) -> Bool { + unregister(id) + actions[id.rawValue] = action if !handlerInstalled { var spec = EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)) - InstallEventHandler(GetApplicationEventTarget(), { _, _, _ in - DispatchQueue.main.async { GlobalHotkey.action?() } + InstallEventHandler(GetApplicationEventTarget(), { _, event, _ in + var pressed = EventHotKeyID() + GetEventParameter(event, EventParamName(kEventParamDirectObject), + EventParamType(typeEventHotKeyID), nil, + MemoryLayout<EventHotKeyID>.size, nil, &pressed) + let id = pressed.id + DispatchQueue.main.async { GlobalHotkey.actions[id]?() } return noErr }, 1, &spec, nil, nil) handlerInstalled = true } - let id = EventHotKeyID(signature: OSType(0x4447_4752), id: 1) // "DGGR" - let status = RegisterEventHotKey(hotkey.keyCode, hotkey.carbonModifiers, id, + var reference: EventHotKeyRef? + let status = RegisterEventHotKey(hotkey.keyCode, hotkey.carbonModifiers, + EventHotKeyID(signature: OSType(0x4447_4752), id: id.rawValue), GetApplicationEventTarget(), 0, &reference) + if status == noErr, let reference { references[id.rawValue] = reference } return status == noErr } - static func unregister() { - if let reference { UnregisterEventHotKey(reference) } - reference = nil + static func unregister(_ id: Id) { + if let reference = references.removeValue(forKey: id.rawValue) { + UnregisterEventHotKey(reference) + } } } |
