keep-computer-on-in-your-backpack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Info.plist +29 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/Sources/main.swift +155 -0
- package/bin/install.mjs +28 -0
- package/install.sh +84 -0
- package/package.json +34 -0
- package/scripts/backpack-awake-controller +88 -0
- package/scripts/com.declankramper.backpack-awake-controller.plist +26 -0
package/Info.plist
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
3
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4
|
+
<plist version="1.0">
|
|
5
|
+
<dict>
|
|
6
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
7
|
+
<string>en</string>
|
|
8
|
+
<key>CFBundleExecutable</key>
|
|
9
|
+
<string>BackpackAwakeMenu</string>
|
|
10
|
+
<key>CFBundleIdentifier</key>
|
|
11
|
+
<string>com.codex.backpack-awake-menu</string>
|
|
12
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
13
|
+
<string>6.0</string>
|
|
14
|
+
<key>CFBundleName</key>
|
|
15
|
+
<string>Backpack Awake</string>
|
|
16
|
+
<key>CFBundlePackageType</key>
|
|
17
|
+
<string>APPL</string>
|
|
18
|
+
<key>CFBundleShortVersionString</key>
|
|
19
|
+
<string>1.0</string>
|
|
20
|
+
<key>CFBundleVersion</key>
|
|
21
|
+
<string>1</string>
|
|
22
|
+
<key>LSMinimumSystemVersion</key>
|
|
23
|
+
<string>14.0</string>
|
|
24
|
+
<key>LSUIElement</key>
|
|
25
|
+
<true/>
|
|
26
|
+
<key>NSHighResolutionCapable</key>
|
|
27
|
+
<true/>
|
|
28
|
+
</dict>
|
|
29
|
+
</plist>
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Declan Kramper
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Keep Computer On In Your Backpack
|
|
2
|
+
|
|
3
|
+
Problem: closing your laptop to put it into your backpack turns it off. This is an issue if you want to keep using local coding tools like Claude code or Codex.
|
|
4
|
+
|
|
5
|
+
Solution:
|
|
6
|
+
1. run the script below to download this app
|
|
7
|
+
2. click the backpack icon in your menu bar to keep your laptop in your backpack. This means you can use codex on the ChatGPT iPhone app to code while you’re commuting.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx keep-computer-on-in-your-backpack
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Note: the screen turns off, but computer is still on. Be wary of hot laptop running in your backpack.
|
|
14
|
+
|
|
15
|
+
@dkbuilds.co
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
private let stateDirectory = URL(fileURLWithPath: NSHomeDirectory())
|
|
4
|
+
.appendingPathComponent("Library/Application Support/BackpackAwake")
|
|
5
|
+
private let stateFile = stateDirectory.appendingPathComponent("state")
|
|
6
|
+
private let caffeinatePIDFile = stateDirectory.appendingPathComponent("caffeinate.pid")
|
|
7
|
+
|
|
8
|
+
final class AppDelegate: NSObject, NSApplicationDelegate {
|
|
9
|
+
private var statusItem: NSStatusItem!
|
|
10
|
+
private let menu = NSMenu()
|
|
11
|
+
private let stateItem = NSMenuItem(title: "Status: Checking", action: nil, keyEquivalent: "")
|
|
12
|
+
private let toggleItem = NSMenuItem(title: "Turn On", action: #selector(toggle), keyEquivalent: "")
|
|
13
|
+
private var enabled = false
|
|
14
|
+
private var caffeinateProcess: Process?
|
|
15
|
+
private var timer: Timer?
|
|
16
|
+
|
|
17
|
+
func applicationDidFinishLaunching(_ notification: Notification) {
|
|
18
|
+
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
|
|
19
|
+
statusItem.autosaveName = "BackpackAwake"
|
|
20
|
+
statusItem.button?.title = ""
|
|
21
|
+
statusItem.button?.imagePosition = .imageOnly
|
|
22
|
+
setStatusIcon(symbolName: "backpack", fallbackSymbolName: "power.circle", tooltip: "Backpack Awake: Checking")
|
|
23
|
+
|
|
24
|
+
toggleItem.target = self
|
|
25
|
+
menu.addItem(stateItem)
|
|
26
|
+
menu.addItem(NSMenuItem.separator())
|
|
27
|
+
menu.addItem(toggleItem)
|
|
28
|
+
menu.addItem(NSMenuItem(title: "Refresh", action: #selector(refresh), keyEquivalent: "r"))
|
|
29
|
+
menu.items.last?.target = self
|
|
30
|
+
menu.addItem(NSMenuItem.separator())
|
|
31
|
+
menu.addItem(NSMenuItem(title: "Quit", action: #selector(quit), keyEquivalent: "q"))
|
|
32
|
+
menu.items.last?.target = self
|
|
33
|
+
|
|
34
|
+
statusItem.menu = menu
|
|
35
|
+
refresh()
|
|
36
|
+
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] _ in
|
|
37
|
+
self?.refresh()
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc private func refresh() {
|
|
42
|
+
DispatchQueue.global(qos: .utility).async {
|
|
43
|
+
let status = self.readDesiredState()
|
|
44
|
+
DispatchQueue.main.async {
|
|
45
|
+
self.enabled = status
|
|
46
|
+
self.syncCaffeinateProcess()
|
|
47
|
+
self.render()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@objc private func toggle() {
|
|
53
|
+
let targetEnabled = !enabled
|
|
54
|
+
toggleItem.isEnabled = false
|
|
55
|
+
stateItem.title = "Status: Updating"
|
|
56
|
+
setStatusIcon(symbolName: "backpack", fallbackSymbolName: "power.circle", tooltip: "Backpack Awake: Updating")
|
|
57
|
+
|
|
58
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
59
|
+
self.writeDesiredState(targetEnabled)
|
|
60
|
+
DispatchQueue.main.async {
|
|
61
|
+
self.enabled = targetEnabled
|
|
62
|
+
self.toggleItem.isEnabled = true
|
|
63
|
+
self.syncCaffeinateProcess()
|
|
64
|
+
self.render()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private func render() {
|
|
70
|
+
if enabled {
|
|
71
|
+
setStatusIcon(symbolName: "backpack.fill", fallbackSymbolName: "power.circle.fill", tooltip: "Backpack Awake: On")
|
|
72
|
+
stateItem.title = "Status: On"
|
|
73
|
+
toggleItem.title = "Turn Off"
|
|
74
|
+
} else {
|
|
75
|
+
setStatusIcon(symbolName: "backpack", fallbackSymbolName: "power.circle", tooltip: "Backpack Awake: Off")
|
|
76
|
+
stateItem.title = "Status: Off"
|
|
77
|
+
toggleItem.title = "Turn On"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private func syncCaffeinateProcess() {
|
|
82
|
+
if enabled {
|
|
83
|
+
if caffeinateProcess?.isRunning == true {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let process = Process()
|
|
88
|
+
process.executableURL = URL(fileURLWithPath: "/usr/bin/caffeinate")
|
|
89
|
+
process.arguments = ["-im"]
|
|
90
|
+
|
|
91
|
+
do {
|
|
92
|
+
try process.run()
|
|
93
|
+
caffeinateProcess = process
|
|
94
|
+
writeCaffeinatePID(process.processIdentifier)
|
|
95
|
+
} catch {
|
|
96
|
+
caffeinateProcess = nil
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if caffeinateProcess?.isRunning == true {
|
|
100
|
+
caffeinateProcess?.terminate()
|
|
101
|
+
}
|
|
102
|
+
caffeinateProcess = nil
|
|
103
|
+
removeCaffeinatePID()
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private func readDesiredState() -> Bool {
|
|
108
|
+
if let value = try? String(contentsOf: stateFile, encoding: .utf8)
|
|
109
|
+
.trimmingCharacters(in: .whitespacesAndNewlines) {
|
|
110
|
+
return value == "on"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private func writeDesiredState(_ enabled: Bool) {
|
|
117
|
+
do {
|
|
118
|
+
try FileManager.default.createDirectory(at: stateDirectory, withIntermediateDirectories: true)
|
|
119
|
+
try (enabled ? "on\n" : "off\n").write(to: stateFile, atomically: true, encoding: .utf8)
|
|
120
|
+
} catch {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private func writeCaffeinatePID(_ pid: Int32) {
|
|
126
|
+
do {
|
|
127
|
+
try FileManager.default.createDirectory(at: stateDirectory, withIntermediateDirectories: true)
|
|
128
|
+
try "\(pid)\n".write(to: caffeinatePIDFile, atomically: true, encoding: .utf8)
|
|
129
|
+
} catch {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private func removeCaffeinatePID() {
|
|
135
|
+
try? FileManager.default.removeItem(at: caffeinatePIDFile)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func setStatusIcon(symbolName: String, fallbackSymbolName: String, tooltip: String) {
|
|
139
|
+
let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: tooltip)
|
|
140
|
+
?? NSImage(systemSymbolName: fallbackSymbolName, accessibilityDescription: tooltip)
|
|
141
|
+
image?.isTemplate = true
|
|
142
|
+
statusItem.button?.image = image
|
|
143
|
+
statusItem.button?.toolTip = tooltip
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc private func quit() {
|
|
147
|
+
NSApp.terminate(nil)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let app = NSApplication.shared
|
|
152
|
+
let delegate = AppDelegate()
|
|
153
|
+
app.delegate = delegate
|
|
154
|
+
app.setActivationPolicy(.accessory)
|
|
155
|
+
app.run()
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
if (process.platform !== "darwin") {
|
|
7
|
+
console.error("Backpack Awake can only be installed on macOS.");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
12
|
+
const installer = resolve(packageRoot, "install.sh");
|
|
13
|
+
|
|
14
|
+
const result = spawnSync("/bin/zsh", [installer], {
|
|
15
|
+
cwd: packageRoot,
|
|
16
|
+
env: {
|
|
17
|
+
...process.env,
|
|
18
|
+
BACKPACK_AWAKE_SOURCE_DIR: packageRoot,
|
|
19
|
+
},
|
|
20
|
+
stdio: "inherit",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (result.error) {
|
|
24
|
+
console.error(result.error.message);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
process.exit(result.status ?? 1);
|
package/install.sh
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
REPO_URL="${REPO_URL:-https://github.com/declankra/keep-computer-on-in-your-backpack.git}"
|
|
5
|
+
WORK_DIR="${TMPDIR:-/tmp}/backpack-awake-install"
|
|
6
|
+
SOURCE_DIR="${BACKPACK_AWAKE_SOURCE_DIR:-}"
|
|
7
|
+
APP_NAME="Backpack Awake.app"
|
|
8
|
+
APP_DIR="$HOME/Applications/$APP_NAME"
|
|
9
|
+
STATE_DIR="$HOME/Library/Application Support/BackpackAwake"
|
|
10
|
+
LOGIN_AGENT="$HOME/Library/LaunchAgents/com.declankramper.backpack-awake-menu.plist"
|
|
11
|
+
CONTROLLER_DIR="/Library/Application Support/BackpackAwake"
|
|
12
|
+
CONTROLLER_PATH="$CONTROLLER_DIR/backpack-awake-controller"
|
|
13
|
+
CONTROLLER_PLIST="/Library/LaunchDaemons/com.declankramper.backpack-awake-controller.plist"
|
|
14
|
+
LOG_DIR="/Library/Logs/BackpackAwake"
|
|
15
|
+
|
|
16
|
+
need_command() {
|
|
17
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
18
|
+
echo "Missing required command: $1" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
need_command swiftc
|
|
24
|
+
need_command codesign
|
|
25
|
+
|
|
26
|
+
if [[ -n "$SOURCE_DIR" ]]; then
|
|
27
|
+
WORK_DIR="$SOURCE_DIR"
|
|
28
|
+
else
|
|
29
|
+
need_command git
|
|
30
|
+
rm -rf "$WORK_DIR"
|
|
31
|
+
git clone --depth 1 "$REPO_URL" "$WORK_DIR"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
mkdir -p "$APP_DIR/Contents/MacOS" "$HOME/Applications" "$STATE_DIR" "$HOME/Library/LaunchAgents"
|
|
35
|
+
|
|
36
|
+
swiftc "$WORK_DIR/Sources/main.swift" -framework AppKit -o "$APP_DIR/Contents/MacOS/BackpackAwakeMenu"
|
|
37
|
+
cp "$WORK_DIR/Info.plist" "$APP_DIR/Contents/Info.plist"
|
|
38
|
+
codesign --force --deep --sign - "$APP_DIR" >/dev/null
|
|
39
|
+
|
|
40
|
+
if [[ ! -f "$STATE_DIR/state" ]]; then
|
|
41
|
+
printf 'off\n' > "$STATE_DIR/state"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
cat > "$LOGIN_AGENT" <<PLIST
|
|
45
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
46
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
47
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
48
|
+
<plist version="1.0">
|
|
49
|
+
<dict>
|
|
50
|
+
<key>Label</key>
|
|
51
|
+
<string>com.declankramper.backpack-awake-menu</string>
|
|
52
|
+
|
|
53
|
+
<key>ProgramArguments</key>
|
|
54
|
+
<array>
|
|
55
|
+
<string>/usr/bin/open</string>
|
|
56
|
+
<string>$APP_DIR</string>
|
|
57
|
+
</array>
|
|
58
|
+
|
|
59
|
+
<key>RunAtLoad</key>
|
|
60
|
+
<true/>
|
|
61
|
+
</dict>
|
|
62
|
+
</plist>
|
|
63
|
+
PLIST
|
|
64
|
+
|
|
65
|
+
if [[ "${BACKPACK_AWAKE_SKIP_PRIVILEGED:-0}" != "1" ]]; then
|
|
66
|
+
echo "Installing the privileged controller. macOS may ask for your password once."
|
|
67
|
+
sudo mkdir -p "$CONTROLLER_DIR" "$LOG_DIR"
|
|
68
|
+
sudo install -o root -g wheel -m 755 "$WORK_DIR/scripts/backpack-awake-controller" "$CONTROLLER_PATH"
|
|
69
|
+
sudo install -o root -g wheel -m 644 "$WORK_DIR/scripts/com.declankramper.backpack-awake-controller.plist" "$CONTROLLER_PLIST"
|
|
70
|
+
sudo launchctl bootout system "$CONTROLLER_PLIST" 2>/dev/null || true
|
|
71
|
+
sudo launchctl bootstrap system "$CONTROLLER_PLIST"
|
|
72
|
+
sudo launchctl kickstart -k system/com.declankramper.backpack-awake-controller
|
|
73
|
+
else
|
|
74
|
+
echo "Skipping privileged controller install."
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
if [[ "${BACKPACK_AWAKE_SKIP_LAUNCH:-0}" != "1" ]]; then
|
|
78
|
+
launchctl bootout "gui/$(id -u)" "$LOGIN_AGENT" 2>/dev/null || true
|
|
79
|
+
launchctl bootstrap "gui/$(id -u)" "$LOGIN_AGENT"
|
|
80
|
+
launchctl kickstart -k "gui/$(id -u)/com.declankramper.backpack-awake-menu"
|
|
81
|
+
open "$APP_DIR"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
echo "Installed. Look for the backpack icon in your menu bar."
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keep-computer-on-in-your-backpack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A macOS menu bar app that keeps your laptop awake with the lid closed while letting the screen turn off.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"keep-computer-on-in-your-backpack": "bin/install.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"install.sh",
|
|
11
|
+
"Info.plist",
|
|
12
|
+
"Sources",
|
|
13
|
+
"scripts"
|
|
14
|
+
],
|
|
15
|
+
"os": [
|
|
16
|
+
"darwin"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --check bin/install.mjs && zsh -n install.sh && zsh -n scripts/backpack-awake-controller && plutil -lint Info.plist scripts/com.declankramper.backpack-awake-controller.plist",
|
|
23
|
+
"pack:check": "npm pack --dry-run"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/declankra/keep-computer-on-in-your-backpack.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/declankra/keep-computer-on-in-your-backpack#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/declankra/keep-computer-on-in-your-backpack/issues"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
5
|
+
|
|
6
|
+
LOG_FILE=/Library/Logs/BackpackAwake/controller.log
|
|
7
|
+
|
|
8
|
+
log() {
|
|
9
|
+
printf '%s %s\n' "$(/bin/date '+%Y-%m-%d %H:%M:%S')" "$*" >> "$LOG_FILE"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
console_user() {
|
|
13
|
+
/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" \
|
|
14
|
+
| /usr/bin/awk '/Name :/ { print $3; exit }'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console_home() {
|
|
18
|
+
local user="$1"
|
|
19
|
+
/usr/bin/dscl . -read "/Users/$user" NFSHomeDirectory 2>/dev/null \
|
|
20
|
+
| /usr/bin/awk '{ print $2; exit }'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
state_paths() {
|
|
24
|
+
local user home
|
|
25
|
+
user="$(console_user)"
|
|
26
|
+
if [[ -z "$user" || "$user" == "loginwindow" ]]; then
|
|
27
|
+
return 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
home="$(console_home "$user")"
|
|
31
|
+
if [[ -z "$home" || ! -d "$home" ]]; then
|
|
32
|
+
return 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
STATE_DIR="$home/Library/Application Support/BackpackAwake"
|
|
36
|
+
STATE_FILE="$STATE_DIR/state"
|
|
37
|
+
PID_FILE="$STATE_DIR/caffeinate.pid"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sleep_disabled() {
|
|
41
|
+
/usr/bin/pmset -g | /usr/bin/grep -q 'SleepDisabled[[:space:]]*1'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
desired_state() {
|
|
45
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
46
|
+
/bin/cat "$STATE_FILE" 2>/dev/null | /usr/bin/tr -d '[:space:]'
|
|
47
|
+
else
|
|
48
|
+
echo "off"
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
kill_recorded_caffeinate() {
|
|
53
|
+
[[ -f "$PID_FILE" ]] || return 0
|
|
54
|
+
local pid
|
|
55
|
+
pid="$(/bin/cat "$PID_FILE" 2>/dev/null || true)"
|
|
56
|
+
[[ -n "$pid" ]] || return 0
|
|
57
|
+
|
|
58
|
+
if /bin/ps -p "$pid" -o comm= 2>/dev/null | /usr/bin/grep -q '^caffeinate$'; then
|
|
59
|
+
/bin/kill "$pid" 2>/dev/null || true
|
|
60
|
+
log "killed recorded caffeinate pid $pid"
|
|
61
|
+
fi
|
|
62
|
+
/bin/rm -f "$PID_FILE"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
apply_once() {
|
|
66
|
+
state_paths || return 0
|
|
67
|
+
|
|
68
|
+
case "$(desired_state)" in
|
|
69
|
+
on)
|
|
70
|
+
if ! sleep_disabled; then
|
|
71
|
+
/usr/bin/pmset -a disablesleep 1
|
|
72
|
+
log "enabled SleepDisabled"
|
|
73
|
+
fi
|
|
74
|
+
;;
|
|
75
|
+
*)
|
|
76
|
+
kill_recorded_caffeinate
|
|
77
|
+
if sleep_disabled; then
|
|
78
|
+
/usr/bin/pmset -a disablesleep 0
|
|
79
|
+
log "disabled SleepDisabled"
|
|
80
|
+
fi
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
while true; do
|
|
86
|
+
apply_once
|
|
87
|
+
/bin/sleep 2
|
|
88
|
+
done
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
3
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4
|
+
<plist version="1.0">
|
|
5
|
+
<dict>
|
|
6
|
+
<key>Label</key>
|
|
7
|
+
<string>com.declankramper.backpack-awake-controller</string>
|
|
8
|
+
|
|
9
|
+
<key>ProgramArguments</key>
|
|
10
|
+
<array>
|
|
11
|
+
<string>/Library/Application Support/BackpackAwake/backpack-awake-controller</string>
|
|
12
|
+
</array>
|
|
13
|
+
|
|
14
|
+
<key>RunAtLoad</key>
|
|
15
|
+
<true/>
|
|
16
|
+
|
|
17
|
+
<key>KeepAlive</key>
|
|
18
|
+
<true/>
|
|
19
|
+
|
|
20
|
+
<key>StandardOutPath</key>
|
|
21
|
+
<string>/Library/Logs/BackpackAwake/controller.out</string>
|
|
22
|
+
|
|
23
|
+
<key>StandardErrorPath</key>
|
|
24
|
+
<string>/Library/Logs/BackpackAwake/controller.err</string>
|
|
25
|
+
</dict>
|
|
26
|
+
</plist>
|