Last active
December 3, 2024 10:29
-
-
Save stephancasas/77592228a3f34a533301305d423f05e8 to your computer and use it in GitHub Desktop.
JXA Core AX Framework Bindings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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