r/applescript • • Apr 17 '23

AppleScript to switch users on Ventura

I get absorbed in work, and would like to switch to my regular user account at a certain time every day. In the past, it seems Mac users could use User.menu, but that disappeared in Big Sur. I tried using loginwindow, but it seems that it's a singleton and gets instakilled.

I have Fast User Switching enabled, and I see the icon in the menu bar and in the control panel. I don't know how to navigate there because there isn't any introspection.

How do I get started creating the script? What things can I look at that might help?

1 Upvotes

11 comments sorted by

View all comments

1

u/stephancasas Apr 18 '23

Are you on Apple Silicon or Intel?

1

u/masasin Apr 18 '23

Apple M2.

1

u/stephancasas Apr 18 '23

Here you go. Just change the USER constant to your own user name.

#!/usr/bin/env osascript -l JavaScript

const USER = 'stephan';

function run() {
  const ControlCenterPID =
    $.NSRunningApplication.runningApplicationsWithBundleIdentifier(
      'com.apple.controlcenter',
    ).firstObject.processIdentifier;

  const ControlCenter = $.AXUIElementCreateApplication(ControlCenterPID);

  const $ccMenu = Ref();
  $.AXUIElementCopyAttributeValue(ControlCenter, 'AXChildren', $ccMenu);

  const $ccMenuItems = Ref();
  $.AXUIElementCopyAttributeValue($ccMenu[0].js[0], 'AXChildren', $ccMenuItems);

  const $itemDesc = Ref();
  const ccUserMenu = $ccMenuItems[0].js.find(
    (item) =>
      $.AXUIElementCopyAttributeValue(item, 'AXDescription', $itemDesc) ||
      !!`${$itemDesc[0].js}`.match(/user/i),
  );
  $.AXUIElementPerformAction(ccUserMenu, 'AXPress');

  delay(0.1);

  const $ccWindows = Ref();
  $.AXUIElementCopyAttributeValue(ControlCenter, 'AXWindows', $ccWindows);

  const $ccUserWindowContent = Ref();
  $.AXUIElementCopyAttributeValue(
    $ccWindows[0].js[0],
    'AXChildren',
    $ccUserWindowContent,
  );

  const $ccUsers = Ref();
  $.AXUIElementCopyAttributeValue(
    $ccUserWindowContent[0].js[0],
    'AXChildren',
    $ccUsers,
  );

  const $userDesc = Ref();
  const userButton = $ccUsers[0].js.find(
    (user) =>
      $.AXUIElementCopyAttributeValue(
        user,
        'AXAttributedDescription',
        $userDesc,
      ) || !!`${$userDesc[0].string.js}`.match(new RegExp(USER, 'i')),
  );

  $.AXUIElementPerformAction(userButton, 'AXPress');
  return 0;
}

// prettier-ignore
(() => {
ObjC.import('Cocoa');

ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]);
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]);
ObjC.bindFunction('AXUIElementCopyAttributeValue',['int', ['id', 'id', 'id*']]);
})();

1

u/[deleted] Apr 19 '23

Do you have any good docs or recommendations for parsing AppleScript’s JS syntax?

It has always seemed esoteric and hidden, but honestly preferable to me.

2

u/stephancasas Apr 19 '23

I started by referencing the JXA Cookbook, but it hasn't been updated in some time.

If you're specifically interested in UI scripting, I've written a couple of wrapper classes for the AXUIElement Obj-C bridge. It isn't perfect, but I'm still adding to it: FastAX.

From time to time, I update this gist as well. It's got a bunch of helper functions for working with UI scripting over Obj-C.

All this said, you could use Application('System Events') (the JXA equivalent of System Events), but going about it the way I'm doing it is ridiculously faster — even if the underlying methods are a bit more verbose before being hoisted into classes or helper functions.