Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stephancasas/5d2ab36ca29f87e55ba87bc89ae62abf to your computer and use it in GitHub Desktop.
Save stephancasas/5d2ab36ca29f87e55ba87bc89ae62abf to your computer and use it in GitHub Desktop.
AXUIElement JXA Objective-C Bridge Example
#!/usr/bin/env osascript -l JavaScript
const App = Application.currentApplication();
App.includeStandardAdditions = true;
ObjC.import('Cocoa');
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]);
ObjC.bindFunction('AXUIElementCopyAttributeValue', [
'int',
['id', 'id', 'id *'],
]);
ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]);
/**
* Get the process id for the first process matching the given bundle id.
* @param bundleId The bundle id to query.
* @returns {pid_t}
*/
const getPidForBundleId = (bundleId) =>
ObjC.unwrap(
$.NSArray.arrayWithArray(
$.CFBridgingRelease($.NSWorkspace.sharedWorkspace.runningApplications),
),
).find(
(runningApplication) =>
(ObjC.unwrap(runningApplication.bundleIdentifier) ?? '').toLowerCase() ==
bundleId.toLowerCase(),
).processIdentifier;
function run(appName = '', bundleId = '') {
// test case
if (!appName.length) {
appName = 'textmate';
bundleId = 'com.macromates.TextMate';
}
// Get the Dock application.
let DockApp = $.AXUIElementCreateApplication(
getPidForBundleId('com.apple.dock'),
);
// Get the Dock's children elements.
let dockChildren = Ref();
$.AXUIElementCopyAttributeValue(DockApp, 'AXChildren', dockChildren);
// Get the children of the first child of the dock, which is the list of apps.
let dockAppListEntries = Ref();
$.AXUIElementCopyAttributeValue(
ObjC.deepUnwrap(dockChildren[0])[0],
'AXChildren',
dockAppListEntries,
);
// Find the app list entry whose title matches the given title.
const entry = ObjC.deepUnwrap(dockAppListEntries[0]).find((appEntry) => {
let entryName = Ref();
$.AXUIElementCopyAttributeValue(appEntry, 'AXTitle', entryName);
return !!ObjC.unwrap(entryName[0]).match(new RegExp(appName, 'i'));
});
// Quit if nothing was found.
if (!entry) {
return;
}
// Show the app menu for the matched entry.
$.AXUIElementPerformAction(entry, 'AXShowMenu');
delay(0.01); // Pause slightly to allow menu to draw in UI.
// Get the matched entry's children.
let entryChildren = Ref();
$.AXUIElementCopyAttributeValue(entry, 'AXChildren', entryChildren);
// Get the children of the app menu for the matched entry.
let entryMenuItems = Ref();
$.AXUIElementCopyAttributeValue(
ObjC.deepUnwrap(entryChildren[0])[0],
'AXChildren',
entryMenuItems,
);
// Find the menu item whose title matches "new file."
let newFile = ObjC.deepUnwrap(entryMenuItems[0]).find((menuItem) => {
let entryName = Ref();
$.AXUIElementCopyAttributeValue(menuItem, 'AXTitle', entryName);
return `${ObjC.unwrap(entryName[0])}`.toLowerCase() == 'new file';
});
// Quit if nothing was found.
if (!newFile) {
return;
}
// Click the found menu item matching "new file."
$.AXUIElementPerformAction(newFile, 'AXPress');
delay(0.1); // Pause slightly to allow the press action to run.
// Bring the application to the foreground, using its bundle id.
$.NSRunningApplication.runningApplicationsWithBundleIdentifier(
bundleId,
).firstObject.activateWithOptions($.NSApplicationActivateIgnoringOtherApps);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment