开发者

Lock screen by API in macOS

开发者 https://www.devze.com 2022-12-15 05:11 出处:网络
Is there an API, that can lock the screen as the menu bar entry you can add from Keychain preferen开发者_如何学Cces?

Is there an API, that can lock the screen as the menu bar entry you can add from Keychain preferen开发者_如何学Cces?

This Keychain function is (was) locking the screen but not bringing the system to sleep.


It's not officially documented and uses private API, but the following works on MacOS 10.10 (and maybe also on earlier systems):

// lockscreen.c
extern int SACLockScreenImmediate ( void );

int main ( ) {
    return SACLockScreenImmediate();
}

Build with:

clang -F /System/Library/PrivateFrameworks -framework login -o lockscreen lockscreen.c 

Now calling ./lockscreen will lock the screen immediately, regardless what the user has configured in their security preferences (whether to lock on screensaver/system sleep) and without logging the user out. This is the function the system uses internally for locking the screen.

I strongly discourage using it, it may break your app and I'm not even sure I am calling it correctly (maybe it needs arguments, maybe it has a return value), so it may even break your whole system (temporarily - reboot will fix everything), who knows. I just wanted to post that somewhere for documentation.

If someone with better hacker skills than me can analyze this call some more, this would be nice and useful.


To lock the screen, call:

/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend


If you actually want to do what keychain does (ie just lock the screen, don't go to login window), it's quite easy:

io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
if (r) {
    IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), sleep ? kCFBooleanTrue);        
    IOObjectRelease(r);
}

However, this only works if the user has 'Require Password after sleep or screen saver begins' set to 'immediately'. But, you can just set it to immediately for them and then set it back to what it was when you are done. Turns out getting that to take effect can be pretty tricky (see this answer for more info) but it can be done. Put it all together and you have something like:

- (void)lockScreen;
{
    int screenSaverDelayUserSetting = 0;

    screenSaverDelayUserSetting = [self readScreensaveDelay];

    if (screenSaverDelayUserSetting != 0) {
        // if the delay isn't already 0, temporarily set it to 0 so the screen locks immediately.
        [self setScreensaverDelay:0];
        [self touchSecurityPreferences];
    }

    io_registry_entry_t r = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/IOResources/IODisplayWrangler");
    if (r) {
        IORegistryEntrySetCFProperty(r, CFSTR("IORequestIdle"), sleep ? kCFBooleanTrue : kCFBooleanFalse);        
        IOObjectRelease(r);
    }

    if (screenSaverDelayUserSetting != 0) {
        [self setScreensaverDelay:screenSaverDelayUserSetting];
        [self launchAndQuitSecurityPreferences];
    }
}

- (void)touchSecurityPreferences;
{
    // necessary for screen saver setting changes to take effect on file-vault-enabled systems
    // NOTE: this *only* works when going from non-zero settings of askForPasswordDelay to zero.

    NSAppleScript *kickSecurityPreferencesScript = [[[NSAppleScript alloc] initWithSource: @"tell application \"System Events\" to tell security preferences to set require password to wake to true"] autorelease];
    [kickSecurityPreferencesScript executeAndReturnError:nil];
}

- (void)launchAndQuitSecurityPreferences;
{
    // necessary for screen saver setting changes to take effect on file-vault-enabled systems when going from a askForPasswordDelay setting of zero to a non-zero setting
    NSAppleScript *kickSecurityPreferencesScript = [[[NSAppleScript alloc] initWithSource:
                                                     @"tell application \"System Preferences\"\n"
                                                     @"     tell anchor \"General\" of pane \"com.apple.preference.security\" to reveal\n"
                                                     @"     activate\n"
                                                     @"end tell\n"
                                                     @"delay 0\n"
                                                     @"tell application \"System Preferences\" to quit"] autorelease];
    [kickSecurityPreferencesScript executeAndReturnError:nil];
}

- (int)readScreensaveDelay;
{
    NSArray *arguments = @[@"read",@"com.apple.screensaver",@"askForPasswordDelay"];

    NSTask *readDelayTask = [[[NSTask alloc] init] autorelease];
    [readDelayTask setArguments:arguments];
    [readDelayTask setLaunchPath: @"/usr/bin/defaults"];

    NSPipe *pipe = [NSPipe pipe];
    [readDelayTask setStandardOutput:pipe];
    NSFileHandle *file = [pipe fileHandleForReading];
    [readDelayTask launch];
    NSData *resultData = [file readDataToEndOfFile];
    NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];
    return resultString.intValue;
}

- (void)setScreensaverDelay:(int)delay;
{
    NSArray *arguments = @[@"write",@"com.apple.screensaver",@"askForPasswordDelay", [NSString stringWithFormat:@"%i", delay]];
    NSTask *resetDelayTask = [[[NSTask alloc] init] autorelease];
    [resetDelayTask setArguments:arguments];
    [resetDelayTask setLaunchPath: @"/usr/bin/defaults"];
    [resetDelayTask launch];
}


Just did Mecki's answer using swift:

    let libHandle = dlopen("/System/Library/PrivateFrameworks/login.framework/Versions/Current/login", RTLD_LAZY)
    let sym = dlsym(libHandle, "SACLockScreenImmediate")
    typealias myFunction = @convention(c) Void -> Void
    let SACLockScreenImmediate = unsafeBitCast(sym, myFunction.self)
    SACLockScreenImmediate()

Was here: https://github.com/ftiff/MenuLock/blob/master/MenuLock/AppDelegate.swift#L126


Preston is right, I use the following method and it works like a charm:

- (void)lockScreen {
    NSTask *task;
    NSArray *arguments = [NSArray arrayWithObject:@"-suspend"];

    task = [[NSTask alloc] init];
    [task setArguments: arguments];
    [task setLaunchPath: @"/System/Library/CoreServices/Menu Extras/User.menu/Contents/Resources/CGSession"];
    [task launch];
    [task release];
    NSLog(@"screen is Locked");
} 


I don't see anything documented as such, but the menu uses the ScreenSaver framework, which defines this:

@interface ScreenSaverDefaults : NSUserDefaults 
{
@private
    NSMutableDictionary     *_defaults;
    NSMutableDictionary     *_registeredDefaults;
    NSString                *_userName;
    NSString                *_domainName;
    BOOL                    _dirty;
    BOOL                    _screenLockPrefChanged;
}

+ (id) defaultsForModuleWithName:(NSString *)inModuleName;

@end


The following code does exactly what the Keychain menu item does since it just calls that. I used to use this (on 10.11 and 10.12), but this now fails on 10.13 (public beta) because there is no Keychain.menu nor menu item at all anymore. Mecki's answer is a working substitute but doesn't fade out the screen so it's really lower level.

void lock() {
    NSBundle *bundle = [NSBundle bundleWithPath:@"/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain.menu"];
    Class principalClass = [bundle principalClass];
    id instance = [[principalClass alloc] init];
    [instance performSelector:@selector(_lockScreenMenuHit:) withObject:nil];
}


To lock computer and show password promt for recent user you can use this code:

- (void)lockScreen
{
    MDSendAppleEventToSystemProcess(kAESleep);
}

OSStatus MDSendAppleEventToSystemProcess(AEEventID eventToSendID)
{
    AEAddressDesc                    targetDesc;
    static const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
    AppleEvent                       eventReply          = {typeNull, NULL};
    AppleEvent                       eventToSend         = {typeNull, NULL};

    OSStatus status = AECreateDesc(typeProcessSerialNumber,
            &kPSNOfSystemProcess, sizeof(kPSNOfSystemProcess), &targetDesc);

    if ( status != noErr ) return status;

    status = AECreateAppleEvent(kCoreEventClass, eventToSendID,
            &targetDesc, kAutoGenerateReturnID, kAnyTransactionID, &eventToSend);

    AEDisposeDesc(&targetDesc);

    if ( status != noErr ) return status;

    status = AESendMessage(&eventToSend, &eventReply,
            kAENormalPriority, kAEDefaultTimeout);

    AEDisposeDesc(&eventToSend);
    if ( status != noErr ) return status;
    AEDisposeDesc(&eventReply);
    return status;
}


I was successfully able to take Mecki's answer and lock the screen on MacOS in Python with the following

import ctypes, ctypes.util

login = ctypes.CDLL( '/System/Library/PrivateFrameworks/login.framework/login' )
login.SACLockScreenImmediate();

I discovered this by scarce information on the Internet and trial-and-error. As far as I know, Apple doesn't document the SACLockScreenImmediate() function at all.

If anyone can find the official reference documentation for the "Login Framework" library, please drop it in the comments :)

Source

The is used in the BusKill app, which locks the screen when a magnetic breakaway connection in a USB Dead Man Switch is severed:

  • https://github.com/BusKill/buskill-app/tree/master/src/packages/buskill
0

精彩评论

暂无评论...
验证码 换一张
取 消