-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
316 additions
and
1 deletion.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
5.10 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,60 @@ | ||
# run-on-macos-screen-unlock | ||
A tiny Swift program to run a command whenever the screen unlocks | ||
|
||
A tiny Swift program to run a command whenever the screen unlocks \ | ||
(I use it for mounting remounting network shares after sleep) | ||
|
||
```sh | ||
run-on-macos-screen-unlock ./examples/mount-network-shares.sh | ||
``` | ||
|
||
# Install | ||
|
||
1. Download | ||
```sh | ||
curl --fail-with-body -L -O https://github.com/coolaj86/run-on-macos-screen-unlock/releases/download/v1.0.0/run-on-macos-screen-unlock-v1.0.0.tar.gz | ||
``` | ||
2. Extract | ||
```sh | ||
tar xvf ./run-on-macos-screen-unlock-v1.0.0.tar.gz | ||
``` | ||
3. Allow running even though it's unsigned | ||
```sh | ||
xattr -r -d com.apple.quarantine ./run-on-macos-screen-unlock | ||
``` | ||
4. Move into your `PATH` | ||
```sh | ||
mv ./run-on-macos-screen-unlock ~/bin/ | ||
``` | ||
# Build from Source | ||
1. Install XCode Tools \ | ||
(including `git` and `swift`) | ||
```sh | ||
xcode-select --install | ||
``` | ||
2. Clone and enter the repo | ||
```sh | ||
git clone https://github.com/coolaj86/run-on-macos-screen-unlock.git | ||
pushd ./run-on-macos-screen-unlock/ | ||
``` | ||
3. Build with `swiftc` | ||
```sh | ||
swiftc ./run-on-macos-screen-unlock.swift | ||
``` | ||
# Release | ||
1. Git tag and push | ||
```sh | ||
git tag v1.0.x | ||
git push --tags | ||
``` | ||
2. Create a release \ | ||
<https://github.com/coolaj86/run-on-macos-screen-unlock/releases/new> | ||
3. Tar and upload | ||
```sh | ||
tar cvf ./run-on-macos-screen-unlock-v1.0.x.tar ./run-on-macos-screen-unlock | ||
gzip ./run-on-macos-screen-unlock-v1.0.x.tar | ||
open . | ||
``` |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
#!/bin/sh | ||
set -e | ||
set -u | ||
|
||
fn_mount_share() { | ||
a_smb_share="${1:-}" | ||
# ex: /usr/bin/osascript -e 'mount volume "smb://[email protected]/TimeMachineBackups"' | ||
/usr/bin/osascript -e "mount volume \"${a_smb_share}\"" | ||
} | ||
|
||
fn_version() { | ||
echo "mount-macos-network-shares v1.0.0 (2024-08-19)" | ||
echo "Copyright AJ ONeal (MPL-2.0)" | ||
} | ||
|
||
fn_help() { | ||
echo "" | ||
echo "USAGE" | ||
echo " mount-macos-network-shares [path-to-config]" | ||
echo "" | ||
echo "OPTIONS" | ||
echo " --help - print this message" | ||
echo " -V,--version - print the version" | ||
echo "" | ||
echo "CONFIG" | ||
echo " Default config file:" | ||
echo " ~/.config/macos-network-shares/urls.conf" | ||
echo "" | ||
echo " Example config file contents:" | ||
echo " smb://puter:[email protected]/TimeMachineBackups" | ||
echo " smb://[email protected]/Family Photos" | ||
echo "" | ||
} | ||
|
||
fn_mount_shares() { | ||
b_urls_file="${1}" | ||
while IFS= read -r b_share_url; do | ||
fn_mount_share "${b_share_url}" | ||
done < "${b_urls_file}" | ||
} | ||
|
||
main() { | ||
if ! test -f ~/.config/macos-network-shares/urls.conf; then | ||
mkdir -p ~/.config/macos-network-shares/ || true | ||
chmod 0700 ~/.config/macos-network-shares || true | ||
touch ~/.config/macos-network-shares/urls.conf || true | ||
chmod 0600 ~/.config/macos-network-shares/urls.conf || true | ||
echo "#smb://user:[email protected]/TimeMachineBackups" >> ~/.config/macos-network-shares/urls.conf || true | ||
fi | ||
|
||
b_urls_file="${1-$HOME/.config/macos-network-shares/urls.conf}" | ||
case "${b_urls_file}" in | ||
--version | -V | version) | ||
fn_version | ||
exit 0 | ||
;; | ||
--help | help) | ||
fn_help | ||
exit 0 | ||
;; | ||
*) ;; | ||
esac | ||
|
||
if ! test -e "${b_urls_file}" && grep -q -v -E '^\s*(#.*)?$' "${b_urls_file}"; then | ||
{ | ||
echo "" | ||
echo "ERROR" | ||
echo " url list '${b_urls_file}' is empty or does not exist" | ||
echo "" | ||
fn_help | ||
} >&2 | ||
fi | ||
|
||
{ | ||
echo "Network URLs List: ${b_urls_file}" | ||
} >&2 | ||
|
||
fn_mount_shares "${b_urls_file}" | ||
} | ||
|
||
main "$@" |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import Foundation | ||
|
||
let name = (CommandLine.arguments[0] as NSString).lastPathComponent | ||
let version = "1.0.0" | ||
let build = "2024-08-19-001" | ||
|
||
let versionMessage = """ | ||
\(name) \(version) (\(build)) | ||
""" | ||
|
||
let copyrightMessage = """ | ||
Copyright 2024 AJ ONeal <[email protected]> | ||
""" | ||
|
||
let helpMessage = """ | ||
Runs a user-specified command whenever the screen is unlocked by | ||
listening for the "com.apple.screenIsUnlocked" event, using /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] <command> [--] [command-arguments] | ||
OPTIONS | ||
--version, -V, version | ||
Display the version information and exit. | ||
--help, help | ||
Display this help and exit. | ||
DESCRIPTION | ||
\(name) is a simple command-line tool that demonstrates how to handle | ||
version and help flags in a Swift program following POSIX conventions. | ||
""" | ||
|
||
signal(SIGINT) { _ in | ||
printForHuman("received ctrl+c, exiting...\n") | ||
exit(0) | ||
} | ||
|
||
enum ScriptError: Error { | ||
case fileNotFound | ||
} | ||
|
||
func printForHuman(_ message: String) { | ||
if let data = message.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 scriptPath = String(data: data, encoding: .utf8)? | ||
.trimmingCharacters(in: .whitespacesAndNewlines) | ||
else { | ||
return nil | ||
} | ||
|
||
if commandv.terminationStatus != 0, scriptPath.isEmpty { | ||
return nil | ||
} | ||
|
||
return scriptPath | ||
} | ||
|
||
class ScreenLockObserver { | ||
var commandPath: String | ||
var commandArgs: ArraySlice<String> | ||
|
||
init(_ commandArgs: ArraySlice<String>) { | ||
self.commandPath = commandArgs.first! | ||
self.commandArgs = commandArgs | ||
|
||
let dnc = DistributedNotificationCenter.default() | ||
|
||
_ = dnc.addObserver(forName: NSNotification.Name("com.apple.screenIsLocked"), object: nil, queue: .main) { _ in | ||
NSLog("notification: com.apple.screenIsLocked") | ||
} | ||
|
||
NSLog("Waiting for 'com.apple.screenIsUnlocked' to run \(self.commandArgs)") | ||
_ = dnc.addObserver(forName: NSNotification.Name("com.apple.screenIsUnlocked"), object: nil, queue: .main) { _ in | ||
NSLog("notification: com.apple.screenIsUnlocked") | ||
self.runOnUnlock() | ||
} | ||
} | ||
|
||
private func runOnUnlock() { | ||
let task = Process() | ||
task.launchPath = "/usr/bin/command" | ||
task.arguments = Array(commandArgs) | ||
task.standardOutput = FileHandle.standardOutput | ||
task.standardError = FileHandle.standardError | ||
|
||
do { | ||
try task.run() | ||
} catch { | ||
printForHuman("Failed to run \(self.commandPath): \(error.localizedDescription)\n") | ||
if let nsError = error as NSError? { | ||
printForHuman("Error details: \(nsError)\n") | ||
} | ||
exit(1) | ||
} | ||
|
||
task.waitUntilExit() | ||
} | ||
} | ||
|
||
@discardableResult | ||
func removeItem(_ array: inout ArraySlice<String>, _ item: String) -> Bool { | ||
if let index = array.firstIndex(of: item) { | ||
array.remove(at: index) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func processArgs(_ args: inout ArraySlice<String>) -> ArraySlice<String> { | ||
var childArgs: ArraySlice<String> = [] | ||
if let delimiterIndex = args.firstIndex(of: "--") { | ||
let childArgsIndex = delimiterIndex + 1 | ||
childArgs = args[childArgsIndex...] | ||
args.removeSubrange(delimiterIndex...) | ||
} | ||
if removeItem(&args, "--help") || removeItem(&args, "help") { | ||
printForHuman(versionMessage) | ||
printForHuman("\n") | ||
printForHuman(helpMessage) | ||
printForHuman("\n") | ||
printForHuman(copyrightMessage) | ||
exit(0) | ||
} | ||
if removeItem(&args, "--version") || removeItem(&args, "-V") || removeItem(&args, "version") { | ||
printForHuman(versionMessage) | ||
printForHuman(copyrightMessage) | ||
exit(0) | ||
} | ||
|
||
childArgs = args + childArgs | ||
guard childArgs.count > 0 else { | ||
printForHuman(versionMessage) | ||
printForHuman("\n") | ||
printForHuman(helpMessage) | ||
printForHuman("\n") | ||
printForHuman(copyrightMessage) | ||
exit(1) | ||
} | ||
|
||
let commandName = childArgs.first! | ||
guard let commandPath = getCommandPath(commandName) else { | ||
printForHuman("ERROR:\n \(commandName) not found in PATH\n") | ||
exit(1) | ||
} | ||
|
||
childArgs[childArgs.startIndex] = commandPath | ||
return childArgs | ||
} | ||
|
||
var args = CommandLine.arguments[1...] | ||
let commandArgs = processArgs(&args) | ||
_ = ScreenLockObserver(commandArgs) | ||
|
||
RunLoop.main.run() |