aboutsummaryrefslogtreecommitdiff
path: root/menubar/hotkey.swift
diff options
context:
space:
mode:
Diffstat (limited to 'menubar/hotkey.swift')
-rw-r--r--menubar/hotkey.swift70
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)
+ }
}
}