Search code examples
javamacosjna

How to get list of all window handles in Java (Using JNA) on MacOS?


I have been using the JNA library to get all visible window handles in Windows. I need to do something similar in macOS using JNA.

Here is code to get all window handles in Windows:

 public static List<HWND> findAll() {
    final List<HWND> windows = new LinkedList<>();
    User32.INSTANCE.EnumWindows(new User32.WNDENUMPROC() {
        @Override
        public boolean callback(HWND hWnd, Pointer arg) {
            if (User32.INSTANCE.IsWindowVisible(hWnd)) {
                windows.add(hWnd);
            }
            return true;
        } 
    }, null);
    return windows;
}

What is the equivalent code in macOS?


Solution

  • You'll need to map portions of the Core Graphics Framework. You can list windows using the CGWindowListCopyWindowInfo() function.

    To load the framework you'll need to map a CoreGraphics interface extending JNA's Library class, and map the function you need:

    public interface CoreGraphics extends Library {
        CoreGraphics INSTANCE = Native.load("CoreGraphics", CoreGraphics.class);
    
        CFArrayRef CGWindowListCopyWindowInfo(int option, int relativeToWindow);
    }
    

    The CFArrayRef type is already mapped in JNA in the CoreFoundation class. Pick the appropriate Window List Option (probably kCGWindowListOptionAll = 0). If you already had a window number you could use relative reerences, otherwise you'll use kCGNullWindowID (0) for the second parameter. Calling it from your code should be simple:

    CFArrayRef windowInfo = CoreGraphics.INSTANCE.CGWindowListCopyWindowInfo(0, 0);
    

    That will give you an array of CFDictionaryRef objects representing the windows. You can iterate the array and then use further methods in the CFDictionaryRef class to explore these dictionary objects: you'll create a CFString for the keys. A list of required keys is documented here and optional keys are here. The constant strings match the variable name.

    This should get you a CFNumberRef for each window number (the "handle" equivalent):

    // Set up keys for dictionary lookup
    CFStringRef kCGWindowNumber = CFStringRef.createCFString("kCGWindowNumber");
    CFStringRef kCGWindowOwnerPID = CFStringRef.createCFString("kCGWindowOwnerPID");
    // Note: the Quartz name is rarely used
    CFStringRef kCGWindowName = CFStringRef.createCFString("kCGWindowName");
    CFStringRef kCGWindowOwnerName = CFStringRef.createCFString("kCGWindowOwnerName");
    
    // Iterate the array
    int numWindows = windowInfo.getCount();
    for (int i = 0; i < numWindows; i++) {
        // For each array element, get the dictionary
        Pointer result = windowInfo.getValueAtIndex(i);
        CFDictionaryRef windowRef = new CFDictionaryRef(result);
    
        // Now get information from the dictionary.
    
        // Get a pointer to the result, in this case a CFNumber
        result = windowRef.getValue(kCGWindowNumber);
        // "Cast" the pointer to the appropriate type
        CFNumberRef windowNumber = new CFNumberRef(result);
        // CoreFoundation.INSTANCE.CFNumberGetType(windowNumber)
        // --> 4 = kCFNumberSInt64Type, signed 64 bit so use getLong()
        
        // Get a pointer to the result, in this case a CFNumber
        result = windowRef.getValue(kCGWindowOwnerPID);
        // "Cast" the pointer to the appropriate type
        CFNumberRef windowOwnerPID = new CFNumberRef(result);
        // CoreFoundation.INSTANCE.CFNumberGetType(windowOwnerPID)
        // --> 4 = kCFNumberSInt64Type, signed 64 bit so use getLong()
    
        // Get a pointer to the result, in this case a CFString
        result = windowRef.getValue(kCGWindowName);
        // "Cast" the pointer to the appropriate type
        // Optional key, check for null
        String windowName = result == null ? "" : new CFStringRef(result).stringValue();
    
        // Get a pointer to the result, in this case a CFString
        result = windowRef.getValue(kCGWindowOwnerName);
        // "Cast" the pointer to the appropriate type
        // Optional key, check for null
        String windowOwnerName = result == null ? "" : new CFStringRef(result).stringValue();
    
        // ... look up other keys if needed ...
        // use ProcessHandle with the PID to get start time
    
        // Output or add to List, etc.
        System.out.println(windowNumber.longValue() 
            + " (" + windowOwnerName + ", pid=" 
            + windowOwnerPID.longValue()
            + "): " + windowName);
    }
    
    // CF references from "Copy" or "Create" must be released
    // release the created key references
    kCGWindowNumber.release();
    kCGWindowOwnerPID.release();
    kCGWindowName.release();
    kCGWindowOwnerName.release();
    // release the array
    windowInfo.release();