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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ closed. Super light & super useful.

## Remove

Before deleting the app, choose **Advanced → Remove Sleep Control…** to remove
the privileged helper and its saved session state. You can then quit Liddddd
and move `Liddddd.app` to the Trash.
Before deleting the app, choose
**Liddddd → Advanced → Remove Sleep Control…** to remove the privileged helper
and its saved session state. You can then quit Liddddd and move
`Liddddd.app` to the Trash.

## Privacy and security

Expand Down
2 changes: 1 addition & 1 deletion Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleVersion</key>
<string>4</string>
<string>5</string>
<key>LSMinimumSystemVersion</key>
<string>15.0</string>
<key>NSHighResolutionCapable</key>
Expand Down
50 changes: 37 additions & 13 deletions Sources/LidddddApp/HelperClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,33 @@ import LidddddCore
final class HelperClient: @unchecked Sendable {
typealias Completion = @MainActor @Sendable (Result<HelperReply, Error>) -> Void

private final class ConnectionBox: @unchecked Sendable {
let connection: NSXPCConnection
private final class CompletionGate: @unchecked Sendable {
private let lock = NSLock()
private var didFinish = false
private let connection: NSXPCConnection
private let completion: Completion

init(_ connection: NSXPCConnection) {
init(connection: NSXPCConnection, completion: @escaping Completion) {
self.connection = connection
self.completion = completion
}

func finish(_ result: Result<HelperReply, Error>) {
lock.lock()
guard !didFinish else {
lock.unlock()
return
}
didFinish = true
lock.unlock()

connection.invalidate()
Task { @MainActor in completion(result) }
}
}

private let requestTimeout: TimeInterval = 5

func status(completion: @escaping Completion) {
request({ proxy, reply in proxy.status(withReply: reply) }, completion: completion)
}
Expand Down Expand Up @@ -55,36 +74,41 @@ final class HelperClient: @unchecked Sendable {
)
connection.remoteObjectInterface = NSXPCInterface(with: LidddddHelperProtocol.self)
connection.activate()
let connectionBox = ConnectionBox(connection)
let completionGate = CompletionGate(connection: connection, completion: completion)

let errorHandler: @Sendable (Error) -> Void = { error in
connectionBox.connection.invalidate()
Task { @MainActor in completion(.failure(error)) }
completionGate.finish(.failure(error))
}

guard
let proxy = connection.remoteObjectProxyWithErrorHandler(errorHandler)
as? LidddddHelperProtocol
else {
connection.invalidate()
Task { @MainActor in
completion(.failure(HelperClientError.invalidProxy))
}
completionGate.finish(.failure(HelperClientError.invalidProxy))
return
}

operation(proxy) { data in
connectionBox.connection.invalidate()
let result = Result { try HelperCodec.decode(data) }
Task { @MainActor in completion(result) }
completionGate.finish(result)
}

DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + requestTimeout) {
completionGate.finish(.failure(HelperClientError.timedOut))
}
}
}

enum HelperClientError: LocalizedError {
case invalidProxy
case timedOut

var errorDescription: String? {
"Liddddd could not reach its sleep control."
switch self {
case .invalidProxy:
"Liddddd could not reach its sleep control."
case .timedOut:
"Liddddd's sleep control did not respond."
}
}
}
14 changes: 14 additions & 0 deletions Sources/LidddddApp/HelperInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ final class HelperInstaller {
return availability(for: service.status)
}

func availabilityAfterConnectionFailure() -> HelperAvailability {
if legacyFilesExist {
return .localHelperRepairRequired
}
#if LIDDDDD_ALLOW_ADHOC
return .localHelperInstallRequired
#else
return .localHelperRepairRequired
#endif
}

func installApplication() throws -> URL {
let sourceURL = Bundle.main.bundleURL.standardizedFileURL
guard sourceURL.pathExtension == "app" else {
Expand Down Expand Up @@ -114,6 +125,9 @@ final class HelperInstaller {
guard !legacyFilesExist else {
throw HelperInstallerError.partialOrExistingHelper
}
if service.status != .notRegistered {
try service.unregister()
}
try runLocalHelperManager(action: "install")
#else
throw HelperInstallerError.localInstallUnavailable
Expand Down
Loading