Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Last active February 15, 2024 01:13
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stephancasas/77592228a3f34a533301305d423f05e8 to your computer and use it in GitHub Desktop.
Save stephancasas/77592228a3f34a533301305d423f05e8 to your computer and use it in GitHub Desktop.
JXA Core AX Framework Bindings
#!/usr/bin/env osascript -l JavaScript
function run() {
const VSCode = axApp('com.microsoft.VSCode');
const window = axGet(VSCode, 'AXWindows')[0];
return axWindowSetBounds(window, 200, 200, 1200, 1400);
}
/**
* -----------------------------------------------------------------------------
* Objective-C Bindings
* -----------------------------------------------------------------------------
*
* Import AX framework functions.
*/
// prettier-ignore
(() => {
ObjC.import('Cocoa');
ObjC.bindFunction('malloc', ['void*', ['int']]);
ObjC.bindFunction('memset', ['void*', ['void*', 'int', 'int']]);
ObjC.bindFunction('AXUIElementPerformAction', ['int', ['id', 'id']]);
ObjC.bindFunction('AXValueCreate', ['id', ['unsigned int', 'void*']]);
ObjC.bindFunction('AXValueGetValue', ['bool', ['id', 'int', 'void*']]);
ObjC.bindFunction('AXUIElementCreateApplication', ['id', ['unsigned int']]);
ObjC.bindFunction('AXUIElementSetAttributeValue', ['int', ['id', 'id', 'id']]);
ObjC.bindFunction('AXUIElementCopyAttributeValue',['int', ['id', 'id', 'id*']]);
})();
const kAXValueTypeCGPoint = 1;
const kAXValueTypeCGSize = 2;
const kAXValueTypeCGRect = 3;
const kAXValueTypeCFRange = 4;
const kAXValueTypeAXError = 5;
const kAXValueTypeIllegal = 0;
/**
* -----------------------------------------------------------------------------
* Helper Functions
* -----------------------------------------------------------------------------
*
* Accessibility/UI-scripting logic, and process ID resolution.
*/
/**
* Get an AXUIApplication using its bundle identifier.
* @param bundleId The application's bundle identifier.
* @returns {AXUIApplication}
*/
function axApp(bundleId) {
const pid = ObjC.unwrap(
$.NSArray.arrayWithArray(
$.CFBridgingRelease($.NSWorkspace.sharedWorkspace.runningApplications),
),
).find(
(runningApplication) =>
(ObjC.unwrap(runningApplication.bundleIdentifier) ?? '').toLowerCase() ==
bundleId.toLowerCase(),
).processIdentifier;
return $.AXUIElementCreateApplication(pid);
}
/**
* Get the value of an attribute of an AXUIElement.
* @param $el The element from which to get an attribute value.
* @param attribute The name of the attribute to retrieve.
* @returns {Any}
*/
function axGet($el, attribute) {
let $result = Ref();
$.AXUIElementCopyAttributeValue($el, attribute, $result);
return ObjC.deepUnwrap($result[0]);
}
/**
* Set the value of an attribute on an AXUIElement.
*
* NOTE: Use the memory-coerced datatypes for special AXValue requirements.
*
* @param $el The element on which to set an attribute value.
* @param attribute The name of the attribute to set.
* @param value The value to assign to the named attribute.
*/
function axSet($el, attribute, value) {
return $.AXUIElementSetAttributeValue($el, attribute, value);
}
/**
* Set the position of an AXUIElement window.
* @param $window The window whose position should set.
* @param x The "x" coordinate of the position to set.
* @param y The "y" coordinate of the position to set.
* @returns {Number}
*/
function axWindowSetPosition($window, x, y) {
return $.AXUIElementSetAttributeValue(
$window,
'AXPosition',
$.AXValueCreate(kAXValueTypeCGPoint, CGPoint(x, y)),
);
}
/**
* Set the size of an AXUIElement window.
* @param $window The window whose size should set.
* @param w The width of the size to set.
* @param h The height of the size to set.
* @returns {Number}
*/
function axWindowSetSize($window, w, h) {
return $.AXUIElementSetAttributeValue(
$window,
'AXSize',
$.AXValueCreate(kAXValueTypeCGSize, CGSize(w, h)),
);
}
/**
* Set the bounds of an AXUIElement window.
* @param $window The window whose bounds should set.
* @param x The "x" coordinate of the position to set.
* @param y The "y" coordinate of the position to set.
* @param w The width of the size to set.
* @param h The height of the size to set.
* @returns {Number}
*/
function axWindowSetBounds($window, x, y, w, h) {
return axWindowSetPosition($window, x, y) + axWindowSetSize($window, w, h);
}
/**
* -----------------------------------------------------------------------------
* Memory-coerced Datatypes
* -----------------------------------------------------------------------------
*
* Functions which enforce datatype assignment via direct memory allocation.
*/
function Float(num) {
const buffer = new ArrayBuffer(8);
const floatArray = new Float64Array(buffer);
floatArray[0] = num;
const byteArray = new Uint8Array(buffer);
return Array.from(byteArray);
}
function CGPoint(x, y) {
let $cgPoint = $.malloc(16);
$.memset($cgPoint, 0, 16);
const bytes = [...Float(x), ...Float(y)];
for (let i = 0; i < 16; i++) {
$cgPoint[i] = bytes[i];
}
return $cgPoint;
}
function CGSize(w, h) {
let $cgSize = $.malloc(16);
$.memset($cgSize, 0, 16);
const bytes = [...Float(w), ...Float(h)];
for (let i = 0; i < 16; i++) {
$cgSize[i] = bytes[i];
}
return $cgSize;
}
function CGRect(x, y, w, h) {
let $cgRect = $.malloc(32);
$.memset($cgRect, 0, 32);
const bytes = [...Float(x), ...Float(y), ...Float(w), ...Float(h)];
for (let i = 0; i < 32; i++) {
$cgRect[i] = bytes[i];
}
return $cgRect;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment