29

I'm trying to find documented (or, undocumented, if that's my only option) APIs on OS X to query a list of windows from the window server and then cause the windows to move and resize. Can anyone point me in the right direction? I guess I'd be starting with something like FindWindowEx and MoveWindow under Win32.

Note that I want to do this from an external process - I'm not asking how to control just my own app's window size and position.

Le Dude
  • 547
  • 5
  • 9

2 Answers2

50

Use the Accessibility API. Using this API you can connect to a process, obtain a list of windows (actually an array), get the positions and sizes of each window and also change window properties if you like.

However, an application can only be using this API if the user has enabled access for assistive devices in his preferences (System Prefs -> Universal Access), in which case all applications may use this API, or if your application is a trusted assitive application (when it is trusted, it may use the API, even if this option is not checked). The Accessibility API itself offers the necessary functions to make your application trusted - basically you must become root (using security services to request root permissions of the user) and then mark your process as trusted. Once your application has been marked trusted, it must be restarted as the trusted state is only checked on start-up and can't change while the app is running. The trust state is permanent, unless the user moves the application somewhere else or the hash of the application binary changes (e.g. after an update). If the user has assistive devices enabled in his prefs, all applications are treated as if they were trusted. Usually your app would check if this option is enabled, if it is, go on and do your stuff. If not, it would check if it is already trusted, if it is, again just do your stuff. If not try to make itself trusted and then restart the application unless the user declined root authorization. The API offers all necessary functions to check all this.

There exist private functions to do the same using the Mac OS window manager, but the only advantage that would buy you is that you don't need to be a trusted Accessibility application (which is a one time operation on first launch in most cases). The disadvantages are that this API may change any time (it has already changed in the past), it's all undocumented and functions are only known through reverse engineering. The Accessibility however is public, it is documented and it hasn't change much since the first OS X version that introduced it (some new functions were added in 10.4 and again in 10.5, but not much else has changed).

Here's a code example. It will wait 5 seconds, so you can switch to a different window before it does anything else (otherwise it will always work with the terminal window, rather boring for testing). Then it will get the front most process, the front most window of this process, print it's position and size and finally move it by 25 pixels to the right. You compile it on command line like that (assuming it is named test.c)

gcc -framework Carbon -o test test.c

Please note that I do not perform any error checking in the code for simplicity (there are various places that could cause the program to crash if something goes wrong and certain things may/can go wrong). Here's the code:

/* Carbon includes everything necessary for Accessibilty API */
#include <Carbon/Carbon.h>

static bool amIAuthorized ()
{
    if (AXAPIEnabled() != 0) {
        /* Yehaa, all apps are authorized */
        return true;
    }
    /* Bummer, it's not activated, maybe we are trusted */
    if (AXIsProcessTrusted() != 0) {
        /* Good news, we are already trusted */
        return true;
    }
    /* Crap, we are not trusted...
     * correct behavior would now be to become a root process using
     * authorization services and then call AXMakeProcessTrusted() to make
     * ourselves trusted, then restart... I'll skip this here for
     * simplicity.
     */
    return false;
}


static AXUIElementRef getFrontMostApp ()
{
    pid_t pid;
    ProcessSerialNumber psn;
    
    GetFrontProcess(&psn);
    GetProcessPID(&psn, &pid);
    return AXUIElementCreateApplication(pid);
}
    

int main (
    int argc,
    char ** argv
) {
    int i;
    AXValueRef temp;
    CGSize windowSize;
    CGPoint windowPosition;
    CFStringRef windowTitle;
    AXUIElementRef frontMostApp;
    AXUIElementRef frontMostWindow;
        
    if (!amIAuthorized()) {
        printf("Can't use accessibility API!\n");
        return 1;
    }
    
    /* Give the user 5 seconds to switch to another window, otherwise
     * only the terminal window will be used
     */
    for (i = 0; i < 5; i++) {
        sleep(1);
        printf("%d", i + 1);
        if (i < 4) {
            printf("...");
            fflush(stdout);
        } else {
            printf("\n");
        }
    }
    
    /* Here we go. Find out which process is front-most */
    frontMostApp = getFrontMostApp();
    
    /* Get the front most window. We could also get an array of all windows
     * of this process and ask each window if it is front most, but that is
     * quite inefficient if we only need the front most window.
     */
    AXUIElementCopyAttributeValue(
        frontMostApp, kAXFocusedWindowAttribute, (CFTypeRef *)&frontMostWindow
    );
    
    /* Get the title of the window */
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXTitleAttribute, (CFTypeRef *)&windowTitle
    );
    
    /* Get the window size and position */
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXSizeAttribute, (CFTypeRef *)&temp
    );
    AXValueGetValue(temp, kAXValueCGSizeType, &windowSize);
    CFRelease(temp);
    
    AXUIElementCopyAttributeValue(
        frontMostWindow, kAXPositionAttribute, (CFTypeRef *)&temp
    );
    AXValueGetValue(temp, kAXValueCGPointType, &windowPosition);
    CFRelease(temp);

    /* Print everything */
    printf("\n");
    CFShow(windowTitle);
    printf(
        "Window is at (%f, %f) and has dimension of (%f, %f)\n",
        windowPosition.x,
        windowPosition.y,
        windowSize.width,
        windowSize.height
    );
    
    /* Move the window to the right by 25 pixels */
    windowPosition.x += 25;
    temp = AXValueCreate(kAXValueCGPointType, &windowPosition);
    AXUIElementSetAttributeValue(frontMostWindow, kAXPositionAttribute, temp);
    CFRelease(temp);
    
    /* Clean up */
    CFRelease(frontMostWindow);
    CFRelease(frontMostApp);
    return 0;
}

Sine Ben asked how you get a list of all windows in the comments, here's how:

Instead of kAXFocusedWindowAttribute you use kAXWindowsAttribute for the AXUIElementCopyAttributeValue function. The result is then no AXUIElementRef, but a CFArray of AXUIElementRef elements, one for each window of this application.

Mecki
  • 125,244
  • 33
  • 244
  • 253
  • "We could also get an array of all windows of this process"... How would I do that? – Ben Packard Jan 21 '10 at 09:17
  • 1
    @Ben: Instead of "kAXFocusedWindowAttribute" you use "kAXWindowsAttribute" for the AXUIElementCopyAttributeValue function. The result is then no AXUIElementRef, but a CFArray of AXUIElementRef elements, one for each window of this application. – Mecki Jan 21 '10 at 10:50
  • Unfortunately, this no longer works on OSX 10.9 as `AXMakeProcessTrusted` has been deprecated. See http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9/24100593#24100593 and http://stackoverflow.com/questions/12419988/global-events-the-mac-app-store-and-the-sandbox/24100470#24100470 – fikovnik Jun 07 '14 at 19:00
  • @fikovnik Yes, it has been deprecated, but for a good reason. Go `Apple Menu -> System Preferences -> Security & Privacy -> Privacy -> Accessibility`. Note how you can now control this individually **per app**. In the past you could only enable/disable that system wide (either all apps or no app), but now the app can request this king of access and the user can grant it or not grant it. Granting this to an app has the same effect as making a process AX trusted before, the API has just been replaced by a user controllable setting, which makes much more sense IMHO. – Mecki Jun 10 '14 at 20:21
  • @Mecki - I think it would have been nice if there is a programatic way to set it. For the less fluent users it would be handy if an app can just pop-up a window asking them if they want the support to be enabled or not. In the end `AXMakeProcessTrusted` was per process even though the preference pane only showed one check box. `AXMakeProcessTrusted` was complicated as it required aux utility, but with `AXIsProcessTrustedWithOptions` they could have done an extra step IMHO. – fikovnik Jun 10 '14 at 23:17
  • Thanks for this answer @Mecki. Using the Accessibility API, is it possible to get and change the order of full screen apps / spaces? Thanks! – sethfri May 02 '21 at 06:36
  • @sethfri You should probably ask this as an own question on SO, makes it easier to answer and pulls more focus onto the topic. Be sure to explain what exactly you are trying to achieve as sometimes it may be indirectly possible but not the way you want to achieve it. – Mecki May 02 '21 at 20:33
  • @mecki I did actually, it just hasn’t gotten any replies so I went looking for other questions :( https://stackoverflow.com/questions/67330562/programmatically-change-order-of-full-screen-apps-on-macos-with-appkit – sethfri May 03 '21 at 23:12
2

I agree that Accessibility is the best way forward. But if you want quick-and-dirty, AppleScript will work as well.

vasi
  • 1,026
  • 7
  • 6