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.
-
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.
Ben Packard : "We could also get an array of all windows of this process"... How would I do that?Mecki : @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.Ben Packard : Thanks, just what I was looking for. -
I agree that Accessibility is the best way forward. But if you want quick-and-dirty, AppleScript will work as well.
0 comments:
Post a Comment