applescript: work "whose" and "exists" into the same line - error-handling

My problem can best be demonstrated with one line of code:
tell application "System Events" to tell application process "Dock" ¬
to tell list 1 to first UI element whose value of attribute "AXTitle" ¬
is "Trash"
This ends in error because not every UI element in Dock has attribute "AXTitle". dock separator item only has AXRole, AXRoleDescription, etc.
I want to know if there is a way to have the code return the correct UI element despite this.
Here is what I've tried and failed:
1) try block: This simply jump over this line of code and continue to next line
2) ignore application response block: Ditto.
3) exists(attribute "attributeName"): I was able to test each individual UI element with e.g. exists (attribute "AXTitle") of UI element 1, but I cannot works exists into whose statement: It should look something like this:
UI elements whose (exists (attribute "AXTitle") is true)
And that doesn't work. Right now I have to run a repeat with loop, a if statement inside, and an exit repeat so that I can cycle through everything. This is cumbersome.
There has to be a better way.
Clarification: A few people showed me more elegant ways to get to Trash. I used Trash as an example but meant the question to be broarder, namely how to find the first item of a list based on an attribute when a item in the list lacks this atrribute. Another example would be:
delay 5
tell application "System Events" to tell application process "Dock" ¬
to tell list 1 to first UI element whose value of attribute ¬
"AXSelected" is true
And move cursor to any item in the Dock. This example failed because again Dock Separator doesn't have the common field "AXSelected".

Just add another condition like this : whose subrole is not "AXSeparatorDockItem"
tell application "System Events"
tell application process "Dock" to tell list 1 to (first UI element whose subrole is not "AXSeparatorDockItem" and its selected is true)
end tell
--
Update : You can use the title property instead of value of attribute "AXTitle", this will not give error.
tell application "System Events"
tell application process "Dock" to tell list 1 to UI elements whose title is "Trash"
end tell

This worked for me, and is also better, since the title of the "Trash" is localized, here it is "Papirkurv". :)
tell application "System Events" to tell process "Dock"
tell list 1
get first UI element whose value of attribute "AXSubrole" is "AXTrashDockItem"
end tell
end tell
If you got Xcode installed, then you should have an app named UIElementInspector, which lets you read off the different values of the UI Elements.

Related

Scripting Safari to open 10 URLs and then rotate through through then about every 15 seconds

I need to be able to open roughly 10 URLs in Safari and then rotate through them, pausing on each for about 15 seconds. I'd also like the Safari window to be maximized.
I tried this with a simple Javascript, but it causes the windows to refresh every time and that's distracting. So, I think AppleScript with Safari would be a "cleaner" approach
Here's what I've started with:
tell application "Safari"
activate
tell window 1
make new tab with properties {URL:"http://news.yahoo.com"}
make new tab with properties {URL:"http://news.google.com"}
make new tab with properties {URL:"http://www.macintouch.com"}
set current tab to tab 1
set numTabs to number of tabs
end tell
activate
tell window 1
repeat with j from 1 to (count of tabs of window 1) by 1
set current tab to j
activate
delay 5
end repeat
end tell
end tell
This AppleScript code works for me using the latest version of macOS Mojave.
Be sure to fill in the remaining 7 URL's. I think this following code achieves what you are looking for.
tell application "Safari"
activate
if not (exists of window 1) then make new document
tell window 1
set startingTabForRotating to make new tab with properties {URL:"http://news.yahoo.com"} -- URL 1
make new tab with properties {URL:"http://news.google.com"} -- URL 2
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
--make new tab with properties {URL:"something.com"}
set endingTabForRotating to make new tab with properties {URL:"http://www.macintouch.com"} -- URL 10
set current tab to startingTabForRotating
delay 1
tell application "System Events"
if exists of UI element 3 of window 1 of application process "Safari" then
click UI element 3 of window 1 of application process "Safari" -- Maximizes Safari
end if
end tell
repeat until current tab is endingTabForRotating
delay 15
set current tab to tab ((index of current tab) + 1)
end repeat
end tell
end tell
I've taken a slightly different approach to #wch1zpink. In the script below, you can edit and add to the property URLs, which will define what tabs to open whilst keeping them collected at the top of the script and separate from the main body of the code. You can supply nonsense URLs if you wish, as these are dealt with gracefully by the script.
I've also elected to maximise the window rather than go fullscreen, but if you prefer fullscreen, then this is easily done and I've included a snippet at the end to demonstrate a clean way to achieve this.
property URLs : {"https://google.com", ¬
"https://dropbox.com", ¬
"https://imdb.com", ¬
"https://bbc.co.uk", ¬
"foobar blah blah", ¬
"https://disney.com"}
property errorPage : "safari-resource:/ErrorPage.html"
property time : 15 -- time (seconds) between tab switches
tell application "System Events" to set screenSize to ¬
the size of scroll area 1 of process "Finder"
tell application "Safari"
make new document with properties {URL:the first item in the URLs}
tell the front window
set W to it
repeat with URL in the rest of the URLs
make new tab with properties {URL:URL}
end repeat
set the bounds to {0, 0} & the screenSize
activate
tell (a reference to the current tab) to repeat
delay my time
if not (W exists) then return
set N to the number of tabs in W
try
set contents to tab (index mod N + 1) in W
end try
if its URL = errorPage then close
end repeat
end tell
end tell
The other property you can set is time, which defines how long (in seconds) to pause on each tab, presently set to 15 seconds. The tabs rotate in turn, but do so on a continuous loop until you close the window. Any tabs with nonsense URLs are immediately closed just before coming into focus, so the transition to the next sensible tab is actually seamless and you won't notice the closures.
You can still open new tabs as you please (within the 15-second window), and you can switch to whichever tab you desire manually. The progression will continue from whichever tab you select acting as the new starting point.
Fullscreen
Currently, the script sets the size of the window to the maximum area available on your screen which, in modern versions of macOS, should take into account the menu bar and dock without obscuring them.
If you prefer a fullscreen mode, then you would replace this line:
set the bounds to {0, 0} & the screenSize
with this:
tell application "System Events" to set value of attribute ¬
"AXFullScreen" of front window of process "Safari" to true
Strictly speaking, you ought to try and sit this line outside of the tell application "Safari" block, by splitting the Safari block into two; coming out of the first block to enact the fullscreen mode; then re-entering the second block. But Safari won't die if you don't do this, so a simple cut-and-paste that exchanges one line for the other will suffice, as long as you appreciate that this would be considered lazy and poor form.

Applescript to remove items from dock

I'm trying to remove (all) items from the dock. I can remove them by name like so:
tell application "System Events"
tell UI element "Launchpad" of list 1 of process "Dock"
perform action "AXShowMenu"
click menu item "Remove from Dock" of menu 1
end tell
end tell
But I'd like to pull the list of current items and iterate over them.
This stack overflow question seems to cover how to get the list. What I'd like to do is tweak the above code to operate inside of a loop. I would guess that referencing the current item of the list inside the loop would be done with "thisRecord". I think I'm misunderstanding how to convert "thisRecord" into something I can reference within system events.
set plistpath to (path to preferences folder as text) & "com.apple.dock.plist"
tell application "System Events"
set plistContents to contents of property list file plistpath
set pListItems to value of plistContents
end tell
set persistentAppsList to |persistent-apps| of pListItems
set dockAppsList to {}
repeat with thisRecord in persistentAppsList
set end of dockAppsList to |file-label| of |tile-data| of thisRecord
tell application "System Events"
tell UI element application thisRecord
perform action "AXShowMenu"
click menu item "Remove from Dock" of menu 1
end tell
end tell
end repeat
As an alternative... Here is a much more straight forwards approach to removing the persistent apps on the Dock found in the persistent-apps key of the com.apple.dock.plist file:
In Terminal, do the following to first backup the target file:
cd ~/Library/Preferences
cp -a com.apple.dock.plist com.apple.dock.plist.bak
Now to remove the persistent apps, use the following compound command:
defaults delete com.apple.dock persistent-apps; killall Dock
If later you want to restore the backup, use the following compound command:
cd ~/Library/Preferences; rm com.apple.dock.plist; cp -a com.apple.dock.plist.bak com.apple.dock.plist; killall Dock
If for some reason you need to do this with AppleScript, you can use the do shell script command to run these shell commands.
Note: In your OP you stated "I'm trying to remove (all) items from the dock." and the code you've presented only focuses on the apps stored under the persistent-apps key. There are also additional items that can show on the Dock, the first being the default persistent-others, which has the Downloads stack and other items you've added to that section. Then with macOS Mojave there is recent-apps which shows between the two aforementioned sections (by key name) on the Dock. The same premise can be use on these keys as well, substituting persistent-others or recent-apps for persistent-apps in the defaults delete ... compound command.
It would probably be wise to backup your "com.apple.dock.plist" file first. These following two lines of AppleScript code will copy your current com.apple.dock.plist file to your Desktop. This will come in handy if you want to revert your Dock icons back to the way they were before you ran the second script of this post.
set plistpath to (path to preferences folder as text) & "com.apple.dock.plist"
tell application "Finder" to duplicate alias plistpath to desktop
This AppleScript code works for me using the latest version of macOS Mojave.
set plistpath to (path to preferences folder as text) & "com.apple.dock.plist"
tell application "System Events"
set plistContents to contents of property list file plistpath
set pListItems to value of plistContents
end tell
set persistentAppsList to |persistent-apps| of pListItems
set dockAppsList to {}
-- Gets app names and adds them to dockAppsList
repeat with i from 1 to count of persistentAppsList
set thisItem to item i of persistentAppsList
set appName to |file-label| of |tile-data| of thisItem
set end of dockAppsList to appName
end repeat
-- Loops through each app in dockAppsList and removes each app from Dock
repeat with thisRecord in dockAppsList
tell application "System Events"
tell UI element thisRecord of list 1 of process "Dock"
try
perform action "AXShowMenu"
click menu item "Options" of menu 1
click menu item "Remove from Dock" of menu 1 of menu item "Options" of menu 1
on error
try
perform action "AXShowMenu"
click menu item "Remove from Dock" of menu 1
end try
end try
end tell
end tell
end repeat
I realize I could have included everything in one large repeat loop. I thought it would be better ,for purposes of this script, to separate the two looping events in the event that somewhere else in your script you may want to refer back to items of dockAppsList so instead of "removing all from the dock" you may only want to remove items 1 through 5 of dockAppsList from the dock.

Get NSTextFieldCell value using AppleScript

I am trying to automate a few tasks where I need to get the text displayed on a dialog box (the 6 digit code).
Accessibility Inspector revealed the following hierarchy:
I am a beginner in the field of AppleScript and have read a few examples where something similar could be done like this:
set myText to textField's stringValue() as text
But I'm not sure if this could work in my case, as the Accessibility Inspector does not show any variable names for NSTextFieldCell which contains the 6 digit code.
How can I extract the 6 digit code in the NSTextFieldCell and possibly return this value so that a shell script can use this code?
I have something like this right now -
tell application "FollowUpUI"
activate
# get the 6 digit code
end tell
Update
After some help, i have tried to traverse to the text field
tell application "System Events"
repeat with theProcess in processes
#initialize
tell theProcess
set processName to name
set allWindows to windows
end tell
#check if process exists
if processName is "FollowUpUI" then
activate
say "FollowUpUI found"
set windowsCount to count of the allWindows
#only one window should exist
if windowsCount is 1 then
say "1 window was found"
tell window 1
tell group 1
tell text field 1
set code to value
end tell
end tell
end tell
end if
end if
end repeat
end tell
but i've got stuck because of an error -
System Events got an error: cant get window 1. Invalid index.
I am not sure if this is a syntax error. Any pointers would be helpful. Thanks
The error occurs because you have to reference window 1 of process "FollowUpUI"
Your code is too complicated, you just need to check if the process exists
tell application "System Events"
if exists process "FollowUpUI" then
tell process "FollowUpUI"
tell window 1
tell static text 1 of group 1
set code to its value
end tell
end tell
end tell
end if
end tell
If the code is a part of a workflow you have to wait until the window is open
tell application "System Events"
repeat until exists window 1 of process "FollowUpUI"
delay 0.2
end repeat
tell window 1 of process "FollowUpUI"
tell static text 1 of group 1
set code to its value
end tell
end tell
end tell
I added its before value to make sure that it refers to the current reference (the text field)
As you don't send key or mouse events to the window you don't need to activate anything.
Not an answer but I can't paste code in a comment. Here's a trick to get the UI Element. Hit Command-Shift-4 to get a cursor with coordinates. Aim at the UI Element and remember the coordinates. Click to get the arrow cursor back. Use the coordinates in this script.
activate application "FollowUpUI"
tell application "System Events"
tell application process "FollowUpUI"
click at {290, 150}
end tell
end tell
Script Editor must be allowed to control the computer. You can set this in the System prefs, Security & Privacy, Privacy.

Save a user popup selection in a custom Automator Action

Using Xcode 5.* for a cocoa-applescript automator action.
Interface is a a simple popup menu that gets populated using an outlet with:
tell thePopupMenu to removeAllItems()
tell thePopupMenu to addItemsWithTitles_(theList)
When the action is used in a workflow (a Service actually), I want the next time it is run and the action dialog shows up (I will have "Options:Show when run" selected), I want the popup menu to change the selection to the last one that was selected. Right now, the default first item shows, even though last time it was run, the user selected a different item in the popup menu.
My thought was that I need to capture a change in the popup menu with a Sent Action handler, and then set some type of default. I have a working handler:
on thePopupMenuSentAction_(sender)
set popupValue to (popupSelectedValue of my parameters()) as string
-- save this selection somewhere???
end
What's the right way to save this? Do I use User Defaults? My Bindings are currenly all tied through Parameter object/controller. If I should use User Defaults, can someone give example code for setting up User Defaults, and then how to get and set a new value using Cocoa-Applescript?
If I can get the name string of the menu item saved somewhere, I can get the string and then change the selection of the popup menu in the
on opened {}
-- set up the action interface
end
handler which gets called just before the action is displayed each time.
Thanks for any help,
Joe
I did mine a bit differently. I will assume you are referring to what XCode calls a "pop up button" (somewhat misleading). I did not use the parameters at all, although that is probably better for larger projects. Have a look at the code:
script Insert_Picture_into_Powerpoint_Slide_Show
property parent : class "AMBundleAction"
property menuChoices : {"Insert a Beginning", "Insert at End"}
property menuSelection : missing value
if (menuSelection as string) is "missing value"
set menuSelection to 0 as integer -- sets default value
end if
end script
I bound Content Values to File's Owner and under Model Key Path I put menuChoices.
Then you just bind the Selected Index to File's Owner and for the Model Key Path type menuSelection.
Defaults
On the initial run, if the user does not click anything, the menuSelection will be missing value. Because I couldn't find a way around this, I created a conditional which tests for this and sets it to the first choice by default (the one that is shown when you add the action).
When the user selections one of the menu choices, the choice is remembered on sequential runs.

How to close all, or only some, tabs in Safari using AppleScript?

I have made a very simple AppleScript to close tabs in Safari. The problem is, it works, but not completely. Only a couple of tabs are closed. Here's the code:
tell application "Safari"
repeat with aWindow in windows
repeat with aTab in tabs of aWindow
if [some condition is encountered] then
aTab close
end if
end repeat
end repeat
end tell
I've also tried this script:
tell application "Safari"
repeat with i from 0 to the number of items in windows
set aWindow to item i of windows
repeat with j from 0 to the number of tabs in aWindow
set aTab to item j of tabs of aWindow
if [some condition is encountered] then
aTab close
end if
end repeat
end repeat
end tell
... but it does not work either (same behavior).
I tried that on my system (MacBook Pro jan 2008), as well as on a Mac Pro G5 under Tiger and the script fails on both, albeit with a much less descriptive error on Tiger.
Running the script a few times closes a few tab each time until none is left, but always fails with the same error after closing a few tabs. Under Leopard I get an out of bounds error. Since I am using fast enumeration (not using "repeat from 0 to number of items in windows") I don't see how I can get an out of bounds error with this...
My goal is to use the Cocoa Scripting Bridge to close tabs in Safari from my Objective-C Cocoa application but the Scripting Bridge fails in the same manner. The non-deletable tabs show as NULL in the Xcode debugger, while the other tabs are valid objects from which I can get values back (such as their title). In fact I tried with the Scripting Bridge first then told myself why not try this directly in AppleScript and I was surprised to see the same results.
I must have a glaring omission or something in there... (seems like a bug in Safari AppleScript support to me... :S) I've used repeat loops and Obj-C 2.0 fast enumeration to iterate through collections before with zero problems, so I really don't see what's wrong here.
Anyone can help?
Thanks in advance!
I have a script that closes all of the tabs but does not need a repeat loop.
set closeTab to "Stack Overflow" as string
tell application "Safari"
close (every tab of window 1 whose name is not equal to closeTab)
end tell
See if that works for you.
Note: change "Stack Overflow" to whatever the title name is of
the tab you want to stay open.
this works for me nice and simple
tell application "Safari"
close every window
end tell
ok you have to go from the count to 1 otherwise the count will be off when you close the window
tell application "Safari"
repeat with i from (count of windows) to 1 by -1
repeat with j from (count of tabs of window i) to 1 by -1
set thistab to tab j of window i
set foo to name of thistab
if foo is not equal to "bar" then close thistab
end repeat
end repeat
end tell
Both provided answers are fine, but I think it is better to combine both. This way you will be closing all tabs of all chrome windows, and it is less verbose than the first answer:
set closeTab to "Post Attendee" as string
tell application "Google Chrome"
repeat with i from (count of windows) to 1 by -1
close (every tab of window i whose name is not equal to closeTab)
end repeat
end tell