How to list all windows from all workspaces in Python on Mac?
Asked Answered
X

2

4

The following Python 2 code prints list of all windows in the current workspace:

#!/usr/bin/python
import Quartz
for window in Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly, Quartz.kCGNullWindowID):
    print("%s - %s" % (window['kCGWindowOwnerName'], window.get('kCGWindowName', u'Unknown').encode('ascii','ignore')))

Although it doesn't print the applications which are in full screen (as it's in another workspace).

How do I modify above script to list all windows from all desktops?

Xenolith answered 28/5, 2017 at 22:0 Comment(0)
G
6

The following script should return window information on any desktop/workspace/display, fullscreen, and detailed information (coordinates, pid, titles, etc.):

#!/usr/bin/python

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

def windowList(wl):
    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') # remove 'encode' for Python 3.x

wl1 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
print('Move target window (or ignore)\n')
time.sleep(5)

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

wl2 = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)

w = NSMutableSet.setWithArray_(wl1)
w.minusSet_(NSSet.setWithArray_(wl2))

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

windowList(wl)

print('\nDetailed window information: {0}\n'.format(w))
Gurolinick answered 28/5, 2017 at 22:35 Comment(0)
X
2

The key is here to use the right option for the 1st argument of CGWindowListCopyWindowInfo. So apart of using optionOnScreenOnly property (which list all windows that are currently onscreen), excludeDesktopElements property needs to be added.

excludeDesktopElements: Exclude any windows from the list that are elements of the desktop, including the background picture and desktop icons.

E.g.

list = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID)

Alternatively for all windows, kCGWindowListOptionAll property can be also used.

kCGWindowListOptionAll: List all windows, including both onscreen and offscreen windows. When retrieving a list with this option, the relativeToWindow parameter should be set to kCGNullWindowID.

For other properties, check CGWindow.h in CoreGraphics.


So the original code can be changed to:

#!/usr/bin/python
# Prints list of all windows.
# See: https://mcmap.net/q/751243/-how-to-list-all-windows-from-all-workspaces-in-python-on-mac/55075
import Quartz
for window in Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID):
    print("%s - %s" % (window['kCGWindowOwnerName'], window.get('kCGWindowName', u'Unknown').encode('ascii','ignore')))
Xenolith answered 29/5, 2017 at 12:35 Comment(1)
Shouldn't this be a bitwise or?Shrum

© 2022 - 2024 — McMap. All rights reserved.