-
-
Save anongit/a831e7de78e2fc3873b47c49a25e6b77 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Execute script using the interpreter: | |
$ swift appnap.swift "Skype" | |
or compile it first then run: | |
$ swiftc -O appnap.swift | |
$ ./appnap "(?i)skype" # (?i) — ignore case | |
*/ | |
import AppKit | |
func runUntilKeyboardInterrupt(_ callback: @escaping () -> Void) { | |
// See: https://stackoverflow.com/a/45714258 | |
// Make sure the signal does not terminate the application. | |
signal(SIGINT, SIG_IGN) | |
let source = DispatchSource.makeSignalSource(signal: SIGINT) | |
source.setEventHandler { | |
callback() | |
exit(0) | |
} | |
source.resume() | |
RunLoop.current.run( | |
mode: RunLoopMode.defaultRunLoopMode, | |
before: NSDate.distantFuture | |
) | |
} | |
func main(_ args: [String]) { | |
if args.count != 2 { | |
print("Usage: \(args[0]) <pattern>") | |
return | |
} | |
guard let pattern = try? NSRegularExpression(pattern: "^.*\(args[1]).*$") else { | |
print("Invalid regex pattern") | |
return | |
} | |
// What to match against the pattern | |
let appToString = { (app: NSRunningApplication) in | |
app.executableURL?.path ?? app.bundleIdentifier ?? "[]" | |
} | |
let matchesPattern = { (app: NSRunningApplication) -> Bool in | |
let x = appToString(app) | |
return pattern.numberOfMatches(in: x, range: NSRange(x.startIndex..., in: x)) > 0 | |
} | |
let workspace = NSWorkspace.shared | |
var active: NSRunningApplication? | |
var suspended = Set<pid_t>() | |
let resume = { (pid: pid_t) in | |
kill(pid, SIGCONT) | |
suspended.remove(pid) | |
} | |
let suspend = { (pid: pid_t) in | |
kill(pid, SIGSTOP) | |
suspended.insert(pid) | |
} | |
if let app = workspace.frontmostApplication, matchesPattern(app) { | |
active = app | |
} | |
workspace.runningApplications | |
.filter { $0 != active && matchesPattern($0) } | |
.forEach { (app) in | |
print("suspending \(appToString(app))") | |
suspend(app.processIdentifier) | |
} | |
// The observervation is only happening while token exists, | |
// so the token variable is actually used. | |
let token = workspace.observe(\.frontmostApplication) { workspace, _ in | |
if let previous = active { | |
print("suspending \(appToString(previous))") | |
suspend(previous.processIdentifier) | |
active = nil | |
} | |
guard let app = workspace.frontmostApplication else { | |
return | |
} | |
if matchesPattern(app) { | |
print("resuming \(appToString(app))") | |
resume(app.processIdentifier) | |
active = app | |
} | |
} | |
runUntilKeyboardInterrupt { | |
suspended.forEach(resume) | |
} | |
} | |
main(CommandLine.arguments) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment