1import Cocoa
2
3// Compilation: swiftc menubar.swift -o menubar
4// Usage: ./menubar <matchaPath>
5
6class MenubarController: NSObject {
7 private var statusItem: NSStatusItem
8 private var matchaPath: String
9 private var unreadCount: Int = 0
10
11 init(matchaPath: String) {
12 self.matchaPath = matchaPath
13 self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
14 super.init()
15
16 setupMenu()
17 updateTitle()
18 setupNotifications()
19 }
20
21 private func setupMenu() {
22 let menu = NSMenu()
23
24 let openItem = NSMenuItem(title: "Open Matcha", action: #selector(openMatcha), keyEquivalent: "o")
25 openItem.target = self
26 menu.addItem(openItem)
27
28 let composeItem = NSMenuItem(title: "Compose Message", action: #selector(openCompose), keyEquivalent: "n")
29 composeItem.target = self
30 menu.addItem(composeItem)
31
32 menu.addItem(NSMenuItem.separator())
33
34 let refreshItem = NSMenuItem(title: "Check for Mail", action: #selector(refreshMail), keyEquivalent: "r")
35 refreshItem.target = self
36 menu.addItem(refreshItem)
37
38 menu.addItem(NSMenuItem.separator())
39
40 let quitItem = NSMenuItem(title: "Quit Menubar Helper", action: #selector(terminate), keyEquivalent: "q")
41 quitItem.target = self
42 menu.addItem(quitItem)
43
44 statusItem.menu = menu
45 }
46
47 private func updateTitle() {
48 if let button = statusItem.button {
49 // Using a system symbol or a custom string
50 let icon = unreadCount > 0 ? "✉️ " : "📩 "
51 button.title = icon + (unreadCount > 0 ? "\(unreadCount)" : "")
52 }
53 }
54
55 private func setupNotifications() {
56 // Listen for updates from the main Matcha Go process
57 DistributedNotificationCenter.default().addObserver(
58 self,
59 selector: #selector(handleUpdateNotification(_:)),
60 name: NSNotification.Name("com.floatpane.matcha.UpdateUnread"),
61 object: nil
62 )
63 }
64
65 @objc private func handleUpdateNotification(_ notification: Notification) {
66 if let userInfo = notification.userInfo,
67 let countString = userInfo["count"] as? String,
68 let count = Int(countString) {
69 self.unreadCount = count
70 updateTitle()
71 }
72 }
73
74 @objc private func openMatcha() {
75 runTerminalCommand(command: "'\(matchaPath)'")
76 }
77
78 @objc private func openCompose() {
79 runTerminalCommand(command: "'\(matchaPath)' send")
80 }
81
82 @objc private func refreshMail() {
83 // Trigger a notification that the Go app can listen for, or just run a command
84 DistributedNotificationCenter.default().postNotificationName(
85 NSNotification.Name("com.floatpane.matcha.RefreshRequest"),
86 object: nil,
87 userInfo: nil,
88 deliverImmediately: true
89 )
90 }
91
92 private func runTerminalCommand(command: String) {
93 let script = """
94 tell application "Terminal"
95 activate
96 if (count of windows) is 0 then
97 do script "\(command)"
98 else
99 do script "\(command)" in window 1
100 end if
101 end tell
102 """
103
104 if let appleScript = NSAppleScript(source: script) {
105 var error: NSDictionary?
106 appleScript.executeAndReturnError(&error)
107 }
108 }
109
110 @objc private func terminate() {
111 NSApplication.shared.terminate(nil)
112 }
113}
114
115let args = ProcessInfo.processInfo.arguments
116guard args.count > 1 else {
117 print("Usage: ./menubar <matchaPath>")
118 exit(1)
119}
120
121let matchaPath = args[1]
122
123let app = NSApplication.shared
124let controller = MenubarController(matchaPath: matchaPath)
125app.setActivationPolicy(.prohibited) // Run as a background agent
126app.run()