woutr_be
woutr_be

Reputation: 9722

CGWindowListCopyWindowInfo: multiple screens and changing properties

I'm working on a simple Mac app that detects when an external screen is unplugged, saves the location of all windows and once the external screen is plugged in again, restores all windows to its original position. (I know there are apps out there already, I'm just curious how this is done)

After a lot of searching, I finally managed to get all windows on screen by using

NSArray *openWindows = [[NSWorkspace sharedWorkspace] runningApplications];
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);

This would return something like

{
    kCGWindowAlpha = 1;
    kCGWindowBounds =         {
        Height = 22;
        Width = 279;
        X = 1559;
        Y = 0;
    };
    kCGWindowIsOnscreen = 1;
    kCGWindowLayer = 25;
    kCGWindowMemoryUsage = 13596;
    kCGWindowName = "";
    kCGWindowNumber = 18;
    kCGWindowOwnerName = SystemUIServer;
    kCGWindowOwnerPID = 260;
    kCGWindowSharingState = 1;
    kCGWindowStoreType = 2;
},
    {
    kCGWindowAlpha = 0;
    kCGWindowBounds =         {
        Height = 22;
        Width = 1920;
        X = 0;
        Y = 0;
    };
    kCGWindowIsOnscreen = 1;
    kCGWindowLayer = 25;
    kCGWindowMemoryUsage = 5404;
    kCGWindowNumber = 19;
    kCGWindowOwnerName = SystemUIServer;
    kCGWindowOwnerPID = 260;
    kCGWindowSharingState = 1;
    kCGWindowStoreType = 2;
},

I would then loop through the array and look at each individual window

 for (int i = 0; i < CFArrayGetCount(windowList); i++) {
    CFDictionaryRef ref = CFArrayGetValueAtIndex(windowList, i);

    NSLog(@"%@", CFDictionaryGetValue(ref, kCGWindowBounds));
}

But this is where I got stuck, how can I first of all know on which screen a window is when working with multiple screens. And secondly, how can I adjust the window bounds later on? Does each application has its own ID? Or is there another method I could use?

Upvotes: 4

Views: 3460

Answers (1)

Thomas Zoechling
Thomas Zoechling

Reputation: 34253

As the CGWindowList APIs don't expose a screen ID, you'll have to check the window bounds against the screen bounds:

NSArray* windowList = (__bridge NSArray*)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
uint32_t maxDisplayCount = 10;
CGDirectDisplayID onlineDisplayIDs[maxDisplayCount];
uint32_t displayCount;
CGGetOnlineDisplayList(maxDisplayCount, (CGDirectDisplayID*)&onlineDisplayIDs, &displayCount);
for(uint32_t i = 0; i < displayCount; ++i)
{
    CGRect dspyRect = CGDisplayBounds(onlineDisplayIDs[i]);
    for(NSDictionary* windowDict in windowList)
    {
        CGRect windowRect;
        CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)(windowDict[(id)kCGWindowBounds]), &windowRect);
        if(CGRectContainsRect(dspyRect, windowRect))
        {
            NSLog(@"window %@ is on screen with ID:%d", windowDict[(id)kCGWindowName], onlineDisplayIDs[i]);
        }
    }
}

The above code performs a naive check, that tests if a window is on a specific screen in its entirety.
OS X considers a window belonging to a screen if a certain portion of a window covers that screen. (This behavior also slightly changed between OS X releases)

To move the window you could use Apple Events or Cocoa Accessibility APIs.
Note that Cocoa Accessibility requires your uses to enable "Enable access for assistive devices" in the System Preferences.
Both technologies may have issues when you enable App Sandboxing.

Upvotes: 3

Related Questions