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
Related
I am encountering an index error that appears when the app in use has an overlay or notification appear. To provide a better description, the app will occasionally show an alert if something needs to be acknowledged or dismissed. When that happens, the script is unable to return the value from the designated location in the GUI, and returns the following error message: "Can’t get group 4 of toolbar 1 of window 1 of process "App I'm Using". Invalid index.System Events got an error: Can’t get group 4 of toolbar 1 of window 1 of process "App I'm Using". Invalid index. (-1719)"
The behavior is expected, but I would like to adjust the script to where it will either delay trying again for 30 seconds or so, or just not display said error at all.
I've been toying around with using an 'on error' statement, but I can't get it to take with the 'tell' statement that it's referring to, for example:
on error error_message number error_number
if error_number = -1719 then
wait 30
end if
I'm unsure of how I can use the 'on error' function with the section of the script below, but if I can make it try again in 30 - 45 seconds without displaying an error, it would be perfect.
on idle
-- Update the status item's text here.
tell application "System Events"
if not (exists process appName) then
display alert "Application " & appName & " is not running" as warning giving up after 6
quit me
end if
tell process appName
tell first window's first toolbar's fourth group's first group's first menu button
set activityState to first item of (value as list) as text
end tell
end tell
end tell
end idle
I believe the error is encountered when the script reaches "tell window's first toolbar's fourth group's..." before it is supposed to "set activityState to first item...".
I have used the 'on error' function with 'try' statements successfully, but I'm having issues moving forward with this one.
Or you can try this approach which will remain in the repeat loop until first window's first toolbar's fourth group's first group's first menu button becomes available.
on idle
-- Update the status item's text here.
tell application "System Events"
if not (exists process appName) then
display alert "Application " & appName & " is not running" as warning giving up after 6
quit me
end if
tell process appName
repeat until exists of first window's first toolbar's fourth group's first group's first menu button
delay 0.2
end repeat
tell first window's first toolbar's fourth group's first group's first menu button
set activityState to first item of (value as list) as text
end tell
end tell
end tell
end idle
You can’t split statements (such as if or tell). The try statement needs to wrap around the complete statement(s) you want it to work with, for example, the statement telling the app process:
on idle
tell application "System Events"
if not (exists process appName) then
---
end if
try
tell process appName
---
end tell
on error errmess number errnum
return 30 -- try the idle handler again in 30 seconds
end try
end tell
end idle
You can use the exists keyword to check if an element is present before trying to access it. exists won't through an error if the element isn't there, and will let you skip over the problematic lines:
on idle
-- Update the status item's text here.
tell application "System Events"
if not (exists process appName) then
display alert "Application " & appName & " is not running" as warning giving up after 6
quit me
end if
tell process appName
-- assuming the window and toolbar are always going to be there
try
tell first window's first toolbar
-- check to see if the UI element exists
if exists fourth group's first group's first menu button then
-- only get the activity state if it does
tell fourth group's first group's first menu button
set activityState to first item of (value as list) as text
end tell
end if
end tell
on error errstr
(*
this code is in an 'idle' handler, so on any error we
just return 30 to idle for another 30 seconds and try again.
*)
return 30
end try
end tell
end tell
end idle
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.
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.
I have two monitors set up and I am trying to position the window of an application in the second monitor but nothing I do seems to work. For example I am using my laptop and the terminal window is maximized on the screen. Then I plug in an external monitor. I then want to run the applescript and have the terminal maximize on the larger second monitor.
Here is what I have right now:
set monitorTwoPos to {1050, -600}
set monitorTwoSze to {1200, 1920}
tell application "Microsoft Outlook"
set position of window 1 to monitorTwoPos
set size of window 1 to monitorTwoSze
end tell
Here is the error I get:
/Users/vcutten/AppleScripts/SpacesWork.scpt:1291:1332: execution error:
Microsoft Outlook got an error: Can’t make position of window 1 into type specifier. (-1700)
I'm pretty sure I'm just using set position and set size completely wrong :( When I used bounds it kind of works...
Bonus Question:
How can I loop through the open windows and get their size? Thanks!
What have you tried?
I think to solve this you need to calculate the screen size and coordinates of the second monitor. For example, your main monitor starts at position {0,0}. So the starting position of the second monitor has to be something different and you need to find that. Luckily I have written a tool that will give you both the starting coordinates and screen size of your monitors. Once you have the size and position then it's simple. System events can set the size and position of a window so you could do something like this...
set monitorSize to {800, 600}
set monitorPosition to {-800, 0}
tell application "System Events"
tell process "Terminal"
set frontWindow to first window
set position of frontWindow to monitorPosition
set size of frontWindow to monitorSize
end tell
end tell
So from the above script you just need the size and position variables. You can get my tool here called hmscreens which will give you those. You may need to do some adjusting of the coordinates depending on if the screen is measured from the lower left corner or upper left, but that's just simple math.
I hope that helps...
Use bounds instead of position, it works. You can get bounds of the window like this:
tell application "Microsoft Outlook"
get bounds of first window
end tell
Answer to the bonus question:
tell application "Microsoft Outlook"
repeat with nextWindow in (get every window)
get bounds of nextWindow
end repeat
end tell
If you open Replies tab at bottom part of Applescript editor, you will see all get results.
Hope it helps.
Here is a script that handles saving and restoring size and postion for multiple display configurations. It may have some issues with fullscreen apps but it seems to work ok.
-- allSettings is a list of records containing {width:? height:? apps:{{name:? pos:? size:?},...}
-- for each display setup store the apps and their associated position and size
property allSettings : {}
-- create a variable for the current settings
set currentSettings to {}
display dialog "Restore or save window settings?" buttons {"Restore", "Save"} default button "Restore"
set dialogResult to result
tell application "Finder"
-- use the desktop bounds to determine display config
set desktopBounds to bounds of window of desktop
set desktopWidth to item 3 of desktopBounds
set desktopHeight to item 4 of desktopBounds
set desktopResolution to desktopWidth & "x" & desktopHeight
-- find the saved settings for the display config
repeat with i from 1 to (count of allSettings)
if (w of item i of allSettings is desktopWidth) and (h of item i of allSettings is desktopHeight) then
set currentSettings to item i of allSettings
end if
end repeat
if (count of currentSettings) is 0 then
-- add the current display settings to the stored settings
set currentSettings to {w:desktopWidth, h:desktopHeight, apps:{}}
set end of allSettings to currentSettings
--say "creating new config for " & desktopResolution
else
--say "found config for " & desktopResolution
end if
end tell
tell application "System Events"
if (button returned of dialogResult is "Save") then
say "saving"
repeat with p in every process
if background only of p is false then
tell application "System Events" to tell application process (name of p as string)
set appName to name of p
if (count of windows) > 0 then
set appSize to size of window 1
set appPosition to position of window 1
else
set appSize to 0
set appPosition to 0
end if
set appSettings to {}
repeat with i from 1 to (count of apps of currentSettings)
if name of item i of apps of currentSettings is name of p then
set appSettings to item i of apps of currentSettings
end if
end repeat
if (count of appSettings) is 0 then
set appSettings to {name:appName, position:appPosition, size:appSize}
set end of apps of currentSettings to appSettings
else
set position of appSettings to appPosition
set size of appSettings to appSize
end if
end tell
end if
end repeat
end if
if (button returned of dialogResult is "Restore") then
if (count of apps of currentSettings) is 0 then
say "no window settings were found"
else
say "restoring"
repeat with i from 1 to (count of apps of currentSettings)
set appSettings to item i of apps of currentSettings
set appName to (name of appSettings as string)
try
tell application "System Events" to tell application process appName
if (count of windows) > 0 then
set position of window 1 to position of appSettings
set size of window 1 to size of appSettings
end if
end tell
end try
end repeat
end if
end if
end tell
https://gist.github.com/cmackay/5863257
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