26

Is there any way to check if screen is locked in shell or applescript? Not just check if screensaver is running, but screen is locked by energy saver settings or by pressing ⌃⇧⏏ (shift+control+eject).

Thank you in advance.

LevB
  • 2,326
  • 3
  • 16
  • 14

3 Answers3

33

First, there's a bit of confusion in your question. Both Shift+Control+Eject and Energy Saver put the screens to sleep, which isn't the same thing as locking them. Depending on your other settings, this may also entail locking the screen, but that's a separate issue. IIRC, on Lion, by default, neither one will ever lock the screen—but if you leave the screen asleep for longer than the time set in Security & Privacy, that will lock it.

Anyway, the API CGSessionCopyCurrentDictionary allows you to get information about both screen sleep and screen lock, for your GUI session. If you don't have a GUI session (e.g., because you're running in an ssh shell), or your session doesn't own the console (e.g., because someone has fast-user-switched you out), you won't be able to get this information, but you will at least be able to detect those cases.

This is the only mechanism I know of that works for all OS's from 10.5 (actually 10.3) to 10.8 (but that doesn't mean it's the only one there actually is…).

There's no direct way to call this from bash or AppleScript. However, you can use your favorite bridge (PyObjC, MacRuby, ASOC, etc.) to call it indirectly. Here's an example using Python:

#!/usr/bin/python
import Quartz
d = Quartz.CGSessionCopyCurrentDictionary()
print d

Here's how to interpret the response:

  • If you get nothing back, then you don't have a UI session.
  • If the dictionary has kCGSSessionOnConsoleKey = 0, or not present, either your GUI session doesn't own the console, or the console's screens are asleep.
  • If the dictionary has CGSSessionScreenIsLocked = 1, the screens are locked.

The one problem case is where kCGSSessionOnConsoleKey is 0 (or missing) and CGSSessionScreenIsLocked is 1. In that case, either you've put the screens to sleep and locked them, or someone else has taken the console and locked the screens (with or without putting them to sleep). And I'm not sure if there's a way to distinguish between these cases. But if you're looking for "don't try to display a dialog because the user will have to unlock the screen first", both of those cases mean "don't display a dialog".

So, this should give you what you want:

#!/usr/bin/python
import sys
import Quartz
d=Quartz.CGSessionCopyCurrentDictionary()
sys.exit(d and 
         d.get("CGSSessionScreenIsLocked", 0) == 0 and 
         d.get("kCGSSessionOnConsoleKey", 0) == 1)

Or, turning it into a one-liner you can put directly in a shell script:

python -c 'import sys,Quartz; d=Quartz.CGSessionCopyCurrentDictionary(); sys.exit(d and d.get("CGSSessionScreenIsLocked", 0) == 0 and d.get("kCGSSessionOnConsoleKey", 0) == 1)'

Now, what if you've ssh'd into a Mac, and you're also currently logged into that Mac's GUI console (as the same user)? In that case, your ssh login session can communicate with the console login session in exactly the same way that a local Terminal login session would. So, CGSessionCopyCurrentDictionary is going to get the same values.

The bootstrap server that mediates that connection will apply some restrictions (e.g., security authorize -u foo should work from the Terminal but not over ssh), but those aren't fully documented, and change from version to version, so that's probably not something you want to rely on. Instead, you want to actually read your login session information

If you want to go further with this, start with reading Multiple User Environments Programming Topics. But some of the information isn't really documented anywhere (e.g., how the Mach-level sessions referenced by SessionGetInfo and the BSD-level sessions referenced by utmpx are tied together). Many of the relevant tools and libraries are open source, which may help. Even if reading up on all of that doesn't tell you how to do what you want, it will tell you exactly what you want, and the right terms to use to search and ask questions, which may be good enough.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • I mean "locking" because any of this states locks my computer due to security settings (immediately require password for sleep and screen saver). And thank you for the detailed answer. Actually I solved my task by parsing _ioreg -n IODisplayWrangler_ for current power state. – LevB Jul 17 '12 at 09:05
  • Thanks for this; I was trying to figure out if the user was on a GUI session or not, and the python Quartz module looks very useful. (sadly, though, when I'm SSH'd into a Mac, your script snippet returns the exact same output as when I'm on the GUI console.) I know I can check SSH-specific environment variables, but I wanted to get more accurate... – mpontillo Jul 25 '12 at 20:01
  • Since my comment is too long to fit, I'll add it to the answer above. – abarnert Jul 27 '12 at 17:44
  • 2
    This is absolutely fantastic thanks for sharing. I wrote a js-ctypes version here: https://gist.github.com/Noitidart/ff19ae88500a649c1ef9 can run from firefox, open scratchpad set environment to browser and run it. View output in browser console. An issue I found though is: If lock is enabled, no matter what the delay is for requiring the password, as soon as screen saver shows, the value of CGSSessionScreenIsLocked is 1. I have delay of 60seconds before requiring password, is there anyway to get an accurate value? Like if screensaver is running and not 60sec yet, it should report 0/false? – Noitidart Dec 17 '14 at 10:54
  • @Noitidart: It seems like (at least in 10.10; I no longer have immediate access to old machines to test on), that's what `CGSSessionScreenIsLocked` means, and there aren't any constants that look likely to mean what you want. (It's hard to look up, because it's not a documented API, and every search for it turns up either my code, your JS port, someone else's Perl port, or someone who's copied one of the above without explanation…) – abarnert Dec 17 '14 at 22:05
  • 1
    @Noitidart: One other possibility to look at: I believe it's the `loginwindow` service that controls screen locking, and at least old versions of `loginwindow` (the app is buried in `/System/Library/CoreServices`) were AppleScriptable, even though they didn't publish their dictionary. So, if you `sdef` it, you might be able to find something useful. However, I wouldn't be surprised if they removed the scriptability from `loginwindow` when they ported all the core stuff to Cocoa between 10.5 and 10.7, adding new higher-level (and documented) APIs for some things and removing others… – abarnert Dec 17 '14 at 22:05
  • 1
    @Noitidart: For example, all the screensaver stuff is now in the `System Events` dictionary, but, while that includes enough information to determine that the screensaver is on and "require password to wake" is enabled, that doesn't tell you whether "require password to wake" has activated or whether you're still in the grace period. If Apple didn't want you to be able to distinguish something, unless you get lucky and they gave you something that works by accident, you can't distinguish it… – abarnert Dec 17 '14 at 22:12
  • 1
    Thanks abarnert very much for your input! I'll try to investigate! I know there is a cocoa NSDsitributedNotificationCenter observer we can use, but that's an observer, we can't query it, so might (even though unlikely) miss if the script starts during screensaver or lock. http://stackoverflow.com/questions/27514778/test-if-screensaver-is-running-or-user-locked Would you know of any cocoa methods other then this observer? :) Thanks again! – Noitidart Dec 17 '14 at 23:09
  • 1
    @Noitidart: I don't know of anything. You could of course install a launch agent helper that runs when triggered with that notification and puts the info somewhere useful for your script. Or, if you _really_ want to get hacky, there's a good chance that `loginwindow` or `lsuseractivityd` or whoever else broadcasts this message has the relevant code written in ObjC, which means by injecting your own code (e.g., via SIMBL) you should be able to find the current state and expose it in some way. If you know about FScript Anywhere and exploring the ObjC object model and so on, that could be fun… – abarnert Dec 18 '14 at 00:01
  • Thanks very much for that! I'm a total noob Im just a js guy thats converting stuff with difficulty from C. I'll see if i can make some friends and ask them for some ObjC model exploring i dont know how to do that haha. – Noitidart Dec 18 '14 at 07:22
  • 1
    @Noitidart: Well, that last idea is probably out of scope here, but maybe as a JS guy you'll understand this: SIMBL lets you inject code into any native app, like Greasemonkey does for any JS app. You don't have access to the source code, but you do have access to the globals, which includes all the class objects, and you can call methods by name just like in JS (unlike C). Learning how to use `otool` and `lldb` to search for what you're looking for is actually the hard part; writing a SIMBL extension once you know what you want to do is comparatively easy. – abarnert Dec 18 '14 at 07:28
  • Thanks so much man. I really appreciate it! It has to be through C though because this code is going into firefox-addon :( All those methods I'll recommend to the person im trying to help: http://stackoverflow.com/questions/27377686/firefox-extension-addon-detect-workspace-locked they are happy with the 1 when screensaver is on and not locked, but im just seeking perfection haha and i love open source stuff so i love to help out others, as i see lots of people asking it :) – Noitidart Dec 18 '14 at 07:30
  • You can write Firefox addons in ObjC. Or, if you want, you can access the ObjC runtime through its C API (which is tedious, but it works). However, you can't inject code into another program from within a Firefox addon, so… that's not going to help. – abarnert Dec 18 '14 at 07:41
  • You can distinguish between console access problems and sleeping display with: CGDisplayIsAsleep – totaam Mar 31 '20 at 12:36
16

The answer from @LevB is great but with the advent of Python "deprecation" in macOS (which has not yet seen its removal as of Big Sur) I wondered if there was another way to get CGSSessionScreenIsLocked, Applecare Enterprise Support pointed me to ioreg where these CoreGraphics Session values also live

Below are a couple shell function one-liners, in the shell style they return zero (0) in the affirmative (success) and non-zero (1) in the negative so they can be used as expected in if statements.

function screenIsLocked { [ "$(/usr/libexec/PlistBuddy -c "print :IOConsoleUsers:0:CGSSessionScreenIsLocked" /dev/stdin 2>/dev/null <<< "$(ioreg -n Root -d1 -a)")" = "true" ] && return 0 || return 1; }
function screenIsUnlocked { [ "$(/usr/libexec/PlistBuddy -c "print :IOConsoleUsers:0:CGSSessionScreenIsLocked" /dev/stdin 2>/dev/null <<< "$(ioreg -n Root -d1 -a)")" != "true" ] && return 0 || return 1; }

Note these keys only exist when the screen is locked: kCGSSessionSecureInputPID, CGSSessionScreenLockedTime, CGSSessionScreenIsLocked. When the screen is unlocked CGSSessionScreenIsLocked is not present, thus the != "true" comparison, since it will never be "false"

Example usage (since the simplicity of if evaluating exit codes is sometimes not realized):

#!/bin/sh

function screenIsLocked { [ "$(/usr/libexec/PlistBuddy -c "print :IOConsoleUsers:0:CGSSessionScreenIsLocked" /dev/stdin 2>/dev/null <<< "$(ioreg -n Root -d1 -a)")" = "true" ] && return 0 || return 1; }
function screenIsUnlocked { [ "$(/usr/libexec/PlistBuddy -c "print :IOConsoleUsers:0:CGSSessionScreenIsLocked" /dev/stdin 2>/dev/null <<< "$(ioreg -n Root -d1 -a)")" != "true" ] && return 0 || return 1; }

if screenIsLocked; then
    echo "Screen locked"
fi

if screenIsUnlocked; then
    echo "Screen unlocked"
fi

if ! screenIsLocked; then
    echo "Screen unlocked (inverse logic)"
fi

if ! screenIsUnlocked; then
    echo "Screen locked (inverse logic)"
fi
Joel Bruner
  • 566
  • 4
  • 6
0

With jdk9 you can use

java.awt.Desktop



Desktop tempDesktop = Desktop.getDesktop();
        tempDesktop.addAppEventListener(new UserSessionListener() {

            @Override
            public void userSessionDeactivated(UserSessionEvent aE) {
                LOG.info("Desktop:userSessionDeactivated Reason=" + aE.getReason() + " Source=" + aE.getSource());
                // Windows Key L: 
                // Tue Aug 31 11:22:49 CEST 2021:info:MainController:userSessionDeactivated Reason=LOCK

                // Close Lid:
                // Tue Aug 31 11:24:38 CEST 2021:info:MainController:userSessionDeactivated Reason=LOCK
                // Tue Aug 31 11:24:39 CEST 2021:info:MainController:systemAboutToSleep Source=java.awt.Desktop@741f67cd

                ptcUserStatus = PtcUserStatus.AWAY;

            }

            @Override
            public void userSessionActivated(UserSessionEvent aE) {
                LOG.info("Desktop:userSessionActivated Reason=" + aE.getReason() + " Source=" + aE.getSource());
                // Logon after Windows Key L
                // Tue Aug 31 11:22:53 CEST 2021:info:MainController:userSessionActivated Reason=LOCK

                // Open Lid:
                // Tue Aug 31 11:24:56 CEST 2021:info:MainController:systemAwoke Source=java.awt.Desktop@741f67cd
                // Tue Aug 31 11:25:06 CEST 2021:info:MainController:userSessionActivated Reason=LOCK
                ptcUserStatus = PtcUserStatus.BACK;
            }
        });
Muhammad Dyas Yaskur
  • 6,914
  • 10
  • 48
  • 73
Quaddy
  • 11
  • 1