|
|
|
@ -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 |
|
|
|
|