Remove empty items from a list (get all windows of all processes)

Finding which “LSUIElement” background app had been responsible for leaving unclosable windows behind, I used this script:

tell application "System Events"
	get windows of every process
end tell

The problem is that this lists every process, which includes nearly 100 processes that have no windows.

I’d like to get a list of processes with only non-empty window lists.

I tried various ways of using whose and where without success.

Is there an elegant way to solve this, or do I have to assign the list to a variable and then go over that list, checking their contents, and building a second list from it? Which is rather tedious (and hard to remember due to the - for me - complicated list handling).

BTW, here’s a solution that shows the result in the debugger (SD or Script Editor):

I appreciate comments for improvements.

tell application "System Events"
	set res to {}
	set ps to every process
	repeat with p in ps
		set wl to windows of p
		if (count of wl) > 0 then
			set names to get (title of every window of p)
			set end of res to {application:name of p, windowTitles:names}
		end if
	end repeat
	return res
end tell

And a version that prints them in an alert window:

tell application "System Events"
	set res to {}
	set ps to every process
	set AppleScript's text item delimiters to return & tab
	repeat with p in ps
		set wl to windows of p
		if (count of wl) > 0 then
			set names to get (title of every window of p)
			set end of res to name of p & ":" & return & tab & (names as string)
		end if
	end repeat
	set AppleScript's text item delimiters to return
	display dialog "All apps with windows" & return & return & (res as string)
end tell

This will remove a lot of the extra fluff:

tell application "System Events"
	get windows of (every process whose background only is false)
end tell

Stan C.

That’s not helpful in my case because it removes exactly those helper apps I wanted to find with my script (it was an Apple process that kept windows open after failing to eject a volume, so I had to identify the process so that I could then quit it using Activity Monitor).

Flatten the list ?

use framework "Foundation"

-- flatten :: [[a]] -> [a]
on flatten(xs)
    ((current application's NSArray's arrayWithArray:xs)'s ¬
        valueForKeyPath:"@unionOfArrays.self") as list
end flatten


flatten({{"alpha"}, {}, {"beta"}, {}})

--> {"alpha", "beta"}
2 Likes

What, the ObjC bridge can automatically convert between AppleScript lists and NSArray? And apparently the same for NSDictionary! I had not dared dreaming of this. Very handy.

1 Like

With respect to @ComplexPoint’s answer, there are a couple of options here for the flatten handler. One can use @unionOfArrays.self if one doesn’t care about duplicates, or @distinctUnionOfArrays.self when one decides to remove duplicates.

This is discussed further in Apple’s Key-Value programming guide.

1 Like

Yes – that one I would think of more as nub than flatten – note that the value returned is indeed pruned of duplicates, but is not flattened to a list of atomic values.

i.e. the membership of the list changes, but not its nesting depth.

use framework "Foundation"

-- flatten :: [[a]] -> [a]
on flatten(xs)
    ((current application's NSArray's arrayWithArray:xs)'s ¬
        valueForKeyPath:"@unionOfArrays.self") as list
end flatten


-- nub :: [a] -> [a]
on nub(xs)
    ((current application's NSArray's arrayWithArray:xs)'s ¬
        valueForKeyPath:"@distinctUnionOfObjects.self") as list
end nub


flatten({{"alpha"}, {}, {"beta"}, {"alpha"}, {}})

--> {"alpha", "beta"}


nub({{"alpha"}, {}, {"beta"}, {"alpha"}, {}})

--> {{}, {"alpha"}, {"beta"}}
1 Like

The flatten handler gives me unduplicated atomic text list elements when using @distinctUnionOfArrays.self and the nub handler returns a list of three unique list items where the first element is an empty list – just as shown in your example.

1 Like

Script only uses one list

local res, sw, c, i
tell application "System Events"
	set res to windows of every process
end tell
set c to 1
repeat with i from 1 to count res
	if 0 = count (item i of res) then
		if i ≠ c then
			set sw to item i of res
			set item i of res to item c of res
			set item c of res to sw
		end if
		set c to c + 1
	end if
end repeat
return items c thru -1 of res

Despite being a late contribution, most of the attention has (appropriately) been put towards cleaning up the list of processes and windows from System Events—which is very often the fastest approach across a variety of contexts in AppleScript to enumerate and filter a large number of objects—so, instead, I’ve elected to turn my attention towards an enumeration of processes in System Events that yields a fully-saturated list.

It is not remotely fast, although not intolerably slow. Regardless of whether it has practical utility for you or other people in the real world, it might offer some value as a point of interest in the context of the following objectives:

I ran your script to visualise the kind of output you had had in mind:

which yielded the following output, correctly enumerating the windows and applications I opened for this purpose to cover a variety of window styles from both foreground and background processes:

{{application:"iTerm2", windowTitles:{"time osascript -s s  ~/L/M/c/#/key"}},
 {application:"One Thing", windowTitles:{"One Thing"}},
 {application:"Arc", windowTitles:{"Remove empty items from a list (get all windows of all processes) - AppleScript - Late Night Software Ltd."}},
 {application:"Transmission", windowTitles:{"Transmission"}},
 {application:"Telegram", windowTitles:{"Telegram"}},
 {application:"Photo Booth", windowTitles:{"Photo Booth"}},
 {application:"Finder", windowTitles:{"Finder Preferences", "CK", "Downloads"}}}

Execution time: ~7.7s

Aside: About UI element collections

It’s worth bearing in mind that, since you’re implementing a repeat loop to iterate through every process, your concern regarding the presence of empty elements returned by enumerating every window in every process isn’t necessarily as pertinent as it might first appear if you aren’t familiar with how System Events handles its enumerated lists of UI elements prior to dereferencing:

The following code simply enumerates the names of windows to illustrate this aside:

tell application id "com.apple.systemevents" to get ¬
        the name of every window in every process

It produces the following output when run against the same set of windows and processes as before:

{{}, {}, {}, {}, {}, {}, {}, {}, {"osascript -s s -e 't
~/L/M/c/#/key"}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {"One Thing"}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {}, {}, {}, {"Remove empty items from a
list (get all windows of all processes) - AppleScript -
Late Night Software Ltd."}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
{}, {}, {}, {}, {}, {}, {}, {}, {"Transmission"}, {}, {},
{}, {"Telegram"}, {}, {"Photo Booth"}, {}, {"Finder
Preferences", "CK", "Downloads"}, {}, {}, {}, {}, {}, {}}

Looking at this output, the presence of the numerous empty list elements makes sense (those processes have no windows, and no names to populate their respective entries), but beyond looking somewhat unwieldy in depicting what appears to be a laborious task of cleaning up the result, observe what happens if you access the collection while it’s still in its referenced state (i.e. prior to evaluation):

tell application id ("com.apple.systemevents") ¬
        to repeat with W in processes's windows
        log (get name of W)
end repeat

or, with a counter:

tell application id "com.apple.systemevents" to set |windows| ¬
        to a reference to every window in every process

repeat with i from 1 to the number of |windows|
        [i, "⏵ ", name of item i in |windows|]
        log (result as text)
end repeat

The output of both scripts is broadly similar, but I’ll use the output of the latter as it includes the index, i, against each window name:


1⏵ osascript -s s -e 't ~/L/M/c/#/key
2⏵ One Thing
3⏵ Remove empty items from a list (get all windows of all processes) - AppleScript - Late Night Software Ltd.
4⏵ Transmission
5⏵ Telegram
6⏵ Photo Booth
7⏵ Finder Preferences
8⏵ CK
9⏵ Downloads

As you can see, provided the collection returned by System Events hasn’t been evaluated, it allows you to iterate through and hence enumerate properties against each object, which you’ll also notice behaves as if it has been flattened.

It is slow because I forced it to evaluate every item and retrieve its name property. The best use for referenced collections is where there isn’t a need to evaluate its contents until the end, such as in testing for an object’s existence. This can be used to recurse through a UI element tree very speedily.


A targeted whose filter

tell application id "com.apple.systemevents" to tell (processes ¬
        where the class of its windows contains window) to get ¬
        [its name, the name of every window]

which, when run against the same set of windows and processes as before, yields this output:

{{"iTerm2", "One Thing", "Arc", "Transmission", 
  "Telegram", "Photo Booth", "Finder"}, {
 {"time osascript -s s  ~/L/M/c/#/key"},
 {"One Thing"},
 {"Remove empty items from a list (get all windows of all processes) - AppleScript - Late Night Software Ltd."},
 {"Transmission"},
 {"Telegram"},
 {"Photo Booth"},
 {"Finder Preferences", "CK", "Downloads"}}}

Execution time: ~12.9s

The above output is a list containing two sub-list items, the first of which are process names, to which items of the second list provide the corresponding list of window names.

Then, to organise this into a record:

use framework "Foundation"
tell application id "com.apple.systemevents" to tell (every ¬
        process where every window's class contains window) ¬
        to get dictionaryWithObjects_forKeys_(get windows's ¬
        name, get its file's name) of my NSDictionary
return the result as record

Output:

{|iterm.app|:{"time osascript -s s  ~/L/M/c/#/key"},
 |telegram.app|:{"Telegram"}, 
 |transmission.app|:{"Transmission"}, 
 |arc.app|:{"Remove empty items from a list (get all 
             windows of all processes) - AppleScript
             - Late Night Software Ltd."},
 |photo booth.app|:{"Photo Booth"}, 
 |finder.app|:{"Finder Preferences", "CK", "Downloads"},
 |one thing.app|:{"One Thing"}}

Execution time: ~13.3s

2 Likes