I am developing a remote support tool for macOS. While we have successfully implemented a Privileged Helper Tool and LaunchDaemon architecture that works within an active Aqua session, we have observed a total failure to capture the screen buffer or receive input at the macOS Login Window.
Our observation of competitor software (AnyDesk, TeamViewer) shows they maintain graphical continuity through logout/restart. We are seeking the official architectural path to replicate this system-level access.
Current Technical Implementation
Architecture: A root-level LaunchDaemon manages the persistent network connection. A PrivilegedHelperTool (installed in /Library/PrivilegedHelperTools/) is used for elevated tasks.
Environment: Tested on macOS 14.x (Sonoma) and macOS 15.x (Sequoia) on Apple Silicon.
Capture Methods: We have implemented ScreenCaptureKit (SCK) as the primary engine and CGDisplayCreateImage as a fallback.
Binary Status: All components are signed with a Developer ID and have been successfully Notarized.
Observed Behavior & Blockers
The "Aqua" Success: Within a logged-in user session, our CGI correctly identifies Display IDs and initializes the capture stream. Remote control is fully functional.
The "Pre-Login" Failure: When the Mac is at the Login Window (no user logged in), the following occurs:
-
The Daemon remains active, but the screen capture buffer returns NULL or an empty frame.
-
ScreenCaptureKit fails to initialize, citing a lack of graphical context.
-
No TCC (Transparency, Consent, and Control) prompt can appear because no user session exists.
The "Bootstrap" Observation: We have identified that the loginwindow process exists in a restricted Mach bootstrap namespace that our Daemon (running in the System domain) cannot natively bridge.
Comparative Analysis (Competitor Benchmarking)
We have analyzed established remote desktop solutions like AnyDesk and Jump Desktop to understand their success at the login screen. Our findings suggest:
Dual-Context Execution: They appear to use a Global LaunchAgent with LimitLoadToSessionType = ["LoginWindow"]. This allows a child process to run as root inside the login window’s graphical domain.
Specialized Entitlements: These apps have migrated to the com.apple.developer.persistent-content-capture entitlement. This restricted capability allows them to bypass the weekly/monthly TCC re-authorization prompts and function in unattended scenarios where a user cannot click "Allow."
Questions
-
Entitlement Requirement: Is the persistent-content-capture entitlement the only supported way for a third-party app to capture the LoginWindow buffer without manual user intervention?
-
LaunchAgent Strategy: To gain a graphical context at the login screen, is it recommended to load a specialized agent into the loginwindow domain via launchctl bootstrap loginwindow ...?
-
ScreenCaptureKit vs. Legacy: Does ScreenCaptureKit officially support the LoginWindow session, or does it require an active Aqua session to initialize?
-
MDM Bypass: For Enterprise environments, can a Privacy Preferences Policy Control (PPPC) payload grant "Screen Recording" to a non-entitled Daemon specifically for the login window context?
LaunchDaemon architecture that works within an active Aqua session
Hmmm. Either you’re mixing up your terminology or things aren’t working the way you think they’re working )-: Lemme explain.
A launchd job is either a daemon or an agent, depending on how it’s installed [1]. If it’s a daemon, then LimitLoadToSession types is ignored. That’s because daemons are always loaded into the global session. In contrast, that property works for an agent, where it controls the types of sessions that system loads the agent in to.
I talk about these concepts in a lot more detail in the very-old-but-still-reasonably-accurate TN2083 Daemons and Agents.
Daemons should not be messing around with GUI stuff, and that includes ScreenCaptureKit. The standard architecture for a screen sharing product is to have both a daemon and an agent:
- The daemon does all the global stuff, like manage the network connection.
- The agent does all the GUI stuff, like record the screen and post keyboard and mouse events.
- The agent connects to the daemon over some sort of IPC mechanism, typically XPC.
If you want your product to support the pre-login context, set your agent’s LimitLoadToSession property to an array value containing both Aqua and LoginWindow.
On the agent side of this, I’ve recently prototyped this approach using ScreenCaptureKit and it works as expected on on macOS 14.4 and later. Prior to that, you have to deploy older technologies in order to work in the pre-login context (like CGDisplayStream).
IMPORTANT In macOS 14.4 we fixed a bug that prevents you from using ScreenCaptureKit in the pre-login context (r. 121253782).
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] Traditionally this would be based on whether it’s installed in /Library/LaunchDaemons or /Library/LaunchAgents. This still applies, but there are now other ways to install jobs, like SMAppService. However, these still maintain the distinction between daemons and agents.