Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Last active November 12, 2024 11:19
Show Gist options
  • Save stephancasas/a36c81fbc4189f46bc803f388a1985be to your computer and use it in GitHub Desktop.
Save stephancasas/a36c81fbc4189f46bc803f388a1985be to your computer and use it in GitHub Desktop.
Toggle sidecar or screen mirroring from Control Center in macOS Ventura
#!/usr/bin/env osascript -l JavaScript
/**
* -----------------------------------------------------------------------------
* Activate Sidecar/Screen Mirroring from Control Center
* -----------------------------------------------------------------------------
*
* Created on February 17, 2023 by Stephan Casas
* Updated on May 18, 2023 by Stephan Casas
*
* Options:
* - TARGET_DEVICE_NAME
* - The name of the sidecar/screen mirroring device to toggle.
* - This should be exactly as it's written in screen mirroring menu.
* - Include any whitespace characters as given in the menu entry.
*
*
* Notes:
* This script was tested on macOS 13 Ventura and may break with future OS
* updates.
*/
const TARGET_DEVICE_NAME = 'REPLACE_WITH_YOUR_DEVICE_NAME';
const $attr = Ref();
const $windows = Ref();
const $children = Ref();
function run(_) {
// Get the current Control Center PID.
const pid = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(
'com.apple.controlcenter',
).firstObject.processIdentifier;
// Get the Control Center application.
const app = $.AXUIElementCreateApplication(pid);
// Get the Control Center menubar extra children.
$.AXUIElementCopyAttributeValue(app, 'AXChildren', $children);
$.AXUIElementCopyAttributeValue($children[0].js[0], 'AXChildren', $children);
// Locate the Control Center menubar extra (also has Clock, Users, etc.).
const ccExtra = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXIdentifier', $attr);
return $attr[0].js == 'com.apple.menuextra.controlcenter';
});
// Open Control Center window and await draw.
$.AXUIElementPerformAction(ccExtra, 'AXPress');
if (
!(() => {
const timeout = new Date().getTime() + 2000;
while (true) {
$.AXUIElementCopyAttributeValue(app, 'AXWindows', $windows);
if (
typeof $windows[0] == 'function' &&
($windows[0].js.length ?? 0) > 0
) {
return true;
}
if (new Date().getTime() > timeout) {
return false;
}
delay(0.1);
}
})()
) {
return;
}
// Get Control Center window children.
$.AXUIElementCopyAttributeValue($windows[0].js[0], 'AXChildren', $children);
// Locate the Control Center modules group.
const modulesGroup = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXRole', $attr);
return $attr[0].js == 'AXGroup';
});
// Get the individual modules within the modules group.
$.AXUIElementCopyAttributeValue(modulesGroup, 'AXChildren', $children);
// Locate the screen-mirroring module.
const screenMirroring = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXIdentifier', $attr);
return $attr[0].js == 'controlcenter-screen-mirroring';
});
// Activate the screen mirroring module and await draw.
$.AXUIElementPerformAction(
screenMirroring,
// Wtf is this action name, Apple??
'Name:show details\nTarget:0x0\nSelector:(null)',
);
if (
!(() => {
const timeout = new Date().getTime() + 2000;
while (true) {
$.AXUIElementCopyAttributeValue(modulesGroup, 'AXChildren', $children);
if (
typeof $children[0] == 'function' &&
($children[0].js.length ?? 0) > 0
) {
return true;
}
if (new Date().getTime() > timeout) {
return false;
}
delay(0.1);
}
})()
) {
return;
}
// Get the scroll area containing the device mirroring options.
const mirroringOptions = $children[0].js.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXRole', $attr);
return $attr[0].js == 'AXScrollArea';
});
// Get all mirroring options.
$.AXUIElementCopyAttributeValue(mirroringOptions, 'AXChildren', $children);
// Locate the toggle element for the target mirroring device.
const toggle = $children[0].js
.filter((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXRole', $attr);
return $attr[0].js == `AXCheckBox`;
})
.find((child) => {
$.AXUIElementCopyAttributeValue(child, 'AXIdentifier', $attr);
return $attr[0].js == `screen-mirroring-device-${TARGET_DEVICE_NAME}`;
});
if (!toggle) {
console.log(
'Error: Could not get toggle for target screen-mirroring device.',
);
return 1;
}
// Press the toggle for the target device.
$.AXUIElementPerformAction(toggle, 'AXPress');
// Send ⎋ to dismiss Control Center.
$.CGEventPost($.kCGHIDEventTap, $.CGEventCreateKeyboardEvent(null, 53, true));
$.CGEventPost($.kCGHIDEventTap, $.CGEventCreateKeyboardEvent(null, 53, true));
return 0;
}
// prettier-ignore
(() => {
ObjC.import('Cocoa'); // yes, it's necessary -- stop telling me it isn't
ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]);
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]);
ObjC.bindFunction('AXUIElementCopyAttributeValue',['int', ['id', 'id', 'id*']]);
})();
@mig8447
Copy link

mig8447 commented Feb 4, 2024

Couldn't get it to work on Ventura 13.6.4 either, although I'm thinking this is because my OS is in another language (Spanish)

@emrecengdev
Copy link

The action “Run JavaScript” encountered an error: “Error: TypeError: undefined is not an object (evaluating '$children[0].js[0]')”

@stephancasas
Copy link
Author

@emrecengdev I'll need a little more context to help you troubleshoot. Can you tell me more about your setup? Language, Control Center config, etc.?

@Cyan-ZSD
Copy link

Works in Sonoma 14.5. Thanks a lot! This is an amazing work!!!!!!!!

@GrumTech
Copy link

GrumTech commented Aug 1, 2024

Brilliant work, still working for me also!

@tonydehnke
Copy link

Seems to be broken in MacOS Sequiia 15.1 Beta - has anyone else tested it there?

@RainerUderia
Copy link

Doesn't work for me either - MacOS Sequiia 15.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment