alexgolec
alexgolec

Reputation: 28282

Is there a way to iterate over all open windows in Mac OS X?

When you unplug an external monitor with a higher resolution that your macbook from your laptop, the windows mostly retain their width, but their size gets clipped to the (smaller) height of the macbook screen. When you plug the monitor back in, their size remains frustratingly small.

My question is: is there any way that I can iterate over all open windows, save their size, and restore them once the monitor gets plugged in again?

Upvotes: 3

Views: 4855

Answers (2)

Anne
Anne

Reputation: 27073

The following AppleScript show how to:

  • Loop through all windows
  • Retrieve and change window position
  • Retrieve and change window size

Code:

tell application "System Events"
    set theProcesses to application processes
    repeat with theProcess from 1 to count theProcesses
        tell process theProcess
            repeat with x from 1 to (count windows)
                set windowPosition to position of window x
                set windowSize to size of window x
                set position of window x to {0, 0}
                set size of window  to {100, 100}
            end repeat
        end tell
    end repeat
end tell

Note: The script requires access for assistive devices (AfAD):
"System Preferences" → "Universal Acces" → "Enable access for assistive devices"

EDIT (response to comment)

Enabling AfAD from AppleScipt might improve user experience, but don't do this every time the script is executed, only enable AfAD in case AfAD is disabled. Enabling features without informing the user is no good practice, prompt the user for permission to enable AfAD.

Example:

set AccesEnables to do shell script "[ -e \"/private/var/db/.AccessibilityAPIEnabled\" ] && echo \"Yes\" || echo \"No\""
if (AccesEnables is equal to "No") then
    set askUser to display dialog "This application requires access for assistive devices. Enable this feature?" default button 2
    set answer to button returned of askUser
    if answer is equal to "OK" then
        do shell script "touch /private/var/db/.AccessibilityAPIEnabled" with administrator privileges
    else
        close
    end if
end if

Upvotes: 10

abarnert
abarnert

Reputation: 366103

The AppleScript solution—for which see Anne's answer—is by far the easiest.

If you want to give this to friends who can't be relied on to figure out how to enable assistive access, or distribute it more widely, just add this line:

do shell script ¬
  "touch /private/var/db/.AccessibilityAPIEnabled" ¬
  with administrator privileges

This will pop up the usual auth dialog and then use the privileges to turn on assistive access.

It is actually possible to do this without assistive access, but it requires using private functions within CoreGraphics/Quartz Window Services, namely CGSPrivate.h.

With public APIs, you can easily enumerate all of the windows:

CFArrayRef windows = 
  CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly |
                             kCGWindowListExcludeDesktopElements,
                             kCGNullWindowID);

This returns an array of dictionaries, and each dictionary contains a kCGWindowBounds dictionary, which has Height, Width, X, and Y integer values.

But these public APIs are strictly read-only. To actually move the windows, you have to dip into CGSPrivate.h to do something like this:

CGSConnection conn = _CGSDefaultConnection();
for (NSDictionary *window in windows) {
  CGSWindow wid = (CGSWindow)[[window objectForKey:@"kCGWindowNumber"] intValue];
  CGRect bounds;
  CGRectMakeWithDictionaryRepresentation([window objectForKey:@"kCGWindowBounds"],
                                         &bounds);
  CGSMoveWindow(conn, wid, bounds.origin);
}

Obviously, this is pretty nasty, and you should only consider it if you really need to distribute an app that can't ask for assistive access.

You could also reverse-engineer the Window Server protocol and talk to it directly, but this is even nastier.

Upvotes: 3

Related Questions