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
26 changes: 26 additions & 0 deletions .qlty/qlty.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,31 @@ exclude_patterns = [
"**/node_modules/**",
]

[file_types.osv-scanner]
# Qlty replaces this list, so retain its existing lockfile targets.
globs = [
"**/conan.lock",
"**/pubspec.lock",
"**/mix.lock",
"**/go.mod",
"**/buildscript-gradle.lockfile",
"**/gradle.lockfile",
"**/gradle/verification-metadata.xml",
"**/pom.xml",
"**/package-lock.json",
"**/pnpm-lock.yaml",
"**/yarn.lock",
"**/composer.lock",
"**/Pipfile.lock",
"**/poetry.lock",
"**/requirements.txt",
"**/pdm.lock",
"**/renv.lock",
"**/Gemfile.lock",
"**/Cargo.lock",
"**/Package.resolved",
]

[smells]
mode = "comment"

Expand All @@ -21,6 +46,7 @@ name = "bandit"

[[plugin]]
name = "osv-scanner"
version = "2.4.0"

[[plugin]]
name = "radarlint-python"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Keeps your Mac awake. A native macOS 26 menu bar app for Apple Silicon.

From [CodesWhat](https://codeswhat.com): small, sharp tools from one developer and a lot of agents.

The local preview includes timed and indefinite awake holds, display-sleep controls,
battery and Low Power Mode guards, four menu bar glyphs, optional compact/clock countdowns or end time, Settings,
configurable global shortcuts, local history and statistics, URL automation, AppleScript,
Expand Down
2 changes: 2 additions & 0 deletions Sources/DilatedUI/Automation/Downloads.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ private final class DownloadEventTarget: Sendable {
requestRefresh()
}

var pendingReadForTesting: Task<Void, Never>? { readTask }

func tick() {
guard active, let quietDeadline, clock() >= quietDeadline, readTask == nil else { return }
requestRefresh()
Expand Down
6 changes: 6 additions & 0 deletions Sources/DilatedUI/Automation/TriggersSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ import UniformTypeIdentifiers
}

@MainActor struct TriggerRuleEditor: View {
#if DEBUG
var didAppear: ((Self) -> Void)?
#endif
@State var rule: TriggerRule
@State private var criteria: TriggerCriteriaRows
var save: (TriggerRule) throws -> Void
Expand Down Expand Up @@ -220,6 +223,9 @@ import UniformTypeIdentifiers
}
.padding(24).frame(width: 560, height: 600)
.font(DesignFont.sans(13)).foregroundStyle(DesignPalette.ink).background(DesignPalette.bg)
#if DEBUG
.onAppear { didAppear?(self) }
#endif
}
}

Expand Down
6 changes: 5 additions & 1 deletion Sources/DilatedUI/Automation/WiFi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ private final class NativeWiFiAuthorization: NSObject, WiFiAuthorizationSource,
}

@MainActor final class WiFiObserver {
typealias Schedule = @MainActor (@escaping @MainActor @Sendable () -> Void) -> Void
private let schedule: Schedule
private let suppliedAccess: WiFiAccess?
private let readSSIDs: () -> Set<Data>?
private let changed: () -> Void
Expand All @@ -144,8 +146,10 @@ private final class NativeWiFiAuthorization: NSObject, WiFiAuthorizationSource,

init(
access: WiFiAccess? = nil, readSSIDs: (() -> Set<Data>?)? = nil,
schedule: @escaping Schedule = { action in Task { @MainActor in action() } },
changed: @escaping () -> Void
) {
self.schedule = schedule
suppliedAccess = access
self.readSSIDs = readSSIDs ?? { WiFiCatalog.read() }
self.changed = changed
Expand Down Expand Up @@ -187,7 +191,7 @@ private final class NativeWiFiAuthorization: NSObject, WiFiAuthorizationSource,
guard !notificationPending else { return }
notificationPending = true
let generation = generation
Task { @MainActor [weak self] in
schedule { [weak self] in
guard let self, self.access != nil, self.generation == generation else { return }
self.notificationPending = false
self.changed()
Expand Down
2 changes: 2 additions & 0 deletions Sources/DilatedUI/Feedback/FeedbackController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ struct FeedbackNotice: Sendable {
@ObservationIgnored private var requests: [FeedbackNotice.Kind: UUID] = [:]
@ObservationIgnored private var tasks: [FeedbackNotice.Kind: Task<Void, Never>] = [:]

var pendingDeliveriesForTesting: [Task<Void, Never>] { Array(tasks.values) }

init(service: any FeedbackService, sounds: any FeedbackSoundPlaying) {
self.service = service
self.sounds = sounds
Expand Down
2 changes: 1 addition & 1 deletion Sources/DilatedUI/Settings/SettingsWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private enum SettingsPage: String, CaseIterable, Identifiable {
}
.padding(.horizontal, 28).padding(.top, 12).padding(.bottom, 28)
.frame(maxWidth: .infinity, alignment: .leading)
}.background(DesignPalette.bg)
}.background(DesignPalette.bg).id(page)
}
.font(DesignFont.sans(13)).foregroundStyle(DesignPalette.ink).tint(SettingsTheme.control)
.frame(minWidth: 860, minHeight: 600)
Expand Down
15 changes: 14 additions & 1 deletion Tests/DilatedCoreTests/PackageHelperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ struct PackageHelperTests {
process.waitUntilExit()
}
}
try await Task.sleep(for: .milliseconds(50))
let deadline = ContinuousClock.now.advanced(by: .seconds(5))
let pattern = "^" + NSRegularExpression.escapedPattern(for: executable.path) + "($|[[:space:]])"
var ready = false
while ContinuousClock.now < deadline {
let found = try await BoundedProcess.run(
executable: URL(fileURLWithPath: "/usr/bin/pgrep"),
arguments: ["-f", pattern], timeout: 0.5)
if !found.timedOut, found.status == 0 {
ready = true
break
}
try await Task.sleep(for: .milliseconds(20))
}
try #require(ready, "The child did not adopt the expected app identity.")
await #expect(throws: (any Error).self) {
try await PackageHelperCheck.verifyAppAbsent(executable: executable)
}
Expand Down
4 changes: 3 additions & 1 deletion Tests/DilatedCoreTests/TriggerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ struct TriggerTests {
var input = TriggerInputs()
input.processNames = ["make"]
evaluator.reconcile(rules: [rule], inputs: input, engine: &engine, now: start)
#expect(evaluator.activeHoldIDs[rule.id] == engine.holds.first?.id)
try #require(engine.holds.count == 1)
let hold = try #require(engine.holds.first)
#expect(evaluator.activeHoldIDs[rule.id] == hold.id)
#expect(!engine.desiredState.closedLid)
evaluator.reconcile(rules: [], inputs: input, engine: &engine, now: start)
#expect(evaluator.activeHoldIDs.isEmpty && engine.holds.isEmpty)
Expand Down
50 changes: 45 additions & 5 deletions Tests/DilatedUITests/DownloadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ import Testing
if failure { fixture.readFails = true } else { fixture.snapshot.files = [:] }
fixture.event?()
await fixture.drain()
#expect(fixture.ends.count == 1)
try #require(fixture.ends.count == 1)
#expect((fixture.ends[0] != nil) == failure)
#expect(fixture.cancellations == 1)
}
Expand Down Expand Up @@ -303,6 +303,7 @@ import Testing
defer { try? FileManager.default.removeItem(at: directory) }
var power = PowerState()
let model = AppModel(endpoint: .init(stateDirectory: directory), power: { power })
defer { model.shutdown() }
let fixture = DownloadFixture()
_ = try model.startDownload(fixture.target, quietSeconds: 30, makeMonitor: fixture.monitor)
await fixture.drain()
Expand All @@ -311,9 +312,43 @@ import Testing
#expect(model.engine.holds.isEmpty && fixture.cancellations == 1)
power.lowPowerMode = false
model.refreshPower()
_ = try model.startDownload(fixture.target, quietSeconds: 30, makeMonitor: fixture.monitor)
var readGate: CheckedContinuation<DownloadSnapshot, Never>?
var readReturned = false
var completions = 0
var retainedMonitor: DownloadMonitor?
_ = try model.startDownload(fixture.target, quietSeconds: 30) { target, seconds, finished in
let monitor = DownloadMonitor(
target: target, quietSeconds: seconds,
read: { _ in
let snapshot = await withCheckedContinuation { readGate = $0 }
readReturned = true
return snapshot
},
subscribe: { _, _ in { fixture.cancellations += 1 } }, clock: { 0 },
finished: {
completions += 1
finished($0)
})
retainedMonitor = monitor
return monitor
}
let monitor = try #require(retainedMonitor)
defer {
readGate?.resume(returning: .init(files: [:]))
monitor.stop()
}
let pending = try #require(monitor.pendingReadForTesting)
let deadline = ContinuousClock.now.advanced(by: .seconds(5))
while readGate == nil && ContinuousClock.now < deadline {
try await Task.sleep(for: .milliseconds(1))
}
let gate = try #require(readGate)
model.shutdown()
await fixture.drain()
#expect(pending.isCancelled)
gate.resume(returning: .init(files: [:]))
readGate = nil
await pending.value
#expect(readReturned && completions == 0)
#expect(model.engine.holds.isEmpty && fixture.cancellations == 2)
}
}
Expand All @@ -331,16 +366,18 @@ import Testing
var cancellations = 0
var ends: [String?] = []
var event: (@MainActor @Sendable () -> Void)?
private weak var lastMonitor: DownloadMonitor?
func monitor() -> DownloadMonitor {
monitor(target, 30) { [weak self] in self?.ends.append($0) }
}
func monitor(
_ target: DownloadTarget, _ quietSeconds: TimeInterval,
_ finished: @escaping @MainActor (String?) -> Void
) -> DownloadMonitor {
DownloadMonitor(
let monitor = DownloadMonitor(
target: target, quietSeconds: quietSeconds,
read: { [self] _ in
try await Task.sleep(for: .milliseconds(5))
reads += 1
if readFails { throw CocoaError(.fileReadNoPermission) }
if let readError { throw readError }
Expand All @@ -350,6 +387,9 @@ import Testing
self.event = event
return { [self] in cancellations += 1 }
}, clock: { [self] in uptime }, finished: finished)
lastMonitor = monitor
return monitor
}
func drain() async { for _ in 0..<20 { await Task.yield() } }
var pendingRead: Task<Void, Never>? { lastMonitor?.pendingReadForTesting }
func drain() async { await pendingRead?.value }
}
127 changes: 67 additions & 60 deletions Tests/DilatedUITests/DriveAliveTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,69 +327,76 @@ import Testing
#expect(delays == [120])
}

@Test(.enabled(if: ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"] != nil))
func nativeSelectedVolumeAcceptsWritesOrRejectsReadOnlyMounts() async throws {
let path = try #require(ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"])
let folder = URL(fileURLWithPath: path)
let selected = try #require(try NativeDriveAlive.mounted().first { $0.volume.path == path })
let before = try FileManager.default.contentsOfDirectory(atPath: path).sorted()
if selected.readOnly {
await #expect(throws: (any Error).self) { try await NativeDriveAlive.pulse(selected.volume) }
} else {
try await NativeDriveAlive.pulse(selected.volume)
@Suite(.serialized)
@MainActor struct NativeFixtureTests {
@Test(.enabled(if: ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"] != nil))
func nativeSelectedVolumeAcceptsWritesOrRejectsReadOnlyMounts() async throws {
let path = try #require(ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"])
let folder = URL(fileURLWithPath: path)
let selected = try #require(try NativeDriveAlive.mounted().first { $0.volume.path == path })
let before = try FileManager.default.contentsOfDirectory(atPath: path).sorted()
if selected.readOnly {
await #expect(throws: (any Error).self) {
try await NativeDriveAlive.pulse(selected.volume)
}
} else {
try await NativeDriveAlive.pulse(selected.volume)
}
#expect(try FileManager.default.contentsOfDirectory(atPath: path).sorted() == before)
#expect(try Data(contentsOf: folder.appendingPathComponent("user-file")) == Data([1, 2, 3]))
}
#expect(try FileManager.default.contentsOfDirectory(atPath: path).sorted() == before)
#expect(try Data(contentsOf: folder.appendingPathComponent("user-file")) == Data([1, 2, 3]))
}

@Test(
.enabled(if: ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"] != nil),
.timeLimit(.minutes(1)))
func nativeMixedModesPulseOnlyTheEligibleVolumeAndStillPause() async throws {
let path = try #require(ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"])
let mount = try #require(try NativeDriveAlive.mounted().first { $0.volume.path == path })
let before = try FileManager.default.contentsOfDirectory(atPath: path).sorted()
var selected = mount.volume
selected.mode = .always
var pulses: [String] = []
var scheduled: (@MainActor @Sendable () -> Void)?
let didSchedule = Signal()
let controller = DriveAliveController(
clock: { 1000 },
pulse: { volume in
pulses.append(volume.id)
try await NativeDriveAlive.pulse(volume)
},
schedule: { _, fire in
scheduled = fire
didSchedule.fire()
return {}
})
defer { controller.stop() }
var settings = DriveAliveSettings()
settings.enabled = true
settings.volumes = [
.init(id: "native-awake-only", name: "Inactive", path: "/Volumes/Missing", mode: .awakeOnly),
selected,
]
controller.configure(settings, awake: false, paused: false)
try await didSchedule.wait()
try #require(scheduled != nil)
#expect(pulses == [selected.id])
#expect(controller.statuses["native-awake-only"] == nil)
if mount.readOnly {
#expect(controller.statuses[selected.id]?.contains("read-only") == true)
} else {
#expect(controller.statuses[selected.id] == "Active")
@Test(
.enabled(if: ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"] != nil),
.timeLimit(.minutes(1)))
func nativeMixedModesPulseOnlyTheEligibleVolumeAndStillPause() async throws {
let path = try #require(ProcessInfo.processInfo.environment["DILATED_DRIVE_FIXTURE"])
let mount = try #require(try NativeDriveAlive.mounted().first { $0.volume.path == path })
let before = try FileManager.default.contentsOfDirectory(atPath: path).sorted()
var selected = mount.volume
selected.mode = .always
var pulses: [String] = []
var scheduled: (@MainActor @Sendable () -> Void)?
let didSchedule = Signal()
let controller = DriveAliveController(
clock: { 1000 },
pulse: { volume in
pulses.append(volume.id)
try await NativeDriveAlive.pulse(volume)
},
schedule: { _, fire in
scheduled = fire
didSchedule.fire()
return {}
})
defer { controller.stop() }
var settings = DriveAliveSettings()
settings.enabled = true
settings.volumes = [
.init(
id: "native-awake-only", name: "Inactive", path: "/Volumes/Missing", mode: .awakeOnly),
selected,
]
controller.configure(settings, awake: false, paused: false)
try await didSchedule.wait()
try #require(scheduled != nil)
#expect(pulses == [selected.id])
#expect(controller.statuses["native-awake-only"] == nil)
if mount.readOnly {
#expect(controller.statuses[selected.id]?.contains("read-only") == true)
} else {
#expect(controller.statuses[selected.id] == "Active")
}
controller.configure(settings, awake: false, paused: true)
scheduled?()
for _ in 0..<20 { await Task.yield() }
#expect(pulses == [selected.id])
#expect(try FileManager.default.contentsOfDirectory(atPath: path).sorted() == before)
#expect(
try Data(contentsOf: URL(fileURLWithPath: path).appendingPathComponent("user-file"))
== Data([1, 2, 3]))
}
controller.configure(settings, awake: false, paused: true)
scheduled?()
for _ in 0..<20 { await Task.yield() }
#expect(pulses == [selected.id])
#expect(try FileManager.default.contentsOfDirectory(atPath: path).sorted() == before)
#expect(
try Data(contentsOf: URL(fileURLWithPath: path).appendingPathComponent("user-file"))
== Data([1, 2, 3]))

}

@Test func writerCreatesOnlyANewFileAndCleansUpWithoutTouchingExistingFiles() throws {
Expand Down
Loading
Loading