Search code examples
objective-cmacosapplescript

unified approach to getting PID, window ID, process name and window name from active window on mac


I am writing an application that needs to periodically obtain the PID, process name, window ID and window name of the active window. The program is written in Go, but the issues relate to any FFI into the Mac ecosystem. I have two approaches, via AppleScript and via Objective C.

Both give a 75% solution; either missing the window ID or the window name. I would like to find a single unified solution that gives all four attributes rather than having to cobble together one that depends on both of the approaches that I have.

AppleScript approach; does not provide WindowNumber for most cases.

global activeApp, activePID, activeName, windowName
set windowName to ""
tell application "System Events"
    set activeApp to first application process whose frontmost is true
    set activePID to unix id of activeApp
    set activeName to name of activeApp
    tell process activeName
        try
            tell (1st window whose value of attribute "AXMain" is true)
                set windowName to value of attribute "AXTitle"
            end tell
        end try
    end tell
end tell
return ("{\"pid\":" & activePID & ",\"name\":\"" & activeName & "\",\"window\":\"" & windowName & "\"}" as text)

Objective C approach; does not provide WindowName for most cases.

#include <Cocoa/Cocoa.h>
#include <CoreGraphics/CGWindow.h>

struct details {
   int wid;
   int pid;
   const char* name;
   const char* window;
};

int activeWindow(struct details *d)
{
    if (d == NULL) {
        return 0;
    }
    NSArray *windows = (NSArray *)CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements|kCGWindowListOptionOnScreenOnly,kCGNullWindowID);
    for(NSDictionary *window in windows){
        int WindowLayer = [[window objectForKey:(NSString *)kCGWindowLayer] intValue];
        if (WindowLayer == 0) {
            d->wid = [[window objectForKey:(NSString *)kCGWindowNumber] intValue];
            d->pid = [[window objectForKey:(NSString *)kCGWindowOwnerPID] intValue];
            d->name = [[window objectForKey:(NSString *)kCGWindowOwnerName] UTF8String];
            d->window = [[window objectForKey:(NSString *)kCGWindowName] UTF8String];
            return 1;
        }
    }
    return 0;
}

Solution

  • From a Developer forum topic, your Objective-C approach should work if the application has been given Screen Recording permission.

    There are a few windows (such as those in menu items) that have weird names, and some just don't have them, but most application and document windows I've tried do have names. There doesn't appear to be an entitlement for Screen Recording, so you would need to direct the user to add the application to the privacy list.

    When testing with Xcode 14.1 in macOS Monterey 12.6.5, the kCGWindowName key in the window list dictionary is included when permission has been granted, otherwise it is left out.