@hasna/recordings 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env swift
2
+ // Test: NSEvent.addGlobalMonitorForEvents — does it see fn key?
3
+ import Cocoa
4
+
5
+ print("Testing NSEvent global monitor for 15 seconds...")
6
+ print("Press fn, Option, any key. Ctrl+C to quit.\n")
7
+
8
+ let app = NSApplication.shared
9
+ app.setActivationPolicy(.accessory)
10
+
11
+ // Monitor flagsChanged (modifier keys including fn)
12
+ NSEvent.addGlobalMonitorForEvents(matching: [.flagsChanged, .keyDown, .keyUp]) { event in
13
+ if event.type == .flagsChanged {
14
+ let fn = event.modifierFlags.contains(.function) ? " [fn]" : ""
15
+ let cmd = event.modifierFlags.contains(.command) ? " [cmd]" : ""
16
+ let opt = event.modifierFlags.contains(.option) ? " [opt]" : ""
17
+ let ctrl = event.modifierFlags.contains(.control) ? " [ctrl]" : ""
18
+ print("flagsChanged keyCode=\(event.keyCode)\(fn)\(cmd)\(opt)\(ctrl)")
19
+ } else if event.type == .keyDown {
20
+ print("keyDown keyCode=\(event.keyCode) chars=\(event.characters ?? "")")
21
+ } else if event.type == .keyUp {
22
+ print("keyUp keyCode=\(event.keyCode)")
23
+ }
24
+ }
25
+
26
+ print("✅ Global monitor registered\n")
27
+
28
+ DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
29
+ print("\nDone.")
30
+ exit(0)
31
+ }
32
+
33
+ app.run()
@@ -18,12 +18,19 @@ class AppDelegate: NSObject, NSApplicationDelegate {
18
18
 
19
19
  let recordingsDir: String = {
20
20
  let home = FileManager.default.homeDirectoryForCurrentUser.path
21
- return "\(home)/.recordings"
21
+ let newDir = "\(home)/.hasna/recordings"
22
+ let oldDir = "\(home)/.recordings"
23
+ // Auto-migrate from old location
24
+ if !FileManager.default.fileExists(atPath: newDir) && FileManager.default.fileExists(atPath: oldDir) {
25
+ try? FileManager.default.createDirectory(atPath: "\(home)/.hasna", withIntermediateDirectories: true)
26
+ try? FileManager.default.copyItem(atPath: oldDir, toPath: newDir)
27
+ }
28
+ return newDir
22
29
  }()
23
30
 
24
31
  let audioDir: String = {
25
32
  let home = FileManager.default.homeDirectoryForCurrentUser.path
26
- return "\(home)/.recordings/audio"
33
+ return "\(home)/.hasna/recordings/audio"
27
34
  }()
28
35
 
29
36
  let recordingsBin: String = {
@@ -60,11 +67,47 @@ class AppDelegate: NSObject, NSApplicationDelegate {
60
67
 
61
68
  // ── Global Hotkey ───────────────────────────────────────────────────────
62
69
 
70
+ /// Check if Accessibility permission is granted. If not, show a dialog and open System Settings.
71
+ func checkAccessibilityPermission() -> Bool {
72
+ // First check without prompting
73
+ let trusted = AXIsProcessTrustedWithOptions(
74
+ [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): false] as CFDictionary
75
+ )
76
+ if trusted { return true }
77
+
78
+ // Not trusted — show dialog explaining why we need it
79
+ let alert = NSAlert()
80
+ alert.messageText = "Accessibility Permission Required"
81
+ alert.informativeText = "RecordingsHelper needs Accessibility access to detect the global hotkey (hold Space to record).\n\nClick 'Open Settings' to grant permission, then restart the app."
82
+ alert.alertStyle = .warning
83
+ alert.addButton(withTitle: "Open Settings")
84
+ alert.addButton(withTitle: "Quit")
85
+
86
+ let response = alert.runModal()
87
+ if response == .alertFirstButtonReturn {
88
+ // Prompt the system dialog AND open System Settings > Accessibility
89
+ AXIsProcessTrustedWithOptions(
90
+ [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
91
+ )
92
+ if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
93
+ NSWorkspace.shared.open(url)
94
+ }
95
+ } else {
96
+ NSApplication.shared.terminate(nil)
97
+ }
98
+ return false
99
+ }
100
+
63
101
  func registerHotkey() {
64
102
  // Hold Space for 1+ second to start recording, release to stop.
65
103
  // Uses CGEventTap to monitor all keyboard events globally.
66
104
  // Normal space presses (< 1 second) pass through untouched.
67
105
 
106
+ // Check Accessibility permission BEFORE attempting event tap
107
+ if !checkAccessibilityPermission() {
108
+ return
109
+ }
110
+
68
111
  let eventMask: CGEventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << CGEventType.flagsChanged.rawValue)
69
112
 
70
113
  guard let tap = CGEvent.tapCreate(