AppleScript: does it understand that another app has a window open?

Can AppleScript understand that an application that the script’s code is “handling” is busy with something else - busy with exporting for example - and only upon finishing its task to execute a part of the script?

Bare in mind I do not want to use the ‘wait’ command.

I am using tell “System Events” to handle an otherwise non-scriptable application and I would be interested to be able to set a loop when exporting (to perform multiple exports). But I’d need the script to understand that the application has finished an export first before going for the next.

Again, the ‘wait’ command cannot be used in this specific instance.

I guess you could see if it’s active process has stopped doing something by checking it’s PID
then let your script carry on if it has, worth a crack.

tell application "System Events"
	set PID to unix id of process "non-scriptable application"
	set pidIdle to do shell script "ps aux | grep " & PID & " | grep -v grep | awk '{print $3}'"
	set pidIdle to result
	if pidIdle is "0.0" as text then
		-- do something --
	end if
end tell

Hello Budgie

I’m puzzled because when I ran :

tell application "System Events"
	set PID to unix id of process "Photos"
	log PID (*780*)
end tell
set pidIdle to do shell script "ps aux | grep " & PID & " | grep -v grep | awk '{print $3}'"
set pidIdle to result
(*"0.0
0.0
0.0
0.0
0.0"*)

the result was not a single string but a 5 paragraphs one.

During the test, Photos was open but did nothing.

I ran this other one

tell application "System Events"
	set PID to unix id of process "Preview"
	log PID (*825*)
end tell
set pidIdle to do shell script "ps aux | grep " & PID & " | grep -v grep | awk '{print $3}'"
set pidIdle to result
(*"0.5"*)

And although Preview was doing nothing the result was “0.5”

Maybe the fact that English is not my native language is the explanation of my misunderstanding.

Yvan KOENIG running El Capitan 10.11.4 in French (VALLAURIS, France) mercredi 11 mai 2016 12:00:33

That is indeed the BSD way to go however keep in mind we’re talking about applications which is an windowserver object and not a process. A process gets the idle state after 20 seconds and not immediately. So getting the process by it’s state is not really accurate because when the process is doing nothing for 15 seconds it’s still have the active state. Getting the process’s cpu time as you did is returning the time of the user time + system time of the process, however this value is accumulative and is not the value since the last time the process went idle which makes this value hardly useful.

But there are more flaws to this, when the process is waiting for something like an event or reading from an network connection (waiting for the server to give results back over a socket) the process itself can turn into an inactive state while in fact UI of the application is showing it is quite busy. Also running XPC services, which is handled by launchd on behalf of the application, can make the application wait for the process to finish. While in fact the processes is inactive, the application itself is not. This list of exceptions can go on and on.

The best way to see if an application (not a process) is busy is simply by doing the same as the end-user does. Look at the GUI if there are any UI elements be visible, animated or enabled that indicates that there is something going on.

Hi Yvan

I ran your first script and only got back “0.0” not 5 paragraphs.
then I ran your second script and got back “0.0”, even when preview was doing nothing.
wonder why this is happening this way for you and not me.

Hi DJ
Thank you for the explanation, adds a lot of clarity, it appears that checking the UI is definitely
the correct way to go here.

I have no idea about what makes the differences.
Today the code testing Photos (with every window hidden) returned PID = 509 and pidIdle = “0.0
0.0
0.0” (Yes, three paragraphs) Same result with a photo open and the thumbnails disabled.
With the library open it returned “0.0
0.0
0.0
0.0
0.0” ( Yes, five paragraphs )

Preview returned PID = 487 and pidIdle = “0.0
0.0” (Yes, two paragraphs)

So I wouldn’t rely about that to determine the state of an application.

Yvan KOENIG running El Capitan 10.11.4 in French (VALLAURIS, France) jeudi 12 mai 2016 10:24:31

so I guess this would be what is needed

tell application "System Events"
	if exists (window 1 of process "Preview") then
		display dialog "Exists"
	else
		display dialog "Does Not Exist"
	end if
end tell

Hey all.

Many thanks for the reply back. Sorry it took me a while to reply back. New baby in the house so scripting had to take a back seat.

I am currently using the “Accessibility Inspector” off XCode and “UI Browser” to identify scriptable elements in this app I am building.

One of my current issues that I cannot resolve is that:

  1. The scripted app brings up another window (a progress bar window) when it is performing an export.
  2. Once the export has finished, that progress bar window automatically disappears.
  3. The “Accessibility Inspector” sees this window as a new element
  4. I want my script to understand that this window is open and hold the script (delay) until the progress bar window disappears.
  5. In this particular case I cannot - I repeat, I cannot - put a manual delay value, as the exporting time is never fixed and varies depending on numerous factors.

Any ideas would be hugely appreciated!

Thanks

First of all congratulations!

Try to identify the window with something, it can be size, location, name or elements it contains like labels. I have successfully identified windows by it’s elements it contained while the window itself looked anonymous.

Update: here an simple example of an script that waits until the the preference window of the Finder closes within 20 minutes. There are easier ways but it shows how you can identify a window based on it’s contents rather than the window properties itself.

IMPORTANT: update the line if btns contains “Algemeen” and btns contains “Tags” then with the toolbar button names of your preferences window.

set start to current date

repeat
	if not isFinderPreferenceWindowOpen() then exit repeat
	delay 1
	-- timeout after 20 minutes
	if (current date) - start > 1200 then error "Timed out" number -128
end repeat

return "window is closed"

on isFinderPreferenceWindowOpen()
	tell application "System Events"
		tell process "Finder"
			set windowRef to missing value
			repeat with win in every window
				repeat 1 times
					if (count of toolbars of win) = 0 then exit repeat
					set btns to name of buttons of toolbar 1 of win
					-- note "Algemeen" and "Tags" are localized Dutch strings
					if btns contains "Algemeen" and btns contains "Tags" then
						set windowRef to win
					end if
				end repeat
			end repeat
		end tell
	end tell
	
	return windowRef is not missing value
end isFinderPreferenceWindowOpen

Just for the fun, here is a version which is not localization dependent.

It use a trick useful for several applications in which some tables of localized strings aren’t available for English.

set thePath to path to application "Finder"
set theBundle to (thePath as text) & "Contents:Resources:" as alias
set theKey to "Czl-up-T3R.title"
set local1 to my localizeThat(theKey, "PreferencesWindow", theBundle)
--> "En effectuant une recherche :" -- in French
--> "Bij uitvoeren van een zoekopdracht:" -- in Dutch
if local1 = theKey then set local1 to "When performing a search:" -- because the English table doesn't exist

set theKey to "N8C-4e-jxB.title"
set local2 to my localizeThat(theKey, "PreferencesWindow", theBundle)
--> "Afficher toutes les extensions de fichiers" -- in French
--> "Toon alle bestandsnaamextensies" -- in Dutch
if local2 = theKey then set local2 to "Show all filename extensions" -- because the English table doesn't exist

set start to current date

repeat
	if not my isFinderPreferenceWindowOpen(local1, local2, theBundle) then exit repeat
	delay 1
	-- timeout after 20 minutes
	if (current date) - start > 1200 then error "Timed out" number -128
end repeat

return "window is closed"

on isFinderPreferenceWindowOpen(local1, local2, theBundle)
	tell application "System Events"
		tell process "Finder"
			set windowRef to missing value
			repeat with win in every window
				repeat 1 times
					tell win
						if (count of toolbars) = 0 then exit repeat
						set cBoxNames to name of checkboxes
						--> {"Afficher toutes les extensions de fichiers", "Avertir avant de modifier une extension", "Afficher un avertissement avant de supprimer d'iCloud Drive", "Avertir avant de vider la corbeille", "Supprimer les éléments de la corbeille après 30 jours", "Laisser les dossiers en haut lors d'un tri par nom"}
						set sTextNames to name of static texts
						--> {"En effectuant une recherche :", "Préférences du Finder"}
						if cBoxNames contains local2 and sTextNames contains local1 then
							set windowRef to win
						end if
					end tell # win
				end repeat
			end repeat
		end tell
	end tell
	
	return windowRef is not missing value
end isFinderPreferenceWindowOpen

on localizeThat(theKey, theTable, theBundle)
	return localized string theKey from table theTable in bundle theBundle
end localizeThat

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) mercredi 7 décembre 2016 19:07:39

:cool: