Applescript to remove items from dock - automation

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.

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.

Disable File>Share in Excel with VBA

I need a way to disable the ability for a user to go to (or use) the menu File > Share
I have similar for other save commands like below but need something for this feature as well
Application.CommandBars("Worksheet Menu Bar").Controls("File").Controls("Save As...").Enabled = False
Application.CommandBars("Worksheet Menu Bar").Controls("File").Controls("Save").Enabled = False
I have tried:
Application.CommandBars("Worksheet Menu Bar").Controls("File").Controls("Share").Enabled = False
to no avail
My aim is to stop users saving copies of this file (Hosted on a server), I fully understand Excel isn't meant to be secure and there is always a way to do this but want to make it as hard as I can for the average Joe
Regards
You could use a for each loop to extract the available commands:
' Iterate available commands from the file command bar.
Sub PrintAllCommandBarControlNames()
Dim cbControl As CommandBarControl
For Each cbControl In Application.CommandBars("Worksheet Menu Bar").Controls("File").Controls
Debug.Print cbControl.Caption
Next
End Sub
Run on Excel 2010, I couldn't find a share option. Might just be my system. The above returned:
&New...
&Open...
&Close
&Save
Save &As...
Single Web Page (*.mht)
Save &Workspace...
File Searc&h...
Per&mission...
Per&mission
Ch&eck Out
Ch&eck In...
Ve&rsion History...
We&b Page Preview
Page Set&up...
Prin&t Area
Print Pre&view
&Print...
Sen&d To
Propert&ies
&Recent File Name Goes Here
&Recent File Name Goes Here
Sign ou&t
E&xit Excel
The 'backstage' menu (that you get when you click top left File menu) is effectively part of the Ribbon, and not a Command Bar. If you tried disabling Save for instance, using your example, you'll find they don't work on 2010/2013 etc.
There is this answer which tells you how to manipulate those menu items in the ribbon: Remove "Save & Send" from File menu in Excel 2010 using custom XML and one of the items is called TabShare.

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.

applescript: work "whose" and "exists" into the same line

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.

Send HTML Mail from Cocoa with Mail.app

I'm trying to send html email from Cocoa app, through Mail.app. I want to open new message in Mail.app, include subject, recipient and add HTML Body with links and other content. But can't find the way to do this.
I already tried Scripting Bridge, but MailOutgoingMessage class doesn't have content type i can add content in plaintext.
tried AppleScript, something like this:
set htmlContent to read "/Path/index.html"
set recipientList to {"mail#mail.com"}
tell application "Mail"
set newMessage to make new outgoing message with properties {subject:"qwerty", visible:true}
tell newMessage
make new to recipient at end of to recipients with properties {address:"mail#s.com"}
set html content to htmlContent
--send
end tell
end tell
this code send email with html, only if I'm changing --send to send. But i need to send letter later, after user made some changes.
To recap the problem: Using AppleScript to create a message with HTML content for interactive editing does not work (as of OS X 10.9.2): the new-message form comes up with an empty body.
This should be considered a bug and I encourage everyone to let Apple know at http://bugreport.apple.com - caveat: the html content message class property is not defined in Mail.sdef, Mail.app's AppleScript dictionary, so assigning HTML may not be officially supported.
There is a workaround, but it ain't pretty:
Create the message invisibly.
Save it as a draft.
Open the draft message, at which point the HTML content will appear.
Implementing this robustly is challenging, because several workarounds are required. The following code tries its hardest, though:
Note: Since the code uses GUI scripting, Access for Assistive Devices must be enabled (via System Preferences > Security & Privacy > Accessibility) for the application running this code (e.g., AppleScript Editor or, if run via osascript, Terminal.app).
# Example values; use `read someFile` to read HTML from a file.
set htmlContent to "<html><body><h1>Hello,</h1><p>world.</p></body></html>"
set recipientList to {"person1#example.com", "person2#example.com"}
set msgSubject to "qwerty"
tell application "Mail"
# Create the message *invisibly*, and assign subject text
# as well as the HTML content.
set newMessage to make new outgoing message with properties ¬
{visible:false, subject:msgSubject, html content:htmlContent}
# Add recipients.
# !! Given the workaround below, this is currently pointless.
tell newMessage
repeat with toRcpt in recipientList
make new to recipient at end of to recipients with properties {address:toRcpt}
end repeat
end tell
# Save the current number of drafts messages.
set draftCountBefore to count messages of drafts mailbox
# !! Save the new message as a *draft* - this is necessary
# for the HTML content to actually appear in the message
# body when we open the message interactively later.
save newMessage
# !! Sadly, it takes a little while for the new message
# !! to appear in the drafts mailbox, so we must WAIT.
set newMessageAsDraft to missing value
repeat with i from 1 to 30 # give up after n * 0.1 secs.
set draftCountNow to (count messages of drafts mailbox)
if draftCountNow > draftCountBefore then
set newMessageAsDraft to message 1 of drafts mailbox
exit repeat
end if
delay 0.1 # sleep a little
end repeat
# Abort, if the draft never appeared.
if newMessageAsDraft is missing value then error "New message failed to appear in the drafts mailbox within the timeout period."
# Open the new message as a *draft* message - this ensures that
# the HTML content is displayed and editable in the message body.
# !! The ONLY solution I found is to use `redirect`, which, unfortunately,
# !! *wipes out the recipients*.
# !! It does, however, ensure that the draft is deleted once the message is sent.
redirect newMessageAsDraft with opening window
# Activate Mail.app and thus the draft message's window.
activate
# !! Since the recipients have been wiped out, we need to
# !! add them again - unfortunately, the only way we can do that is to
# !! *GUI scripting* - simulating invocation of a menu command or
# !! sending keystrokes.
tell application "System Events"
# We must make sure that the target window is active before
# we can perform GUI scripting on it.
set newMessageWindow to missing value
repeat with i from 1 to 30 # give up after n * 0.1 secs.
tell (first window of (first process whose frontmost is true) whose subrole is not "AXFloatingWindow")
if name is msgSubject then
set newMessageWindow to it
exit repeat
end if
end tell
delay 0.1 # sleep a little
end repeat
if newMessageWindow is missing value then error "New message failed to become the active window within the timeout period."
# Turn the list of recipients into comma-delimited *string* for pasting into the To field.
set {orgTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {","}}
set recipientListString to (recipientList as text)
set AppleScript's text item delimiters to orgTIDs
# Save the current clipboard content.
set prevClipboardContents to the clipboard
# Cursor is in the "To:" field, so use GUI scripting to send the Edit > Paste command now.
# NOTE: Access for assistive devices must be enabled via System Preferences > Security & Privacy > Accessibility.
set the clipboard to recipientListString
my pasteFromClipboard("")
# Restore the previous clipboard content.
# !! We mustn't do this instantly, as our temporary content may not have
# !! finished pasting yet. It would be non-trivial to determine
# !! when pasting has finished (examining `count of to recipients` doesn't work),
# !! so we take our chances with a fixed, small delay.
delay 0.1
set the clipboard to prevClipboardContents
# Place the cursor in the message *body*.
# !! This works as of Mail.app on OS X 10.9.2, but may break in the future.
try
tell newMessageWindow
tell UI element 1 of scroll area 1
set value of attribute "AXFocused" to true
end tell
end tell
end try
end tell
end tell
(*
Pastes form the clipboard into the active window of the specified application (process) using GUI scripting
rather than keyboard shortcuts so as to avoid conflicts with keyboard shortcuts used to invoke this handler.
Specify "" or `missing value` to paste into the currently active (frontmost) application.
The target process may be specified by either name or as a process object.
CAVEAT: While this subroutine IS portable across *UI languages*, it does make an assumption that will hopefully hold for
all applications: that the "Edit" menu is the *4th* menu from the left (Apple menu, app menu, File, Edit).
Examples:
my pasteFromClipboard("") # paste into frontmost app
my pasteFromClipboard("TextEdit")
*)
on pasteFromClipboard(targetProcess)
tell application "System Events"
if targetProcess is missing value or targetProcess = "" then
set targetProcess to first process whose frontmost is true
else
if class of targetProcess is text then
set targetProcess to process targetProcess
end if
-- Activate the application (make it frontmost), otherwise pasting will not work.
set frontmost of targetProcess to true
end if
tell menu 1 of menu bar item 4 of menu bar 1 of targetProcess
-- Find the menu item whose keyboard shortcut is Cmd-V
set miPaste to first menu item whose value of attribute "AXMenuItemCmdChar" is "V" and value of attribute "AXMenuItemCmdModifiers" is 0
click miPaste
end tell
end tell
end pasteFromClipboard
It isn't clear what your are looking for, but I'll do my best to offer some help.
If you leave send commented, then the message should already be open in Mail.app, waiting for further editing and sending.
By adding the line save newMessage, it will be saved to the drafts folder. The user can open it and continue editing whenever they please. If you want to actually send the draft from your application, use:
set sendMessage to first message of drafts mailbox
send sendMessage
Good luck!
I didn't see that you needed to edit the message before sending, so my previous answer was wrong. This time it should be correct.
It basically
takes a preformatted RTF file,
renders it & puts it into the clipboard,
creates a new message,
fills in the fields,
moves the focus to the message body,
pastes the formatted clipboard
Here is the code:
set textSubject to "HTML Test"
set toAddress to "john.doe#gmail.com"
set toName to "John Doe"
tell application "Mail"
do shell script "cat ~/Documents/RTF\\ File.rtf | textutil -stdin -stdout -convert rtf | pbcopy"
set refMessage to make new outgoing message with properties {name:toName, address:toAddress, subject:textSubject, visible:true}
tell refMessage
make new to recipient at end of to recipients with properties {name:toName, address:toAddress}
end tell
end tell
tell application "System Events"
tell application process "Mail"
set frontmost to true
set value of attribute "AXFocused" of scroll area 4 of window textSubject to true
end tell
keystroke "v" using {command down}
end tell
Again, this worked fine on Snow Leopard
Hope that helped.