Skip to content

Add launchd-based system scheduling and UI support for system-scheduled timers - #3

Open
holgerkrupp wants to merge 1 commit into
mainfrom
codex/add-system-integrated-execution-methods-m65wsn
Open

Add launchd-based system scheduling and UI support for system-scheduled timers#3
holgerkrupp wants to merge 1 commit into
mainfrom
codex/add-system-integrated-execution-methods-m65wsn

Conversation

@holgerkrupp

Copy link
Copy Markdown
Owner

Motivation

  • Allow repeating timers to run even when the app is not in the foreground by installing per-user LaunchAgent jobs so macOS launchd handles recurring launches.
  • Surface and reconcile the system-scheduled state in the model and UI so scheduled timers show accurate next-fire times and progress.
  • Fall back to the in-app Timer when launchd installation fails and support correct countdown visuals for system-scheduled timers.

Description

  • Add LaunchAgentScheduler utility with install, uninstall, isInstalled, label, and helper methods that write a plist into ~/Library/LaunchAgents and call launchctl to bootstrap/enable jobs.
  • Extend TimerItem with isSystemScheduleEnabled and systemScheduleStartDate, integrate LaunchAgentScheduler in startTimer/stopTimer, add reconcileSystemScheduleState, nextSystemFireDate, and update calcProgress to compute progress for system-scheduled timers without an in-app Timer.
  • Update views: call timer.reconcileSystemScheduleState() from TimerItemList, show a "Scheduled by macOS launchd" caption and pass repeats into CountdownView in TimerSummaryView, and add a clock ticker to advance nextFireDate for system-scheduled timers; change CountdownView to accept repeats and compute wrap-around remaining time via remainingTime(until:from:).
  • Add a short scheduling note to README.md describing the launchd behavior and when the app falls back to in-app timers.

Testing

  • No automated tests were executed for this change.

Codex Task

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3dce67bafe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

guard let home = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first else {
throw SchedulerError.unsupportedHomeDirectory
}
return home.appendingPathComponent("LaunchAgents", isDirectory: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use a real per-user LaunchAgents location

With AutoRun.entitlements enabling the App Sandbox, FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask) resolves inside the app container rather than the user's ~/Library. That means these plists are written under the container's Library/LaunchAgents, so after a logout/reboot launchd will not auto-load them from the standard per-user LaunchAgents directory, while reconcileSystemScheduleState() can still mark the timer active just because the container plist exists; users then see an active system-scheduled timer that never fires.

Useful? React with 👍 / 👎.

Comment thread AutoRun/TimerItem.swift
isSystemScheduleEnabled = true
nextFireDate = nextSystemFireDate()
isActive = true
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear stale in-app timers after launchd install

When a timer previously used the in-app path (for example a one-shot timer or a launchd fallback), stopTimer() only invalidates the Timer and leaves the non-nil object in timer. This success path returns without clearing it, but isSystemScheduled requires timer == nil, so the UI will not show the launchd state and the clock handler will not advance nextFireDate even though the LaunchAgent was installed.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant