Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ jobs:
run: bash scripts/verify.sh
- name: Website browser checks
run: bash scripts/verify-website-browsers.sh
- name: Upload crash reports
if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: crash-reports
path: ~/Library/Logs/DiagnosticReports/
if-no-files-found: ignore
retention-days: 7
- name: Upload coverage to Codecov
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5.5.5
with:
Expand Down
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ let package = Package(
.testTarget(name: "DilatedControlTests", dependencies: ["DilatedControl"]),
.testTarget(
name: "DilatedUITests",
dependencies: ["DilatedUI", .product(name: "ViewInspector", package: "ViewInspector")]),
dependencies: [
"DilatedUI", "DilatedCLI", .product(name: "ViewInspector", package: "ViewInspector"),
]),
]
)
299 changes: 177 additions & 122 deletions README.md

Large diffs are not rendered by default.

21 changes: 18 additions & 3 deletions Sources/DilatedCLI/DilatedCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import Foundation

@main struct DilatedCommand {
@MainActor static func main() async {
if let launch = ClosedLidWatchdogCommand.parse(Array(CommandLine.arguments.dropFirst())) {
exit(await ClosedLidWatchdogCommand.run(launch))
}
do {
let input = try CLIInput.parse(Array(CommandLine.arguments.dropFirst()))
if input.help {
Expand Down Expand Up @@ -46,7 +49,9 @@ import Foundation
do {
response = try LocalSocketClient.send(request, to: endpoint)
} catch let error as AppConnectionUnavailable {
guard ![.status, .helperStatus].contains(request.action) else { throw error }
guard ![.status, .helperStatus, .lidPermissionStatus].contains(request.action) else {
throw error
}
try await launchApp(endpoint: endpoint)
response = try LocalSocketClient.send(request, to: endpoint)
}
Expand All @@ -56,8 +61,13 @@ import Foundation
if input.json {
print(String(decoding: try WireJSON.encode(response), as: UTF8.self))
} else if response.ok {
if [.helperStatus, .helperSetup, .helperRemove, .prepareUpdate, .prepareUninstall].contains(
request.action)
if request.action == .lidPermissionStatus, let word = response.lidPermission {
print(word)
} else if [.lidPermissionInstall, .lidPermissionRemove].contains(request.action) {
print(response.message ?? "Closed-lid permission updated.")
} else if [.helperStatus, .helperSetup, .helperRemove, .prepareUpdate, .prepareUninstall]
.contains(
request.action)
{
print(response.message ?? "Helper operation completed.")
} else if let id = response.id {
Expand Down Expand Up @@ -261,13 +271,18 @@ import Foundation
dilated hold --ttl 10m [--label NAME] [--id ID | --lid]
dilated release --id ID
dilated run [--label NAME] [--lid] -- command arguments
dilated lid-permission status|install|remove [--json]
dilated helper status|setup|remove [--json]
dilated prepare-update|prepare-uninstall [--json]
dilated package-check

Starts allow display sleep unless --display-awake is supplied.
--override-low-power explicitly allows a new hold in Low Power Mode.
--lid requires the app's approved helper and its closed-lid safety limits.
lid-permission status prints notInstalled, needsRepair, blocked, ready, notAdmin or
allowedByOtherRule (another app's sudo rule already covers closed-lid).
lid-permission install and remove ask for your administrator password in the app.
With closed-lid permission turned on, the helper commands map to it and are deprecated.
helper setup opens macOS approval when needed. Move the app to Applications first.
helper remove verifies sleep-disable is off before unregistering the helper.
prepare-update removes the helper and quits while preserving only unexpired timers.
Expand Down
13 changes: 12 additions & 1 deletion Sources/DilatedControl/LocalSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ public struct AppConnectionUnavailable: LocalizedError {
}

public enum LocalSocketClient {
/// Long enough for the admin password dialog; status keeps the short timeout.
public static let adminPromptSeconds = 300

public static func usesAdminPrompt(_ action: ControlAction) -> Bool {
[.lidPermissionInstall, .lidPermissionRemove, .helperSetup, .helperRemove].contains(action)
}

public static func send(_ request: ControlRequest, to endpoint: SocketEndpoint) throws
-> ControlResponse
{
Expand All @@ -210,8 +217,12 @@ public enum LocalSocketClient {
let powerOperation = [.stopAll, .release, .toggle].contains(request.action)
let helperOperation = [.helperSetup, .helperRemove, .prepareUpdate, .prepareUninstall].contains(
request.action)
let adminPrompt = Self.usesAdminPrompt(request.action)
configure(
fd, receiveSeconds: helperOperation ? 60 : request.closedLid ? 20 : powerOperation ? 10 : 3)
fd,
receiveSeconds: adminPrompt
? Self.adminPromptSeconds
: helperOperation ? 60 : request.closedLid ? 20 : powerOperation ? 10 : 3)
var addr = try address(for: endpoint)
let result = withUnsafePointer(to: &addr) { pointer in
pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) {
Expand Down
17 changes: 16 additions & 1 deletion Sources/DilatedControl/Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Foundation
public enum ControlAction: String, Codable, Sendable {
case status, start, lease, runner, renew, release, stopAll, toggle
case helperStatus, helperSetup, helperRemove
case lidPermissionStatus, lidPermissionInstall, lidPermissionRemove
case prepareUpdate, prepareUninstall
}
public struct ControlRequest: Codable, Sendable {
Expand Down Expand Up @@ -52,11 +53,12 @@ public struct ControlResponse: Codable, Sendable {
public var code: String?
public var helper: HelperState?
public var helperInstallation: String?
public var lidPermission: String?
public var appPID: Int32?
public init(
ok: Bool, id: UUID? = nil, snapshot: EngineSnapshot? = nil, message: String? = nil,
code: String? = nil, helper: HelperState? = nil, helperInstallation: String? = nil,
appPID: Int32? = nil
appPID: Int32? = nil, lidPermission: String? = nil
) {
self.ok = ok
self.id = id
Expand All @@ -65,6 +67,7 @@ public struct ControlResponse: Codable, Sendable {
self.code = code
self.helper = helper
self.helperInstallation = helperInstallation
self.lidPermission = lidPermission
self.appPID = appPID
}
}
Expand Down Expand Up @@ -94,6 +97,8 @@ public enum ControlRouter {
switch request.action {
case .helperStatus, .helperSetup, .helperRemove, .prepareUpdate, .prepareUninstall:
throw InputError("Helper management requires the Dilated app.")
case .lidPermissionStatus, .lidPermissionInstall, .lidPermissionRemove:
throw InputError("Closed-lid permission requires the Dilated app.")
case .status: break
case .stopAll: engine.stopAll(now: now)
case .release:
Expand Down Expand Up @@ -165,6 +170,16 @@ public struct CLIInput {
request: .init(action: verb == "prepare-update" ? .prepareUpdate : .prepareUninstall),
json: arguments.count == 2)
}
if verb == "lid-permission" {
let actions: [String: ControlAction] = [
"status": .lidPermissionStatus, "install": .lidPermissionInstall,
"remove": .lidPermissionRemove,
]
guard arguments.count >= 2, let action = actions[arguments[1]],
arguments.count == 2 || (arguments.count == 3 && arguments[2] == "--json")
else { throw InputError("Use dilated lid-permission status|install|remove [--json].") }
return CLIInput(request: .init(action: action), json: arguments.count == 3)
}
if verb == "helper" {
let actions: [String: ControlAction] = [
"status": .helperStatus, "setup": .helperSetup, "remove": .helperRemove,
Expand Down
Loading
Loading