diff --git a/src/main.swift b/src/main.swift index 467311d..ef8e078 100644 --- a/src/main.swift +++ b/src/main.swift @@ -1,5 +1,38 @@ import Cocoa +func checkSingleInstance() { + let myPid = ProcessInfo.processInfo.processIdentifier + + // 1. Check if another instance with bundle ID "com.soniox.voice" is already running + if let bundleID = Bundle.main.bundleIdentifier { + let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) + for app in runningApps { + if app.processIdentifier != myPid { + fputs("SonioxVoice: Another instance (PID \(app.processIdentifier)) is already running. Exiting duplicate PID \(myPid).\n", stderr) + exit(0) + } + } + } + + // 2. Fallback OS-level file lock (flock) on lockfile to prevent race conditions + let lockPath = NSTemporaryDirectory() + "com.soniox.voice.lock" + let fd = open(lockPath, O_CREAT | O_RDWR, 0o666) + if fd >= 0 { + if flock(fd, LOCK_EX | LOCK_NB) != 0 { + fputs("SonioxVoice: Lock file \(lockPath) is locked by another process. Exiting duplicate PID \(myPid).\n", stderr) + exit(0) + } + ftruncate(fd, 0) + let pidStr = "\(myPid)\n" + pidStr.withCString { ptr in + _ = write(fd, ptr, strlen(ptr)) + } + // Keep fd open for process lifetime to maintain lock + } +} + +checkSingleInstance() + let app = NSApplication.shared let delegate = AppDelegate() app.delegate = delegate