Move other windows on Mac OS X using Accessibility API

7.9k views Asked by At

I'm trying to use the Accessibility API to change the position of other applications windows.What I wish to do is to get all the windows on the screen from all the applications, then move them all a given offset (lets say 5 or 10 or any value). I am having difficulties doing this since this is day one of programming in Objective-C for me.

Here is what I am doing right now. First, I find the list of windows and their PIDs using CGWindowListCopyWindowInfo. Then, for each window I use AXUIElementCreateApplication to get the AXUIElementRef of the window. After, that I should use AXUIElementCopyAttributeValue with the attribute kAXPositionAttribute (which I fail in getting the proper position, always get zeros). Finally, I should add the wanted offset to the position and use AXUIElementSetAttributeValue with the attribute kAXPositionAttribute and the new position point (which I get runtime errors even if I set absolute values like 0,0).

Can someone help me with a snippet doing what I described above, as I tried many things without any luck. Also, it shouldn't need to be exactly as I decided to implement it above. If there is a better way to go about doing that, then I'll be happy to change it.

Update: As requested in the comment, here is a code snippet of one of the attempts:

// Get all the windows
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSArray* arr = CFBridgingRelease(windowList);
// Loop through the windows
for (NSMutableDictionary* entry in arr)
{
    // Get window PID
    pid_t pid = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
    // Get AXUIElement using PID
    AXUIElementRef elementRef = AXUIElementCreateApplication(pid);
    CFTypeRef position;
    CGPoint point;
    // Get the position attribute of the window (maybe something is wrong?)
    AXUIElementCopyAttributeValue(elementRef, kAXPositionAttribute, (CFTypeRef *)&position);
    AXValueGetValue(position, kAXValueCGPointType, &point);
    // Debugging (always zeros?)
    NSLog(@"point=%@", point);
    // Create a point
    NSPoint newPoint;
    newPoint.x = 0;
    newPoint.y = 0;
    position = (CFTypeRef)(AXValueCreate(kAXValueCGPointType, (const void *)&newPoint));
    // Set the position attribute of the window (runtime error over here)
    AXUIElementSetAttributeValue(elementRef, kAXPositionAttribute, (CFTypeRef *)&position);
}
1

There are 1 answers

7
gaige On BEST ANSWER

Based on your sample code (slightly modified, as what you posted does not compile and will crash unmodified), I did some experiments.

Here are a few caveats:

  • You are retrieving the Application by PID, but then acting upon it as if it is a window. That's the core of your problem, but it is only the beginning of the solution.
  • You will need to walk the window list for the accessibility application object in order to find relocatable windows that you can move with the Accessibility Framework.
  • CGWindowListCopyWindowInfo will return "all on screen" windows when asked the way you're calling it, but it doesn't guarantee that these are either "user windows" or windows with accessibility. Most menu bar items have a root window which is "on screen" and most of them aren't accessible (which shows up when you try to walk the accessibility tree for the PIDs you retrieve).
  • You may find the test for AXRole to be helpful, or you may find other window accessibility attributes more useful in determining whether to move the windows or not.

I've included here modifications to your code (this will run without crashing), which will grab the pertinent window information from the applications you retrieve via PID and then move the windows. I have a sleep statement so that I could stop execution, since I was just testing for the effect of movement:

#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <ApplicationServices/ApplicationServices.h>

int main(int argc, char *argv[]) {
    @autoreleasepool {
    // Get all the windows
    CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    NSArray* arr = CFBridgingRelease(windowList);
    // Loop through the windows
    for (NSMutableDictionary* entry in arr)
    {
        // Get window PID
        pid_t pid = [[entry objectForKey:(id)kCGWindowOwnerPID] intValue];
        // Get AXUIElement using PID
        AXUIElementRef appRef = AXUIElementCreateApplication(pid);
        NSLog(@"Ref = %@",appRef);

        // Get the windows
        CFArrayRef windowList;
        AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute, (CFTypeRef *)&windowList);
        NSLog(@"WindowList = %@", windowList);
        if ((!windowList) || CFArrayGetCount(windowList)<1)
            continue;


        // get just the first window for now
        AXUIElementRef windowRef = (AXUIElementRef) CFArrayGetValueAtIndex( windowList, 0);
        CFTypeRef role;
        AXUIElementCopyAttributeValue(windowRef, kAXRoleAttribute, (CFTypeRef *)&role);         
        CFTypeRef position;
        CGPoint point;

        // Get the position attribute of the window (maybe something is wrong?)
        AXUIElementCopyAttributeValue(windowRef, kAXPositionAttribute, (CFTypeRef *)&position);
        AXValueGetValue(position, kAXValueCGPointType, &point);
        // Debugging (always zeros?)
        NSLog(@"point=%f,%f", point.x,point.y);
        // Create a point
        CGPoint newPoint;
        newPoint.x = 0;
        newPoint.y = 0;
        NSLog(@"Create");
        position = (CFTypeRef)(AXValueCreate(kAXValueCGPointType, (const void *)&newPoint));
        // Set the position attribute of the window (runtime error over here)
        NSLog(@"SetAttribute");
        AXUIElementSetAttributeValue(windowRef, kAXPositionAttribute, position);
        sleep(5);
    }       
    }
}