56

I’d like to know if it is possible to identify which process is responsible for creating/managing a window in Mac OS X.

For example, when multiple instances of an application are started, how can I get the process ID (PID) corresponding to one specific window? Or if there is a modal dialog window without a title, how can I get the PID of its owner?

I know in Windows it is possible using the Sysinternals Suite tool that provides a way to search for a library that is running with some data.

I’m looking for a mechanism similar to the one that appears in this blogpost.

In this case, using Sysinternals Suite (and Process Explorer), they found which DLL/program was using the webcam by searching for a DLL or substring (in this case, using the physical name of the device).

So is there any mechanism or program, or do you have any idea about how to search for something similar for Mac OS X? How I can identify which process has launched a window?

2
  • 1
    Some processes are running without any windows, and perhaps even without a controlling terminal. Apr 17, 2015 at 19:09
  • I think what people are yearning for is something like ps -p $(xprop | grep PID | awk -F= '{print $2}'), if there existed an xprop-like thing for macOS.
    – Kevin E
    Jan 11 at 18:48

7 Answers 7

39

I've used this Python 2 script. It isn't foolproof, but it works pretty well for me.

Here's a summary of what it does: It uses CGWindowListCopyWindowInfo, which is imported from Quartz, to collect window info from the system, then asks the user to move the desired window, then collects window info again, and shows info for the ones that changed. The info dumped includes the process ID, as kCGWindowOwnerPID.

Here is the code:

#!/usr/bin/env python

import time
from Quartz import CGWindowListCopyWindowInfo, kCGWindowListExcludeDesktopElements, kCGNullWindowID
from Foundation import NSSet, NSMutableSet

wl1 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
print 'Move target window'
time.sleep(5)
wl2 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)

w = NSMutableSet.setWithArray_(wl1)
w.minusSet_(NSSet.setWithArray_(wl2))
print '\nList of windows that moved:'
print w
print '\n'

The script prints information for the window that changed position within a 5 second interval. So the output looks like this:

List of windows that moved:
{(
        {
        kCGWindowAlpha = 1;
        kCGWindowBounds =         {
            Height = 217;
            Width = 420;
            X = 828;
            Y = 213;
        };
        kCGWindowIsOnscreen = 1;
        kCGWindowLayer = 8;
        kCGWindowMemoryUsage = 406420;
        kCGWindowName = "";
        kCGWindowNumber = 77;
        kCGWindowOwnerName = UserNotificationCenter;
        kCGWindowOwnerPID = 481;
        kCGWindowSharingState = 1;
        kCGWindowStoreType = 2;
    }
)}
7
  • Well, it is not exactly what I was looking for, but it is a good starting point. Thank you @echo on! Jul 13, 2015 at 23:30
  • @echo on - I'm not sure how to apply what that site shows, could you elaborate?
    – C_K
    Oct 28, 2015 at 0:45
  • 1
    Looks like the link to the python script is dead. I believe I found the same post on a new blog site here: cadebaba.blogspot.com/2014/04/… Oct 4, 2017 at 14:43
  • 1
    This is an amazing script. It helped my find nasty software which I couldn't identify. Feb 16, 2018 at 4:05
  • 1
    You will need to install Quartz: pip install pyobjc-framework-Quartz. Jun 10, 2022 at 14:55
34

I made a tool named lswin

#!/usr/bin/env python

# Install Quartz with 'pip install -U pyobjc-framework-Quartz'
import Quartz

#wl = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID)
wl = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID)

wl = sorted(wl, key=lambda k: k.valueForKey_('kCGWindowOwnerPID'))

#print wl

print('PID'.rjust(7) + ' ' + 'WinID'.rjust(5) + '  ' + 'x,y,w,h'.ljust(21) + ' ' + '\t[Title] SubTitle')
print('-'.rjust(7,'-') + ' ' + '-'.rjust(5,'-') + '  ' + '-'.ljust(21,'-') + ' ' + '\t-------------------------------------------')

for v in wl:
    raw_chars = ( \
        str(v.valueForKey_('kCGWindowOwnerPID') or '?').rjust(7) + \
        ' ' + str(v.valueForKey_('kCGWindowNumber') or '?').rjust(5) + \
        ' {' + ('' if v.valueForKey_('kCGWindowBounds') is None else \
            ( \
                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('X')))     + ',' + \
                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Y')))     + ',' + \
                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Width'))) + ',' + \
                str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Height'))) \
            ) \
            ).ljust(21) + \
        '}' + \
        '\t[' + ((v.valueForKey_('kCGWindowOwnerName') or '') + ']') + \
        ('' if v.valueForKey_('kCGWindowName') is None else (' ' + v.valueForKey_('kCGWindowName') or '')) \
    )
    print(raw_chars)
1
  • 3
    There's a pull request to bring the code to Python 3. Raw file from the PR can be found here, to install the dependency use pip3 install pyobjc-framework-Quartz
    – DarkDust
    Feb 8, 2022 at 10:27
13

There's a direct user-friendly OS-native tool for inspecting each window which provide comprehensive information in addition to related process, such as originating code hierarchy : it's found under Xcode Developer Tools, and called Accessibility Inspector

1
  • 3
    Spotlight "Accessibility Inspector" took me right to it. Crosshairs for selecting window of interest. Thank you!!
    – mike
    Nov 16, 2020 at 20:18
12

@kenorb I combined your 2 versions of the script, basically it works like the first one, showing difference but formatting is from the second. Also if window is not on the screen - it's not being printed, otherwise it gives too much garbage

import Quartz
import time
from Foundation import NSSet, NSMutableSet
def transformWindowData(data):
    list1 = []
    for v in data:
        if not v.valueForKey_('kCGWindowIsOnscreen'):
            continue


        row = ( \
            str(v.valueForKey_('kCGWindowOwnerPID') or '?').rjust(7) + \
            ' ' + str(v.valueForKey_('kCGWindowNumber') or '?').rjust(5) + \
            ' {' + ('' if v.valueForKey_('kCGWindowBounds') is None else \
                ( \
                    str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('X')))     + ',' + \
                    str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Y')))     + ',' + \
                    str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Width'))) + ',' + \
                    str(int(v.valueForKey_('kCGWindowBounds').valueForKey_('Height'))) \
                ) \
                ).ljust(21) + \
            '}' + \
            '\t[' + ((v.valueForKey_('kCGWindowOwnerName') or '') + ']') + \
            ('' if v.valueForKey_('kCGWindowName') is None else (' ' + v.valueForKey_('kCGWindowName') or '')) \
        ).encode('utf8')
        list1.append(row)

    return list1;

def printBeautifully(dataSet):
    print 'PID'.rjust(7) + ' ' + 'WinID'.rjust(5) + '  ' + 'x,y,w,h'.ljust(21) + ' ' + '\t[Title] SubTitle'
    print '-'.rjust(7,'-') + ' ' + '-'.rjust(5,'-') + '  ' + '-'.ljust(21,'-') + ' ' + '\t-------------------------------------------'

    # print textList1
    for v in dataSet:
        print v;

#grab initial set
wl = Quartz.CGWindowListCopyWindowInfo( Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID)
wl = sorted(wl, key=lambda k: k.valueForKey_('kCGWindowOwnerPID'))

#convert into readable format
textList1 = transformWindowData(wl);

#print everything we have on the screen
print 'all windows:'
printBeautifully(textList1)

print 'Move target window'
time.sleep(5)

#grab window data the second time
wl2 = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID)
textList2 = transformWindowData(wl2)

#check the difference
w = NSMutableSet.setWithArray_(textList1)
w.minusSet_(NSSet.setWithArray_(textList2))

#print the difference
printBeautifully(w)
3
  • Fantastic. One step closer to xkill for Mac! Sep 8, 2017 at 22:01
  • 2
    with a little bit of pip install pyobjc-framework-Quartz
    – CupawnTae
    Nov 20, 2018 at 22:54
  • 1
    Note that the script doesn't work 100% with multi-monitor set ups. If you run this in a terminal on one screen and then move the window on another screen, you will see many windows listed in the diff. All of them seem to be system windows and icons in the menu bar, etc. Best to move your terminal and the mystery window to the same screen before running.
    – DaveBurns
    Jan 24, 2019 at 20:28
10

You can use the Automator.app for this:

  1. Start Automator (in spotlight type Automator.app + Enter)
  2. Create a new workflow.
  3. On the menu select Workflow > Record (or click the 🔴 [Record] button on the toolbar).
  4. Interact with the window you wish to inspect and then click the ⬛️ [Stop] button in the recording toolbar when done. (❗️ If the system prompts you about missing accessibility access then grant it in System Settings)
  5. Drag the recorded steps of interest from the Watch Me Do section to the empty panel below (a plus button will appear during drag). HINT: You can also select all steps and drag them all together in order to create a single script.
  6. Inspect the generated AppleScript to get the details you need.

NOTE: You can use this method to generate AppleScript code for locating windows, dialogs, setting text, clicking buttons, etc.

Here is a random example video for recording UI actions.

0

Updated @echo on's script to python3, for those in need. I found it immensely useful. Note that you need to install Quartz with pip install pyobjc-framework-Quartz beforehand.

#!/usr/bin/env python

import time
from Quartz import CGWindowListCopyWindowInfo, kCGWindowListExcludeDesktopElements, kCGNullWindowID
from Foundation import NSSet, NSMutableSet

wl1 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
print ('Move target window')
time.sleep(5)
wl2 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)

w = NSMutableSet.setWithArray_(wl1)
w.minusSet_(NSSet.setWithArray_(wl2))
print ('\nList of windows that moved:')
print (w)
print ('\n')
0

This might not be foolproof, but it's probably the simplest solution, if and when it works. No extra software or developer tools to install, which may not be possible in managed/corporate environments anyway.

  1. Have Activity Monitor open to the "CPU" tab, sorted descending by "% CPU"
  2. Press and hold Tab, or press it repeatedly.
  3. Watch for which application rises to the top of the list and make a note of its PID.

I'm not sure if that that setting for tabbing between controls under System Preferences → KeyboardShortcuts will factor in here. I don't think it will, but for what it's worth, mine is currently unchecked.

The key you press probably doesn't have to be Tab, but that key has the advantage of moving the focus around user interface elements, requiring more involvement from the CPU, hence propelling it to the top of the process list faster.

1
  • @Contredanse, I just realized I could've simply approved your edit, overriding the reviewers, but I incorporated all your points anyway. Thanks!
    – Kevin E
    Jan 11 at 18:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .