How to identify which process is running which window in Mac OS X?

30

10

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?

I.Cougil

Posted 2015-04-17T16:37:40.050

Reputation: 403

“…which process is showing which window…” This is confusing when compared to your Windows example of “…which DLL/program was using the webcam by searching for a DLL or substring.” Can you please edit your question to clarify. – JakeGould – 2015-04-17T18:20:08.603

1Some processes are running without any windows, and perhaps even without a controlling terminal. – Basile Starynkevitch – 2015-04-17T19:09:21.627

Answers

23

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

I won't repost the full script without permission, but here's a summary: 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;
    }
)}

echo on

Posted 2015-04-17T16:37:40.050

Reputation: 356

Well, it is not exactly what I was looking for, but it is a good starting point. Thank you @echo on! – I.Cougil – 2015-07-13T23:30:43.120

@echo on - I'm not sure how to apply what that site shows, could you elaborate? – C_K – 2015-10-28T00:45:00.197

Looks like the link to the python script is dead. I believe I found the same post on a new blog site here: https://cadebaba.blogspot.com/2014/04/what-process-owns-certain-window-mac-os.html

– Mark Ebbert – 2017-10-04T14:43:37.770

1This is an amazing script. It helped my find nasty software which I couldn't identify. – Samvel Avanesov – 2018-02-16T04:05:18.487

NICE!! This is indeed the clean, correct way to identify and remove malware which pops up alert windows. Much better than installing and running an antivirus program which, who knows, may itself be malware. – Jerry Krinock – 2019-10-13T02:57:46.717

15

I made a tool named lswin

$ python lswin.py

    PID WinID  x,y,w,h                  [Title] SubTitle
------- -----  ---------------------    -------------------------------------------
    169  1956 {0,-38,1280,25        }   [Window Server] Backstop Menubar
    169  1955 {0,-60,1280,22        }   [Window Server] Menubar
    169   396 {0,-38,1280,25        }   [Window Server] Backstop Menubar
    169   395 {0,-60,1280,22        }   [Window Server] Menubar
    169     6 {0,0,0,0              }   [Window Server] Cursor
    169     4 {0,22,1280,25         }   [Window Server] Backstop Menubar
    169     3 {0,0,1280,22          }   [Window Server] Menubar
    169     2 {0,0,1280,800         }   [Window Server] Desktop
    262   404 {0,-38,1280,38        }   [Google Chrome] 
    262   393 {0,0,1280,800         }   [Google Chrome] 
    262   380 {100,100,1,1          }   [Google Chrome] Focus Proxy
    ... ...

Then you can use grep to find your window's pid.

Here is the source code of the script:

#!/usr/bin/env python

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:
    print ( \
        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')

osexp2003

Posted 2015-04-17T16:37:40.050

Reputation: 356

Works perfectly. Thanks for sharing @osexp2003! – Hay – 2017-12-06T09:37:11.510

11

@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)

Ruzard

Posted 2015-04-17T16:37:40.050

Reputation: 211

Fantastic. One step closer to xkill for Mac! – Michael Fox – 2017-09-08T22:01:43.727

2with a little bit of pip install pyobjc-framework-Quartz – CupawnTae – 2018-11-20T22:54:39.557

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 – 2019-01-24T20:28:37.763