Mouse Middle Click To Toggle Recording

This commit is contained in:
Alexey Haidamaka 2025-08-29 06:05:36 +02:00
parent 26362bb50e
commit be59064093
2 changed files with 105 additions and 0 deletions

View File

@ -26,6 +26,17 @@ class HotkeyManager: ObservableObject {
setupHotkeyMonitoring()
}
}
@Published var isMiddleClickToggleEnabled: Bool {
didSet {
UserDefaults.standard.set(isMiddleClickToggleEnabled, forKey: "isMiddleClickToggleEnabled")
setupHotkeyMonitoring()
}
}
@Published var middleClickActivationDelay: Int {
didSet {
UserDefaults.standard.set(middleClickActivationDelay, forKey: "middleClickActivationDelay")
}
}
private var whisperState: WhisperState
private var miniRecorderShortcutManager: MiniRecorderShortcutManager
@ -39,6 +50,10 @@ class HotkeyManager: ObservableObject {
private var globalEventMonitor: Any?
private var localEventMonitor: Any?
// Middle-click event monitoring
private var middleClickMonitors: [Any?] = []
private var middleClickTask: Task<Void, Never>?
// Key state tracking
private var currentKeyState = false
private var keyPressStartTime: Date?
@ -118,6 +133,11 @@ class HotkeyManager: ObservableObject {
// ---- normal initialisation ----
self.selectedHotkey1 = HotkeyOption(rawValue: UserDefaults.standard.string(forKey: "selectedHotkey1") ?? "") ?? .rightCommand
self.selectedHotkey2 = HotkeyOption(rawValue: UserDefaults.standard.string(forKey: "selectedHotkey2") ?? "") ?? .none
self.isMiddleClickToggleEnabled = UserDefaults.standard.bool(forKey: "isMiddleClickToggleEnabled")
let storedDelay = UserDefaults.standard.integer(forKey: "middleClickActivationDelay")
self.middleClickActivationDelay = storedDelay > 0 ? storedDelay : 200
self.whisperState = whisperState
self.miniRecorderShortcutManager = MiniRecorderShortcutManager(whisperState: whisperState)
@ -144,6 +164,7 @@ class HotkeyManager: ObservableObject {
setupModifierKeyMonitoring()
setupCustomShortcutMonitoring()
setupMiddleClickMonitoring()
}
private func setupModifierKeyMonitoring() {
@ -166,6 +187,40 @@ class HotkeyManager: ObservableObject {
}
}
private func setupMiddleClickMonitoring() {
guard isMiddleClickToggleEnabled else { return }
// Mouse Down
let downMonitor = NSEvent.addGlobalMonitorForEvents(matching: .otherMouseDown) { [weak self] event in
guard let self = self, event.buttonNumber == 2 else { return }
self.middleClickTask?.cancel()
self.middleClickTask = Task {
do {
let delay = UInt64(self.middleClickActivationDelay) * 1_000_000 // ms to ns
try await Task.sleep(nanoseconds: delay)
guard self.isMiddleClickToggleEnabled, !Task.isCancelled else { return }
Task { @MainActor in
guard self.canProcessHotkeyAction else { return }
await self.whisperState.handleToggleMiniRecorder()
}
} catch {
// Cancelled
}
}
}
// Mouse Up
let upMonitor = NSEvent.addGlobalMonitorForEvents(matching: .otherMouseUp) { [weak self] event in
guard let self = self, event.buttonNumber == 2 else { return }
self.middleClickTask?.cancel()
}
middleClickMonitors = [downMonitor, upMonitor]
}
private func setupCustomShortcutMonitoring() {
// Hotkey 1
if selectedHotkey1 == .custom {
@ -198,6 +253,14 @@ class HotkeyManager: ObservableObject {
localEventMonitor = nil
}
for monitor in middleClickMonitors {
if let monitor = monitor {
NSEvent.removeMonitor(monitor)
}
}
middleClickMonitors = []
middleClickTask?.cancel()
resetKeyStates()
}

View File

@ -118,6 +118,48 @@ struct SettingsView: View {
}
}
SettingsSection(
icon: "computermouse.fill",
title: "Middle-Click Toggle",
subtitle: "Optionally use your middle mouse button to toggle recording"
) {
VStack(alignment: .leading, spacing: 12) {
Toggle("Enable Middle-Click Toggle", isOn: $hotkeyManager.isMiddleClickToggleEnabled.animation())
.toggleStyle(.switch)
if hotkeyManager.isMiddleClickToggleEnabled {
HStack {
Text("Activation Delay")
.font(.system(size: 13, weight: .medium))
.foregroundColor(.secondary)
TextField("", value: $hotkeyManager.middleClickActivationDelay, formatter: {
let formatter = NumberFormatter()
formatter.numberStyle = .none
formatter.minimum = 0
return formatter
}())
.textFieldStyle(PlainTextFieldStyle())
.padding(EdgeInsets(top: 3, leading: 6, bottom: 3, trailing: 6))
.background(Color(NSColor.textBackgroundColor))
.cornerRadius(5)
.frame(width: 70)
Text("ms")
.foregroundColor(.secondary)
Spacer()
}
.transition(.opacity.combined(with: .move(edge: .top)))
Text("A short delay to prevent accidental toggles when closing browser tabs.")
.font(.system(size: 12))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
SettingsSection(
icon: "speaker.wave.2.bubble.left.fill",
title: "Recording Feedback",