18

I want to get the list of window titles of the currently running applications.

On windows I have EnumWndProc and GetWindowText.

On Linux I have XGetWindowProperty and XFetchName.

What is the Native Mac equivalent?

Radiodef
  • 37,180
  • 14
  • 90
  • 125
Phil Hannent
  • 12,047
  • 17
  • 71
  • 118

2 Answers2

13

A few potentially useful references:

CGSGetWindowProperty is not officially documented, but I believe you can use it with the an item of NSWindowList() as follows (completely untested):

OSErr err;
CGSValue titleValue;
char *title;
CGSConnection connection = _CGSDefaultConnection();
int windowCount, *windows, i;

NSCountWindows(&windowCount);
windows = malloc(windowCount * sizeof(*windows));
if (windows) {
    NSWindowList(windowCount, windows);
    for (i=0; i < windowCount; ++i) {
        err = CGSGetWindowProperty(connection, windows[i], 
                    CGSCreateCStringNoCopy("kCGSWindowTitle"), 
                    &titleValue);
        title = CGSCStringValue(titleValue);
    }
    free(windows);
}

In AppleScript, it's really easy:

tell application "System Events" to get the title of every window of every process

You can call applescript from within an application using NSAppleScript or use appscript as an ObjC-AppleScript bridge. With Leopard, you can use the Scripting Bridge (more untested code):

SystemEventsApplication *systemEvents = [SBApplication applicationWithBundleIdentifier:@"com.apple.systemevents"];
SBElementArray *processes = [systemEvents processes];
for (SystemEventsProcess* process in processes) {
    NSArray *titles = [[process windows] arrayByApplyingSelector:@selector(title)];
}

You could even try it in one long call, if you don't care about readability.

SystemEventsApplication *systemEvents = [SBApplication applicationWithBundleIdentifier:@"com.apple.systemevents"];
NSArray *titles = [[[systemEvents processes] 
                     arrayByApplyingSelector:@selector(windows)] 
               arrayByApplyingSelector:@selector(arrayByApplyingSelector:) 
               withObject:@selector(title)];

The compiler will complain that @selector(title) is the wrong type, but it should work. Hand roll some delegation and you could turn the call into [[[systemEvents processes] windows] title].

Grisha Levit
  • 8,194
  • 2
  • 38
  • 53
outis
  • 75,655
  • 22
  • 151
  • 221
  • 2
    Note that AppleScript is using the accessibility interfaces, which are public and have C equivalents (see http://developer.apple.com/mac/library/documentation/Accessibility/Reference/AccessibilityLowlevel/). CGS* APIs are not only not documented, they may change at any time. (So, only use them if you're willing to test early and often on new OS versions and have no other choice.) – Nicholas Riley Oct 23 '09 at 19:01
  • 1
    Do you have a usage example for the Accessibility API? I had hoped to include it, but aren't familiar enough with it to provide how to use it. – outis Oct 23 '09 at 23:02
  • Also, it requires that Accessibility be enabled. – outis Oct 23 '09 at 23:05
  • You know what? I just tested the AppleScript solution, and getting the list of windows also requires enabling Accessibility. – outis Oct 23 '09 at 23:09
  • 3
    Sample Accessibility API code: http://developer.apple.com/mac/library/samplecode/UIElementInspector/index.html – outis Oct 24 '09 at 06:33
8

The CGSPrivate.h header that's floating around isn't directly compatible with OS X 10.8 in that CGSGetWindowProperty() no longer exists (well, it does, but you can't link to it anymore). So add these two lines to the CGSPrivate.h file -- I went ahead and figured this out myself after many hours searching Google -- to get it to work:

extern CGSConnection CGSDefaultConnectionForThread(void);
extern CGError CGSCopyWindowProperty(const CGSConnection cid, NSInteger wid, CFStringRef key, CFStringRef *output);

Adapting outis's code, here's a way of iterating through each window title. I have tested this with clang 4.2 on Mountain Lion:

CFStringRef titleValue;
CGSConnection connection = CGSDefaultConnectionForThread();
NSInteger windowCount, *windows;

NSCountWindows(&windowCount);
windows = (NSInteger*) malloc(windowCount * sizeof(NSInteger));
if (windows) {
    NSWindowList(windowCount, windows);
    for (int i = 0; i < windowCount; ++i)
    {
        CGSCopyWindowProperty(connection, windows[i], CFSTR("kCGSWindowTitle"), &titleValue);

        if(!titleValue) //Not every window has a title
            continue;

        //Do something with titleValue here
    }
    free(windows);
}

Some other stuff I found out includes the following:

  1. No window title exceeds 127 bytes.
  2. Window titles are encoded with kCFStringEncodingMacRoman

So, if you want it as a C-string, write something like this:

char *cTitle[127] = {0};
CFStringGetCString(titleValue,cTitle,127,kCFStringEncodingMacRoman);

Personally, I'd recommend doing it this way since the Accessibility API is a total pain and requires extra permissions.

Hope this helps someone! Cheers!

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
  • Thanx for this answer but how about portabilty? The `CGS_xx` can't be found and/or are deprecated by `10.6`. I code for 10.8.2 ML and certainly can't rely on deprecated stuff. –  Oct 20 '14 at 09:25
  • Well, the code I gave above was tested on Mountain Lion. It's not that it can't be found so much as it's changed. The reversing work I did above gives the correct signatures that will allow you to solve **this particular problem** using `CGS_xx`. I didn't try for anything other than window titles. If you feel you need to use this, you should probably release multiple versions of your package (ie. for 10.6 and below or for 10.7+) – Alex Reinking Oct 20 '14 at 19:27