1import Cocoa
2
3class AppDelegate: NSObject, NSApplicationDelegate {
4 var handled = false
5
6 func applicationDidFinishLaunching(_ notification: Notification) {
7 log("MatchaMail handler started")
8
9 // Register for legacy Apple Events (GURL = 1196711500)
10 NSAppleEventManager.shared().setEventHandler(
11 self,
12 andSelector: #selector(handleGetURLEvent(_:withReplyEvent:)),
13 forEventClass: AEEventClass(1196711500),
14 andEventID: AEEventID(1196711500)
15 )
16
17 // Timeout
18 DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
19 if !self.handled {
20 self.log("No URL event received within 2s, terminating.")
21 NSApp.terminate(nil)
22 }
23 }
24 }
25
26 // Modern URL handling
27 func application(_ application: NSApplication, open urls: [URL]) {
28 if let url = urls.first {
29 log("Modern API received URL: \(url.absoluteString)")
30 launchMatcha(with: url.absoluteString)
31 }
32 }
33
34 // Legacy Apple Event handling
35 @objc func handleGetURLEvent(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) {
36 if let urlString = event.paramDescriptor(forKeyword: AEKeyword(757935405))?.stringValue {
37 log("Legacy API received URL: \(urlString)")
38 launchMatcha(with: urlString)
39 }
40 }
41
42 func launchMatcha(with url: String) {
43 guard !handled else { return }
44 handled = true
45
46 let matchaPath = "{{MATCHA_PATH}}"
47 log("Launching Matcha via .command file at \(matchaPath) with URL \(url)")
48
49 // Use a .command file to open in the DEFAULT terminal
50 let tempDir = NSTemporaryDirectory()
51 let commandFileName = "matcha-mailto-\(UUID().uuidString).command"
52 let commandFileUrl = URL(fileURLWithPath: tempDir).appendingPathComponent(commandFileName)
53
54 // We use a bash script that opens matcha and then removes itself
55 let scriptContent = """
56 #!/bin/bash
57 '\(matchaPath)' '\(url)'
58 # Clean up this temporary script
59 rm -- "$0"
60 exit
61 """
62
63 do {
64 try scriptContent.write(to: commandFileUrl, atomically: true, encoding: .utf8)
65
66 // Make the file executable
67 let attributes = [FileAttributeKey.posixPermissions: 0o755]
68 try FileManager.default.setAttributes(attributes, ofItemAtPath: commandFileUrl.path)
69
70 // Open the file with NSWorkspace.
71 // Since it's a .command file, macOS will open it in the default terminal.
72 NSWorkspace.shared.open(commandFileUrl)
73 log("Successfully requested macOS to open .command file")
74
75 } catch {
76 log("Failed to create/open .command file: \(error.localizedDescription)")
77 }
78
79 // Small delay to ensure launch
80 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
81 NSApp.terminate(nil)
82 }
83 }
84
85 func log(_ message: String) {
86 let logPath = "/tmp/matcha-handler.log"
87 let timestamp = Date().description
88 let line = "[\(timestamp)] \(message)\n"
89 if let data = line.data(using: .utf8) {
90 if let fileHandle = FileHandle(forWritingAtPath: logPath) {
91 fileHandle.seekToEndOfFile()
92 fileHandle.write(data)
93 fileHandle.closeFile()
94 } else {
95 try? data.write(to: URL(fileURLWithPath: logPath))
96 }
97 }
98 NSLog(message)
99 }
100}
101
102let app = NSApplication.shared
103let delegate = AppDelegate()
104app.delegate = delegate
105app.run()