Displaying a Window or Alert over the loginwindow

I am attempting to pop up a window or alert on the login screen. If after the user inputs their password incorrectly X amount of times, we want to display a message. All of my attempts to display the window/alert appear behind the loginwindow.

I've tried setting the window.level to Int32.max and some other high values near that. It makes it the top most window behind the loginwindow and on top of everything else. If I go higher than that (somewhere in the Int64 range), it makes it the bottom most window behind everything else on the desktop.

I've also tried running a script to tell loginwindow to display an alert. It pops up, but still not over the loginwindow screen, and keyboard input isn't allowed (just get beeps) until the user hits "enter" to close the non-visible alert. Not to mention that it also requires a privacy approval for my process to access loginwindow by the user beforehand.

I've also tried setting the lock message by writing to the defaults.
Code Block
sudo defaults write /Library/Preferences/com.apple.loginwindow LoginwindowText "My login window message"

But, haven't been able to get loginwindow to reload/re-read the defaults value once it is already being displayed. Is that even possible?

Any other suggestions?

Replies

Running at login is a complex issue. Before we start I want to point you at Technote 2228 Running At Login. While it’s quite out of date, it’s still useful because it defines the terminology I use to discuss such issues.

All of my attempts to display the window/alert appear behind the loginwindow.

From what context are you presenting this window?

Share and Enjoy

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

I read the technote you linked. I also was playing around and tried killing the loginwindow process after setting the LoginwindowText value. It does cause the login screen to relaunch and display with the new settings, however, if a user was logged in (screen was just locked), it forces them to logout. Not the most desirable thing since they could potentially lose unsaved work.

As for my attempts to present a window:
I have a launch daemon which is an exec.
Normally when I want to display UI to the end user, the launch daemon will run another .app that I have that will take in command line arguments and display the appropriate view controller to the user. In this case, the view controller just has a label and close button on it.
In order to choose which view controller to display, I use a custom NSWindowController and override windowDidLoad().
In windowDidLoad():
Code Block
...
let alertVC = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "DISPLAY_ALERT") as! NSViewController
if let window = self.window {
window.title = "ALERT"
window.contentViewController = alertVC
window.level = NSWindow.Level(rawValue: 2147483647)
window.canBecomeVisibleWithoutLogin = true
}

In the NSViewController, I override viewDidAppear() and also set the window.level and window.canBecomeVisibleWithoutLogin values there. I tried adding them to viewDidLoad() as well.
Since this app is launched by my launch daemon, it runs as the "root" user. So there is slight different behavior depending if the screen was locked vs no user was logged in. If the screen was locked, the UI from my app appears as the top most window once the user logs back in successfully back to his desktop. If no user was logged in, I don't see the UI anywhere, but I can see my app is running. Probably displayed on the root desktop? Not sure if that is a thing.

I have little actual knowledge of this area (and I may be misunderstanding what you're doing), but I feel that if what you're trying to do were possible, then it'd be too easy to phish credentials by overlaying "your own" fake login-window, so maybe it not working is by design?

I have a launch daemon which is an exec.

You need to stop there. Do not use GUI frameworks from a daemon, or from a process that you launch directly from a daemon. That has never been supported and will inevitably result in problems, including weird misbehaviours and compatibility with future releases. For more background as to why, read through the discussion of execution contents in Technote 2083 Daemons and Agents.

I suspect what you want here is a pre-login agent, as shown by the PreLoginAgents sample code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Okay. At some point I can switch our GUI App to run as a normal launch agent, since we only ever call it when a user is logged in. And use XPC or something from our daemon to tell it when to display things to the user.

Now, I did create a small app to run as a PreLoginAgent. And this works. I can trigger it to display a message window on the login screen after X number of invalid login attempts when no user is logged in yet. However, I still have the issue of displaying over the lock screen when a user is logged in but the screen is locked. Is there a way to launch the agent when a user has locked their screen with the same loginwindow context? I added to the launchd plist both "LoginWindow" and "Aqua" to LimitLoadToSessionType. That way it was running all the time, although, with the Aqua context, it couldn't display the window over the lock screen.

However, I still have the issue of displaying over the lock screen when a user is logged in but the screen is locked.

That is a surprisingly different kettle of fish, alas.

Is there a way to launch the agent when a user has locked their screen with the same loginwindow context?

No. This isn’t a pre-login context and thus pre-login agents won’t run.

If you look again at TN2228 you’ll see that there’s another potential option, namely an authorisation plug-in. Alas, this won’t work either, or at least not directly. The way the lock screen is set up, the only third-party code that can display UI is an SFAuthorizationPluginView. And the goal of that class is to replace the login UI, not supplement it.

Share and Enjoy

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