2
votes

I have the following (minor) problem that I want to solve programmatically. Whenever I unplug my secondary monitor from my laptop, every windows get moved to be visible in the now smaller resolution. When I plug my external monitor back in, I need to manually replace the windows to their correct position. I have to do that every morning (sigh).

Now I decided to write a simple command-line program that could save the position of every open windows and reposition them when I want to restore their positions.

I have managed to do something that works just fine by using the Accessibility API, which allows me to control windows that aren't part of my process space. I have a problem though: the program can only see the windows that are in my current space (I'm talking about the OSX Spaces feature here).

In other words, when I run my program to save the windows positions, it will only be able to save the positions of the windows in the space I'm currently in.

Some more details about my program:

  • It loops through all the processes running and get their PIDs.
  • It creates application elements from these PIDs (AXUIElementCreateApplication)
  • It gets the windows associated with this application, and then their positions

When getting the windows elements from the application element, the AXUIElementCopyAttributeValues only returns me the windows of the current space.

Now, is there a way to control any windows (across different spaces)? If not, I wouldn't mind programmatically changing spaces to get every windows, but that doesn't seem possible.

Any help would be appreciated!

2

2 Answers

1
votes

I'm not aware of a documented way to switch spaces.

You probably want CGSPrivate.h - CGSSetWorkspace et al. Just keep in mind those functions are SPI and can break without warning even in a 10.6.x release.

1
votes

If you want to avoid using private APIs you can take advantage of the fact Mission Control has keyboard shortcuts for moving to various spaces, and you can programmatically send the key codes to activate them. I wrote a blog post about it (http://ianyh.com/blog/2013/06/05/accessibility/) for a tiling window manager I've been working on called Amethyst, which has some example code you can check out in -[AMWindow moveToSpace:].

The short version is moving to a space looks something like using the default ctrl + arrow key to move to adjacent spaces:

CGEventRef keyboardDownEvent = CGEventCreateKeyboardEvent(NULL, kVK_RightArrow, true);
CGEventRef keyboardUpEvent = CGEventCreateKeyboardEvent(NULL, kVK_RightArrow, false);

CGEventSetFlags(keyboardDownEvent, kCGEventFlagMaskControl);
CGEventSetFlags(keyboardUpEvent, 0);

CGEventPost(kCGHIDEventTap, keyboardDownEvent);
CGEventPost(kCGHIDEventTap, keyboardUpEvent);

CFRelease(keyboardEvent);
CFRelease(keyboardEventUp);

You could combine this with NSWorkspaceActiveSpaceDidChangeNotification to traverse all spaces and gather window data.

Also, as a potentially interesting note the accessibility APIs can actually give you windows across all spaces at the same time, but it will only give you windows in spaces that you have traversed to since the process utilizing the APIs launched. I have no idea why this is the case, but it does seem to be.