I am trying to write a script that should press OK button on every dialog that contains particular static text:
tell application "System Events"
set theProcesses to application processes
repeat with theProcess from 1 to count theProcesses
tell process theProcess
repeat with x from 1 to count windows
set texts to static text of window x
repeat with t from 1 to (count texts)
if (static text t of window x whose value is "Particular text") then
click first button whose value is "OK" of window x
end if
end repeat
end repeat
end tell
end repeat
end tell
This script is not executing. Something is wrong with the if statement.
You have to check
if (value of static text t of window x is "Particular text")
The try block is necessary to ignore the error if an OK button does not exist.
tell application "System Events"
set theProcesses to application processes
repeat with theProcess from 1 to count theProcesses
tell process theProcess
repeat with x from 1 to count windows
set texts to static text of window x
repeat with t from 1 to (count texts)
if (value of static text t of window x is "Particular text") then
try
click (first button of window x whose value is "OK")
end try
end if
end repeat
end repeat
end tell
end repeat
end tell
As an alternative method of doing this—and one that may, in theory, be quicker if you had a large number of processes, windows and text objects to loop through—would be to grab everything all at once. This could be done in one, single line, but breaking it down into separate lines doesn't sacrifice anything in terms of speed and affords a much easier-to-read code block:
tell application "System Events"
set P to a reference to every process
set W to a reference to every window of P
set S to a reference to (static texts of W whose value is "Particular text")
set C to a reference to value of attribute "AXParent" of S
set B to a reference to (buttons of C whose name is "OK")
repeat with _b in B
click _b
end repeat
end tell
Breaking it down with some crude explanations:
P: contains a reference to every process. Because it's a reference to every process, rather than simply the processes themselves, AppleScript doesn't need to expend any energy determining what these processes are just yet. It basically stores the instruction to do this later on within the variable P.
W: is a reference to every window of every process. So far, these two lines are the equivalent of your first two repeat loops, except no computations have been exercised here so far, whereas your script will have gone through (count windows × count processes computations).
S: is a reference to all the static texts matching your required value. For all intents and purposes, this "bypasses" the need to loop through them individually, and just grabs the right ones immediately (in reality, a loop is still performed, but it's performed at a deeper code level, which takes a fraction of the time than any loop performed by AppleScript).
C: contains the parent of the static text, which is the window containing said text. (C stands for container = parent)
B: is then, ultimately, the collection of all the "OK" buttons of every window that contains the specific static text. It's a reference to them, so, again, can just grab them all in one go, avoiding the repeat loops.
Finally, to click the buttons in B, we do our first real bit of computation, and loop through B. However, B only contains the buttons that need to be clicked, so there are no wasted iterations, and hence no need for a try...end try block.
As each button from B is accessed through variable _b, this is where AppleScript throws all of those references together, and performs the necessary (and only the necessary) computations needed to retrieve the buttons, which can equate to as few as one single iteration of each of your repeat loops.
Though this is already a very efficient means of acquiring the buttons you need to click, we can filter the processes and windows to improve it further:
tell application "System Events"
set P to a reference to (processes whose background only is false)
set W to a reference to (windows of P whose subrole is not "AXUnknown" and role is "AXWindow")
set S to a reference to (static texts of W whose value is "Particular text")
set C to a reference to value of attribute "AXParent" of S
set B to a reference to (buttons of C whose name is "OK")
repeat with _b in B
click _b
end repeat
end tell
As I mentioned, the additional advantage is that this requires much less—if any at all—error handling (systems running OS X versions earlier than MacOS 10.12 may need to wrap the repeat loop here in a try block; in MacOS 10.12 and later, this is not needed). The try block was needed in your script in the event that first button of window x whose value is "OK" returned an empty result, which would cause click to throw an error. Here, B only contains the buttons named "OK" (change name to value if needs be), so click will always have something to click.
However, for the sake of robustness and to cater for earlier systems, it's probably good practice to be thoughtful when it comes to error-handling:
try
repeat with _b in B
click _b
end repeat
end try
So it might be worth mentioning that your script may need additional error-handling than just the single try block: once the button "OK" is clicked, my guess is that the dialog box disappears. When this happens, the all the static text objects it contains disappear along with it, which means that another error will be thrown by the incomplete repeat with t loop.
Thanks to #user3439894 for testing the code in OS X 10.8.6.
Related
I am currently using Applescript to calculate the size and position of a window from left to right which works great. Here is an example where I am trying to position of a window as "Left half of the screen"
tell application "System Events"
tell application process "Xcode"
activate
tell window 1 to set size to {(1600) / 2, 814}
tell window 1 to set position to {0, 0}
end tell
end tell
I am trying to work on positioning of the window "Right half of the screen" (technically I can calculate this by setting the X = screenWidth/2 but the issue is, the window of Xcode app some part of the screen is not visible the user where as if I calculate it from right I can ensure the entire window is on the screen visible to the user.
Actual result: https://share.cleanshot.com/COYgKD
Expected result: https://share.cleanshot.com/BIELCA
Goal: Show right half of the window that ensure the entire window is on the screen visible to the user
Any help is appreciated
To obtain your your screen's dimensions:
tell application id "com.apple.finder" to set ¬
[null, null, screenW, screenH] to the ¬
bounds of the desktop's window
XCode is scriptable, I believe, so there's no need to invoke System Events to get or set its size and position. However, scriptable application windows offer this by way of the bounds property just like the desktop's window above:
tell application id "com.apple.Xcode" to tell ¬
window 1 to tell (a reference to its bounds)
set [X, Y, W, H] to its contents
set |𝚫𝓍| to W - screenW
set its contents to [X - |𝚫𝓍|, Y, W - |𝚫𝓍|, H]
end tell
This will right-align the window flush with the right edge of the screen, even if it's already fully visible on your screen. To have it only effect the move if it's required, then insert a condition such that |𝚫𝓍| > 0.
The following is a functionally identical version of the last code block, but utilising statements that are formulated in a more explicit manner:
tell application id "com.apple.Xcode" to tell ¬
the front window to if (it exists) then
set [X, Y, W, H] to the bounds
set |𝚫𝓍| to W - screenW
set the bounds to [X - |𝚫𝓍|, Y, W - |𝚫𝓍|, H]
end tell
I added a check to make sure the front window (which is identical to and equivalent terminology for window 1) exists, so in cases where there are no windows, the script won't throw an error.
Here, the bounds property is explicitly accessed by value whenever we need to retrieve or set its value, compared to the version above where the property was being accessed by reference. In this situation, there's no tangible difference between the two methods.
I'm trying to create script for application which doesn't have dictionary. How to get selected list item?
I need something like this
tell application "System Events"
tell process "process"
get selected item of list 1 of splitter group 1 of window 1
end tell
end tell
_
choose from list list 1... fails with 1700 error (can't convert to string)
Unfortunately the selected property of each of those particular elements (which are static text UI Elements) seems to be inaccessible.
Doing the following:
activate application "SongOfGod_1_2"
--(this line above is just to make sure
-- we don't miss anything by not having the app frontmost;
--for many commands it is unnecessary)
tell application "System Events"
tell process "SongOfGod"
properties of static text 2 of list 1 of splitter group 1 of group 1 of splitter group 1 of window 1
end tell
end tell
returns:
{class:static text, minimum value:missing value, orientation:missing value, position:{595, 259}, accessibility description:"2. And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.", role description:"text", focused:false, title:missing value, size:{1605, 18}, help:missing value, entire contents:{}, enabled:true, maximum value:missing value, role:"AXStaticText", value:missing value, subrole:missing value, selected:missing value, name:missing value, description:"2. And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters."}
You'll notice, if you scroll way out there, that the selected property returns missing value, which means you cannot determine if it is selected. I also tried focused, which doesn't work. Nor do click or select work. Trying to get the selected property of the list containing it also doesn't work.
You apparently cannot get what you want out of this app, I'm sorry to say.
I am beginning to work on a script that involves looking at all currently open windows. I am getting an error and I don't understand why.
tell application "System Events"
set theProcesses to application processes
set allWindows to window of processes whose visible is true
repeat with x from 1 to count allWindows
set Pos to position of allWindows item x
end repeat
end tell
If I understand correctly, allWindows is a list. And yet when I try to compile, I get the error "Syntax Error - Expected end of line but found class name." The error points to the line "set Pos to position of allWindows item x". What is wrong with my code?
Update: nevermind, got it!
There are few issues in your script, not only syntax error, but also incorrect understanding.
1) for the syntax, as mentioned before, you must address 'item x of allWindows' and not 'allWindows item x'
2) the variable allWindows contains a list, but each element is a list of a items.
Example {{window "imac27" of application process "Finder"}, {window "What is wrong with the syntax in this applescript? - Stack Overflow" of application process "Safari"}}
In each item, you have the list of windows for the process. Then, in order to address the window itself, you must address 'item y of item X of allWindows'
3)Item x of the list allWindows could be empty {}. The list contains the list of all windows of visible applications. For instance, Microsoft Excel could be running, but without document open. In this case, Excel is process visible, but list of its window is {}. Also, a process may have many windows open.
In the script bellow, corrections have been made, and it loops through all the windows of each process:
tell application "System Events"
set theProcesses to application processes
set allWindows to window of processes whose visible is true
repeat with x from 1 to count of allWindows -- loop through each process
if item x of allWindows is not {} then -- only if process has visible windows
repeat with Y from 1 to count of item x of allWindows -- loop through each window of the process
set Pos to position of item Y of item x of allWindows
end repeat
end if
end repeat
end tell
The general question from my specific problem is how to interact WITH a running Applescript without leaving the program you a currently in. Its kind of the opposite question of how to USE Applescript to interact with other programs.
I need to go through a lot of web pages and input information from them into a spreadsheet. I have somewhat automated the process with an Applescript that takes an webpage as input and then opens its relevant links one by one, waiting for a click in a dialog box before proceeding to the next.
If you want to run the script, use http://www.resultat.dk/tennis/challenger-singler/champaign/resultater/ as input to the dialog box
--Let the user input the the main webpage
display dialog "Linkzor" default answer ""
set tunering to text returned of result
--get the javascript of the web page
tell application "Safari"
tell window 1
open location tunering
end tell
delay 5
set listen to "" as string
tell front document to set ¬
startText to {name, do JavaScript "window.document.documentElement.outerHTML"}
end tell
set listen to {}
set teksten to startText as string
--Gets all the relevant link variables of the web page
repeat with i from 1 to 500
set forekomst to text (nthOffset(teksten, "g_2_", i) + 4) thru (nthOffset(teksten, "g_2_", i) + 11) of teksten
if nthOffset(teksten, "g_2_", i) = 0 then exit repeat
set punkt to "http://www.resultat.dk/kamp/" & forekomst & "/#kampreferat"
set listen to listen & punkt
end repeat
set lengde to count of listen
--opens the links one by one.
repeat with x from 1 to lengde
tell application "Safari"
tell window 1
set URL of document 1 to item x of listen
end tell
end tell
--THIS IS THE PART I WANT TO INTERACT WITH FROM OUTSIDE THE SCRIPT
set question to display dialog "forsæt?" buttons {"Ja", "nej"} default button 1
set answer to button returned of question
if answer is "nej" then exit repeat
end repeat
on nthOffset(myText, subString, n)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to subString
-- There will be one more text item than there are instances of the substring in the string, so:
if (count myText's text items) > n then
-- There are at least n instances of the substring in the text.
-- The first character of the nth instance comes immediately after the last
-- character of the nth text item.
set o to (count text from text item 1 to text item n of myText) + 1
else
-- There isn't an nth instance of the substring in this text.
set o to 0
end if
set AppleScript's text item delimiters to astid
return o
end nthOffset
Now I would like to automate it a bit further so I didn´t have to leave the spreadsheet to click the dialog box when going to the next webpage. Is there a way to force the Applescript proceed without me actually clicking the dialog box? I have tried to save the script as an application ("hent2") and made another script to sent an "enter" keystroke to it while it is running (and then activate the new script through some kind of hotkey):
tell application "System Events"
tell application "hent2" to activate
key code 36
end tell
It does´t do anything.
Any suggestions on how to proceed? Either with the "sent keystroke to Applescript" or a more elegant solution?
EDIT:
I go it to work with the "Hitting a button" solution from Jerry below.
But I can´t get the handler solution to work. It clearly communicating with the main script because it tells me it can´t find the list "listen" of urls:
"error "hent3 got an error: The variable listen is not defined." number -2753 from "listen""
I define and assign values to "listen" in the part that is autorun when I save and run the script as a program. But when I call the subroutine from another Applescript it knows nothing about "listen"
Here is my attempt:
--Let the user input the the main webpage
display dialog "Linkzor" default answer ""
set tunering to text returned of result
--get the javascript of the web page
tell application "Safari"
tell window 1
open location tunering
end tell
delay 5
tell front document to set ¬
startText to {name, do JavaScript "window.document.documentElement.outerHTML"}
end tell
set listen to {} --HERE I DEFINE THE VARIABLE
set teksten to startText as string
--Gets all the relevant link variables of the web page
repeat with i from 1 to 500
set forekomst to text (nthOffset(teksten, "g_2_", i) + 4) thru (nthOffset(teksten, "g_2_", i) + 11) of teksten
if nthOffset(teksten, "g_2_", i) = 0 then exit repeat
set punkt to "http://www.resultat.dk/kamp/" & forekomst & "/#kampreferat"
set listen to listen & punkt -HERE I ASSIGN VALUES TO IT
end repeat
set lengde to count of listen
set i to 1
on ContinueOnward()
tell application "Safari"
tell window 1
set URL of document 1 to item i of listen --HERE IT ISN`T RECOGNISED
end tell
end tell
set i to i + 1
if i > lengde then
display notification "slut"
end if
end ContinueOnward
on nthOffset(myText, subString, n)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to subString
-- There will be one more text item than there are instances of the substring in the string, so:
if (count myText's text items) > n then
-- There are at least n instances of the substring in the text.
-- The first character of the nth instance comes immediately after the last
-- character of the nth text item.
set o to (count text from text item 1 to text item n of myText) + 1
else
-- There isn't an nth instance of the substring in this text.
set o to 0
end if
set AppleScript's text item delimiters to astid
return o
end nthOffset
Hitting a button
First, when scripting dialogs, if you want to press a button, use System Event’s click button:
tell application "System Events"
tell application process "hent2"
tell window 1
click button "Ja"
end tell
end tell
end tell
This works even if the process being hit is not front-most, so it might be all you need.
Activating via handler
You can also activate handlers in one AppleScript via a tell in another AppleScript. For example, save this as Test Handler, as Application and Stay Open After Run:
on ContinueOnward()
display notification "Continuing Onward"
end ContinueOnward
Then, in Script Editor, write the following script:
tell application "Test Handler"
ContinueOnward()
end tell
You should see a notification pop up saying “Continuing Onward” when you run that script. In your own script, of course, you could use that handler to move on to the next URL.
In order to maintain variables across different handlers, you will need to use either properties or globals or some combination. Properties maintain their values across runs of the application; globals only maintain their values across one run of the application. As I read your needs, you will need globals. In every handler that needs access to the global, use:
global variablename
And you’ll also need to initialize the variable outside of the handler.
For example, this application (Save As Application, Stay Open) will change its notification message, and then quit, after the fourth time:
set currentCount to 0
on ContinueOnward()
global currentCount
set currentCount to currentCount + 1
if currentCount < 5 then
display notification "Continuing with item " & currentCount
else
display notification "Quitting"
quit
end if
end ContinueOnward
In your case, you will want to make i, listen, and lengde be global variables. Put:
global i, listen, lengde
at the top of your handler, as I did with currentCount at the top of the example ContinueOnward handler.
This is an application specific problem. I am trying to find and select a tab in Terminal.app depending on contents within. Here is what I'm doing:
tell application "Terminal"
set foundTabs to (every tab of every window) whose contents contains "selectme"
repeat with possibleTab in foundTabs
try
set selected of possibleTab to true
end try
end repeat
end tell
This isn't acting as expected and is pretty foolproof. I wonder if someone can suggest a way to do this with much less code (for instance, the looping shouldn't really be necessary, but applescript is an elusive language).
Thanks
Thing is, the following Applescript will do what you want, but unless your "selectme" string is very unique, you will find it in many tabs.
But anyway, here you go:
tell application "Terminal"
set allWindows to number of windows
repeat with i from 1 to allWindows
set allTabs to number of tabs of window i
repeat with j from 1 to allTabs
if contents of tab j of window i contains "selectme" then
set frontmost of window i to true
set selected of tab j of window i to true
end if
end repeat
end repeat
end tell