Keeping NSApplication during LoginWindow

I'm writing a Agent Application that records the Screen and I'm trying to keep the recording going even when the user logs out. I've been reading about LaunchAgents and LaunchDaemons. From what I understand a Prelogin LaunchAgent is my best bet since I need NSApplication to keep my process going and to access the screens. I'm only able to relaunch the app after login or to reopen the app when the os closes it. But the recording process is interrupted. Here is what I have as far as my LaunchAgent.

My prelogin LaunchAgent (to open the app during the LoginWindow context)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.myApp.prelogin</string>
  <key>LimitLoadToSessionType</key>
  <string>LoginWindow</string>
  <key>RunAtLoad</key>
  </true>
  <key>ProgramArguments</key>
  <array>
    <string>/Library/myApp/myAgent0</string>
    <string>service</key>
  <array>
  <key>QueueDirectories</key>
  <array>
      <string>/etc/myApp/service</string>
  </array>
</dict>
</plist>

Per user launch agent (to keep the application open while a file exists in the path of QueDirectories)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.myApp.peruser</string>
	<key>LimitLoadToSessionType</key>
	<string>Aqua</string>
	<key>RunAtLoad</key>
	<true/>
	<key>ProgramArguments</key>
	<array>
		<string>/Library/myApp/vmyAgent0</string>
		<string>service</string>
	</array>
	<key>QueueDirectories</key>
	<array>
		<string>/etc/myApp/service</string>
	</array>
</dict>
</plist>

And then a daemon that I was suggested to add.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.myApp.server</string>
	<key>ProgramArguments</key>
	<array>
		<string>/Library/myApp/myAgent0</string>
		<string>-service</string>
	</array>
	<key>QueueDirectories</key>
	<array>
		<string>/etc/myApp/service</string>
	</array>
</dict>
</plist>

Currently the application get closed down when the user logs out and gets reopened when the user logs back in or every time I close it (until I remove the file from QueueDirectories). I'm not sure if it gets open during LoginWindow but I don't see any recordings of that so I don't think it does. I know it is possible since VNC viewer does it (you can remotely log into your Mac). I'm not even sure I'm on the right track and I found this other question which tells me I'm on the wrong side using NSApplication for something like this? I'm in need of confirmations lol.

Thank you in advance.

I just posted a summary of how to make this work in a different thread. Read that through and follow up here if you have further questions.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hello! Thank you for the prompt response. The resources provided and your explanation really helped me (I'm still reading through them to understand them better) but I do have some questions I would love your clarification on.

The system will then start an instance of this (my NSApplication?) in each GUI login session

Does this mean that I have access to libraries like NSEvent, AVFoundation and all the good that comes with NSApplication? I wonder this because my application currently saves the new recording file but it is empty for some reason?

That can use IPC (preferably XPC) to connect to your daemon, tell it about the state of the GUI login session it’s loaded in, and also perform work on the daemon’s behalf.

So is my agent or the demon going to be doing the work related to the ui? (getting screen frames, sevenths, etc).

Muchas gracias again.

Does this mean that I have access to libraries like NSEvent, AVFoundation and all the good that comes with NSApplication?

Yes, although this is an odd environment and you will find some sharp edge cases.

I wonder this because my application currently saves the new recording file but it is empty for some reason?

This is most likely a TCC issue, that is, your agent does not have the System Preferences > Security & Privacy > Privacy > Screen Recording privilege.

So is my agent or the demon going to be doing the work related to the ui?

Your agent. To do UI stuff you need to link with frameworks, like AppKit, that are not daemon safe.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for confirming those thoughts. I have made good progress but now I find my self facing new ones and with some new hypothesis. When in the LoginView, I'm not able to capture the key strokes after the user clicks on the password textview. Which I'm assuming is a security measure? or am I doing something wrong? 

From the comments before 

In the specific case of the pre-login context, you’ll find that the agent is started as root when the login window context starts up and then, when the user logs in, that instance is terminated and a new one is started as that user.

 Why is that my AppDelegate function "applicationWillTerminate" does not get called the root's instance for the LoginWindow is terminated to start a new one as the user? — gamakaze less than a minute ago

Accepted Answer

When in the LoginView, I'm not able to capture the key strokes after the user clicks on the password textview. Which I'm assuming is a security measure?

Correct. This is known as secure event input. It’s automatically enabled by NSSecureTextField but it can also be enabled directly. For example, Terminal does this when the terminal goes into no-echo mode.

The API to detect and manually configure secure event input is really obscure, namely IsSecureEventInputEnabled, EnableSecureEventInput, and DisableSecureEventInput, all in the HIToolbox subframework within the Carbon framework.

There’s no up-to-date documentation for these routines (r. 84701788)-: However, the comments in the CarbonEventsCore.h header are very clear. Also, there’s good info in the Documentation Archive, namely Technote 2150 Using Secure Event Input Fairly.

Why is that my AppDelegate function -applicationWillTerminate: does not get called the root's instance for the LoginWindow is terminated to start a new one as the user?

Because your code is running as a launchd agent and launchd jobs have a different process lifecycle than standard applications. See the launchd.plist man page for more.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I'm back again! Thank you for your previous pointers, I have spent a good amount looking at the launchd.plist man page but I was not able to find anything about the life cycle. I did try other ways to keep it alive longer like queueDirectories but I was unsuccessful. Maybe it has something do with how I'm doin things so I'm going to elaborate more.

Everything I'm trying to do is in a swift dylib that communicates with cSharp code (executable file) I run using the mono framework. Currently I start the process with terminal

mono cSharp.exe

that calls a function in my swift dylib that starts my NSApplication like this.

let appDel = AppDelegate()
let appStarter = AppStarter()

@_cdecl("startNSApplication")
public func startNSApplication() {
    appStarter.startNSApplication(appDel)
}

//In my AppStarter class
func startNSApplication(_ appDel: AppDelegate) {
        NSApplication.shared.delegate = appDel
        NSApplication.shared.run()
 }

The "Program Parameters in my LaunchAgent.plist is an executable file that just runs:

mono cSharp.exe (with the right paths)

This way I can record the LoginScreen (and when user is logged in) successfully when I manually close the app (through a button) because NSApplication.terminate gets called and it triggers "applicationWillTerminate" allowing me to properly shutdown things.

So the issue I'm facing is that whenever the app is shutdown by the user loggin in or out, when the process get terminated through terminal, everything gets shut down suddenly and without calling NSApplication.shared.terminate.

I have read about the key in the Info.plist

<key>NSSupportsSuddenTermination</key>
<true/>

but that leads me to my next question. How could I add an info plist to my swift.dylib? I assume the answer to that is to put it in an .app file but where exactly do I need to put it so the OS finds it when the application is opened? Sorry for the super long comment but I figured I would get more help by providing more info.

How could I add an info plist to my swift.dylib?

You can’t. An Info.plist is associated with a bundle, and dynamic libraries are not bundles.

Having said that, NSSupportsSuddenTermination won’t help. You’re creating a launchd agent and that does not use the standard Mac application lifecycle. Which brings me back to the the man page. Lemme quote the relevant bits:

A daemon or agent launched by launchd SHOULD:

  • Handle the SIGTERM signal, preferably with a dispatch(3) source, and respond to this signal by unwinding any outstanding work quickly and then exiting.

EnableTransactions <boolean>

This key instructs launchd that the job uses xpc_transaction_begin(3) and xpc_transaction_end(3) to track outstanding transactions. When a process has an outstanding transaction, it is considered active, otherwise inactive. A transaction is automatically created when an XPC message expecting a reply is received, until the reply is sent or the request message is discarded. When launchd stops an active process, it sends SIGTERM first, and then SIGKILL after a reasonable timeout. If the process is inactive, SIGKILL is sent immediately.

EnablePressuredExit <boolean>

This key opts the job into the system’s Pressured Exit facility. Use of this key implies EnableTransactions , and also lets the system consider process eligible for reclamation under memory pressure when it’s inactive. See xpc_main(3) for details. Jobs that opt into Pressured Exit will be automatically relaunched if they exit or crash while holding open transactions.

NOTE launchd(8) does not respect EnablePressuredExit for jobs that have KeepAlive set to true.

IMPORTANT Jobs which opt into Pressured Exit will ignore SIGTERM rather than exiting by default, so a dispatch(3) source must be used when handling this signal.

So, assuming you set KeepAlive (or one of its legacy alternatives) then you don’t get transactions or pressure exit, which means that you’re always considered to be an “active process” and thus you’ll get the SIGTERM then SIGKILL dance.

ps My much-in-need-of-an-update PreLoginAgents sample code shows how to handle SIGTERM using Dispatch.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

85009422

Hola de nuevo!

Following up on getting the keyboard events from the password textfield in the LoginView.

In my LaunchAgentApp:

"IsSecureInputEventEnabled" during LoginView always returns false but the documentation in the Carbon.HIToolboox.CarbonEventCore states:

This API returns whether secure event input is enabled by any process, not just the current process. Secure event input may be disabled in the current process but enabled in some other process; in that case, this API will return true. Shouldn't it return "true" since the password textfield has enabled SecureEventInput?

"EnableSecureEventInput" gives me a "noErr" OSStatus and it effectively stops me from receiving the keyboard events even outside the password textfield, and "DisableSecureEventInput" gives a -50 error. Which is "paramErr" I believe this is because "IsSecureEventInputEnabled" is false?

After trying things out it seems that the password textfield in the LoginView is of type "NSSecureTextField" and that it is the password textview who enables the "SecureInputEvent" on itself? If so, only the textField itself can call DisableSecureInputEvent on itself? is there a way to access the textField in the LoginView to disable it? or am I completely off track here? lol

is there a way to access the textField in the LoginView to disable it? or am I completely off track here?

You are completely off track. A pre-login agent runs in a separate process from that which displays the login UI. You will not be able to manipulate its views [1].

Can you step back here and post a summary of your high-level goal? You started this thread with:

I'm writing a Agent Application that records the Screen and I'm trying to keep the recording going even when the user logs out.

A screen recording app does not, in general, need to capture keyboard input, so how does that requirement factor into your overall product?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Outside of the login context you could do hacky stuff with the accessibility APIs but a) that’s unlikely to work in this context, and b) it probably wouldn’t do want you want to do anyway.

not possible to disable secure input events

I don’t know what you mean by “disable secure input events”. I suspect that you’re trying to prevent the login window from enabling secure event input, with the ultimate goal of using an API like CGEvent tag to capture the keys that the user enters in the login window password field. If so, you are correct that there’s no way to do that.

If you explain more about your app’s big picture, I may be able to offer further advice.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Keeping NSApplication during LoginWindow
 
 
Q