forked from coolaj86/run-on-macos-screen-unlock
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrun-on-macos-screen-events.swift
executable file
·185 lines (150 loc) · 5.78 KB
/
run-on-macos-screen-events.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
import Foundation
let name = (CommandLine.arguments[0] as NSString).lastPathComponent
let version = "1.1.0"
let build = "2024-09-30-001"
let author = "AJ ONeal <[email protected]>"
let fork = "Andrew Rich <[email protected]>"
let versionMessage = "\(name) \(version) (\(build))"
let copyrightMessage = "Copyright 2024 \(author)"
let copyrightMessage = "\(copyrightMessage)\nForked by \(fork)\"
let helpMessage = """
Runs user-specified commands when the screen is locked or unlocked by
listening for the "com.apple.screenIsLocked" and "com.apple.screenIsUnlocked" events.
It uses /usr/bin/command -v to find the program in the user's PATH (or the explicit path given),
and then runs it with /usr/bin/command, which can run aliases and shell functions also.
USAGE
\(name) [OPTIONS] --lock <lock-command> [lock-command-arguments] --unlock <unlock-command> [unlock-command-arguments]
OPTIONS
--lock <command> [args] Specify the command to run when the screen is locked
--unlock <command> [args] Specify the command to run when the screen is unlocked
--version, -V, version Display the version information and exit
--help, help Display this help and exit
At least one of --lock or --unlock must be specified. If either is omitted, no action will be taken for that event.
EXAMPLES
\(name) --lock "/path/to/lock-script.sh" --unlock "/path/to/unlock-script.sh"
\(name) --lock "echo 'Screen locked' >> /tmp/screen-events.log" --unlock "echo 'Screen unlocked' >> /tmp/screen-events.log"
"""
signal(SIGINT) { _ in
printForHuman("received ctrl+c, exiting...")
exit(0)
}
func printForHuman(_ message: String) {
if let data = "\(message)\n".data(using: .utf8) {
FileHandle.standardError.write(data)
}
}
func getCommandPath(_ command: String) -> String? {
let commandv = Process()
commandv.launchPath = "/usr/bin/command"
commandv.arguments = ["-v", command]
let pipe = Pipe()
commandv.standardOutput = pipe
commandv.standardError = FileHandle.standardError
try! commandv.run()
commandv.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let commandPath = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
else {
return nil
}
if commandv.terminationStatus != 0, commandPath.isEmpty {
return nil
}
return commandPath
}
class ScreenLockObserver {
var lockCommandArgs: ArraySlice<String>?
var unlockCommandArgs: ArraySlice<String>?
init(lockCommandArgs: ArraySlice<String>?, unlockCommandArgs: ArraySlice<String>?) {
self.lockCommandArgs = lockCommandArgs
self.unlockCommandArgs = unlockCommandArgs
let dnc = DistributedNotificationCenter.default()
NSLog("Waiting for screen lock/unlock events")
_ = dnc.addObserver(forName: NSNotification.Name("com.apple.screenIsLocked"), object: nil, queue: .main) { _ in
NSLog("notification: com.apple.screenIsLocked")
if let args = self.lockCommandArgs {
self.runCommand(args)
}
}
_ = dnc.addObserver(forName: NSNotification.Name("com.apple.screenIsUnlocked"), object: nil, queue: .main) { _ in
NSLog("notification: com.apple.screenIsUnlocked")
if let args = self.unlockCommandArgs {
self.runCommand(args)
}
}
}
private func runCommand(_ args: ArraySlice<String>) {
guard let commandPath = args.first else {
NSLog("No command specified")
return
}
let task = Process()
task.launchPath = "/usr/bin/command"
task.arguments = Array(args)
task.standardOutput = FileHandle.standardOutput
task.standardError = FileHandle.standardError
do {
try task.run()
} catch {
printForHuman("Failed to run \(commandPath): \(error.localizedDescription)")
if let nsError = error as NSError? {
printForHuman("Error details: \(nsError)")
}
}
task.waitUntilExit()
}
}
func processArgs(_ args: inout ArraySlice<String>) -> (ArraySlice<String>?, ArraySlice<String>?) {
var lockArgs: ArraySlice<String>?
var unlockArgs: ArraySlice<String>?
while !args.isEmpty {
switch args.first {
case "--lock":
args.removeFirst()
lockArgs = collectCommandArgs(&args)
case "--unlock":
args.removeFirst()
unlockArgs = collectCommandArgs(&args)
case "--help", "help":
printHelp()
exit(0)
case "--version", "-V", "version":
printVersion()
exit(0)
default:
printForHuman("Unknown option: \(args.first ?? "")")
printHelp()
exit(1)
}
}
return (lockArgs, unlockArgs)
}
func collectCommandArgs(_ args: inout ArraySlice<String>) -> ArraySlice<String> {
var commandArgs: ArraySlice<String> = []
while !args.isEmpty && args.first != "--lock" && args.first != "--unlock" {
commandArgs.append(args.removeFirst())
}
return commandArgs
}
func printHelp() {
printForHuman(versionMessage)
printForHuman("")
printForHuman(helpMessage)
printForHuman("")
printForHuman(copyrightMessage)
}
func printVersion() {
printForHuman(versionMessage)
printForHuman(copyrightMessage)
}
// Main execution
var args = CommandLine.arguments[1...]
let (lockCommandArgs, unlockCommandArgs) = processArgs(&args)
if lockCommandArgs == nil && unlockCommandArgs == nil {
printForHuman("No commands specified. Please provide at least one command.")
printHelp()
exit(1)
}
_ = ScreenLockObserver(lockCommandArgs: lockCommandArgs, unlockCommandArgs: unlockCommandArgs)
RunLoop.main.run()