Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Sources/parrot/Input/HotkeyMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ final class HotkeyMonitor {

/// Mask of the modifier we treat as the hotkey. Fn = `.maskSecondaryFn`.
private let mask: CGEventFlags
/// When set, only flagsChanged events with this keycode are considered,
/// so left/right variants of a modifier can be told apart.
private let keycode: Int64?
private let debug: Bool
private var onEvent: ((Event) -> Void)?
private var tap: CFMachPort?
private var runLoopSource: CFRunLoopSource?
private var isPressed = false

init(mask: CGEventFlags = .maskSecondaryFn, debug: Bool = false) {
init(mask: CGEventFlags = .maskSecondaryFn, keycode: Int64? = nil, debug: Bool = false) {
self.mask = mask
self.keycode = keycode
self.debug = debug
}

Expand Down Expand Up @@ -87,6 +91,7 @@ final class HotkeyMonitor {
))
}
guard type == .flagsChanged else { return }
if let keycode, event.getIntegerValueField(.keyboardEventKeycode) != keycode { return }
let pressed = event.flags.contains(mask)
guard pressed != isPressed else { return }
isPressed = pressed
Expand Down
16 changes: 14 additions & 2 deletions Sources/parrot/Parrot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ struct Run: ParsableCommand {
@Option(name: .long, help: "Model id to use. Defaults to the recommended model.")
var model: String?

@Option(name: .long, help: "Push-to-talk key: fn (default) or right-option.")
var hotkey: String = "fn"

func run() throws {
if !skipDoctor {
let checks = DoctorReport.run()
Expand Down Expand Up @@ -81,7 +84,16 @@ struct Run: ParsableCommand {
let app = NSApplication.shared
app.setActivationPolicy(.accessory)

let monitor = HotkeyMonitor(debug: debugHotkey)
let monitor: HotkeyMonitor
switch hotkey {
case "fn":
monitor = HotkeyMonitor(debug: debugHotkey)
case "right-option":
monitor = HotkeyMonitor(mask: .maskAlternate, keycode: 61, debug: debugHotkey)
default:
FileHandle.standardError.write(Data("unknown hotkey: \(hotkey) (use fn or right-option)\n".utf8))
throw ExitCode(1)
}
let capture = AudioCapture()
let dumpWav = self.dumpWav
let overlay: RecordingOverlay? = noOverlay ? nil : MainActor.assumeIsolated { RecordingOverlay() }
Expand Down Expand Up @@ -169,7 +181,7 @@ struct Run: ParsableCommand {
sigint.resume()
signal(SIGINT, SIG_IGN)

FileHandle.standardError.write(Data("listening on fn hold · model: \(chosenModel.id) · ^C to quit\n".utf8))
FileHandle.standardError.write(Data("listening on \(hotkey) hold · model: \(chosenModel.id) · ^C to quit\n".utf8))
app.run()
}
}
Expand Down