Add selected hyperlink to Safari reading list using AppleScript - applescript

I am trying to write an AppleScript service to add a selected hyperlink to Safari reading list. For bare URLs this is easy. For instance, below is my service that receives selected URLs (input is "only URLs"):
on run {theUrl}
using terms from application "Safari"
tell application "Safari"
add reading list item theUrl as string
end tell
end using terms from
return theUrl
end run
However, this apparently does not work if the selected text is hyperlinked (but not bare URL), e.g., StackOverflow. Is there a way to make the service also work for hyperlinked texts? Thanks.

Sadly, there is no good solution, but you could try the following approach (rough outline):
Make your service accept text as input - this will enable it even when a hyperlink is selected (though it will effectively enable for any text)
Instead of processing the text passed in, send a copy-to-clipboard command to the active application (either by sending a keystroke or preferably through GUI scripting). (Note that even a service accepting rich text only passes plain text to the service handler).
Obtain the data as source text from the clipboard using the trick below - sadly, AS won't let you handle RTF or HTML natively.
Extract the URL from the source text and add it to the reading list.
To get RTF or HTML data from the clipboard some hacking is required (thanks, https://stackoverflow.com/a/2548429/45375):
set html to do shell script "osascript -e '«class HTML» of (the clipboard as record)' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))'"
set rtf to do shell script "osascript -e '«class RTF » of (the clipboard as record)' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))'"
You can parse the result with a regex to extract the URL.
Note that some apps put only RTF on the Clipboard (Safari), others put both HTML and RTF (Chrome).
If you're willing to tackle this, let me know if you need help with specific steps. Good luck.
Update: As requested, code for invoking the frontmost application's copy-to-clipboard command:
Simple, but not the most robust: Send ⌘-C (the issue is that if another key happens to be held down by the user while the keystrokes are sent, it can interfere):
tell application "System Events" to keystroke "c" using command down
Robust alternative: Use GUI scripting to invoke a menu command
The only caveat is that access for assistive devices must be enabled via System Preferences, up to 10.8, where this was a single global switch, this could be initiated from a script (with an admin password required to allow it; from 10.9, unfortunately, each individual application using GUI scripting must be authorized, which requires quite a bit of manual intervention from the end user - it's a one-time pain, though).
my copyToClipboard()
(*
# Copies the frontmost application's current selection to the clipboard
# using GUI scripting rather than keyboard shortcuts so as to avoid conflicts
# (with keys held down by the user while the script is sending keystrokes).
# CAVEAT: While this subroutine IS portable across *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).
*)
on copyToClipboard()
tell application "System Events"
tell menu 1 of menu bar item 4 of menu bar 1 of (first process whose frontmost is true)
-- Find the menu item whose keyboard shortcut is Cmd-C
set miCopy to first menu item whose value of attribute "AXMenuItemCmdChar" is "C" and value of attribute "AXMenuItemCmdModifiers" is 0
click miCopy
end tell
end tell
end copyToClipboard
Finally, here's a subroutine for ensuring that access for assistive device is enabled; works both on 10.9 and before:
On 10.8 and earlier, it just triggers an admin-authorization prompt and, if authorized, continues to run.
On 10.9, the best it can do is to pop up a dialog with instructions and open the relevant System preferences pane - the rest is up to the user. Even if the user manages to authorize the app, though, it must be restarted for things to start working.
# Example use.
try
my ensureAssistiveAccess()
on error
# Exit quietly, relying on the prompt to have provided
# sufficient information.
return
end try
# Tries to ensure that access for assistive devices is turned on so as to enable GUI scripting.
# - Up to 10.8.x, access can be turned on programmatically, on demand - via an admin authorization prompt.
# - From 10.9, the best we can do is display a GUI prompt, then open System Preferences to the relevant pane, then exit, telling the user to try again after interactively enabling access.
# Returns:
# Only returns if access is already enabled; throws an error otherwise.
# Example:
# try
# my ensureAssistiveAccess()
# on error
# # Enabling failed (10.8-: user didn't provide admin password; 10.9: user may or may not have authorized
# # this script's app as instructed by the prompt, but either way the script must be restarted.
# # Exit quietly, relying on the prompt to have provided sufficient information.
# return
# end try
on ensureAssistiveAccess()
local ok, isPreMavericks, verOs, verMajor, verMinor, appName, errMsg
# Determine if access is currently enabled.
tell application "System Events" to set ok to UI elements enabled
if not ok then
# See if we're running 10.8 or below
set {orgTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {"."}}
set verOs to system version of (system info)
set verMajor to first text item of verOs as number
set verMinor to second text item of verOs as number
set AppleScript's text item delimiters to orgTIDs
set isPreMavericks to verMajor ≤ 10 and verMinor < 9
if isPreMavericks then # 10.8-: we can try to turn it on ourselves, which will prompt for authorization
try
# Try to turn it on - will prompt for authorization via admin credentials.
tell application "System Events"
set UI elements enabled to true
set ok to UI elements enabled # Check if the user actually provided the authorization.
end tell
end try
else # 10.9+: we cannot turn it on ourselves, it has to be enabled *interactively*, *per application*.
# Try a dummy GUI scripting operation - which we know will fail - in the hope that this will
# get the app at hand registered in System Preferences > Security & Privacy > Privacy > Accessibility.
# ?? Does this work?
try
tell application "System Events" to windows of process "SystemUIServer"
end try
set appName to name of current application
if appName = "osascript" then set appName to "Terminal" # ?? how can we deal with other apps that invoke `osascript`, such as Alfred?
set errMsg to "You must turn on ACCESS FOR ASSISTIVE DEVICES for application '" & appName & "' (System Preferences > Security & Privacy > Privacy > Accessibility) first, then retry."
try
display dialog errMsg & linefeed & linefeed & "Press OK to open System Preferences now; unlock, if necessary, then locate the application in the list and check it." with icon caution
# We only get here if the user didn't cancel.
# Open System Preferences and show the appropriate pane. (This is the best we can do in guiding the user - further guidance would require the very kind of assistive access we're trying to turn on.)
tell application "System Preferences"
activate
tell pane id "com.apple.preference.security"
reveal anchor "Privacy_Assistive"
end tell
end tell
end try
end if
end if
if not ok then
if isPreMavericks then # This indicates that the authorization prompt was aborted; for 10.9+, errMsg was set above.
set errMsg to "You must turn on ACCESS FOR ASSISTIVE DEVICES first, via System Preferences > Accessibility > Enable access for assistive devices"
end if
error errMsg
end if
end ensureAssistiveAccess

It is annoying that this is not possible, but have you tried using (or is there a reason you aren't using) the right-click (control-click) contextual menu item "Add Link to Reading List" in Safari? I'm guessing you want to add the Service to any application, in which case, AFAIK, you're out of luck.

Related

Trigger an apple script / automator when an application comes to focus in mac

I want to automate the keyboard layout based on the application. I am using an Windows VM over remote I wan't to create a script which can:
Change the Keyboard layout when the application switches to Windows VM
Change it back to normal layout with all other apps.
I have hard time finding a trigger event on application focus change. Is it possible to do it over automator?
Or else kindly suggest another solution to resolve the issue.
Assuming that you only have two keyboard layouts, you can use the following script application.
First, you'll need to open System Preferences and look at Keyboard➔Shortcuts➔Input Sources; see what the what the keyboard shortcut for 'selecting previous input source' is, make sure it's clicked on, and copy it into the script at the marked places. I have mine set to comd-ctrl-space (^⌘ ), but yours is likely different. You should also check that "Windows VM" is the correct name for the app: find the application in the Finder, select it and type ⌘I to open a Get Info window, and look in the Name & Extension text box.
Now do the following
Copy the following script in the Script Editor
Save it as a stay-open application
select 'Application' from the 'Format' menu at the bottom left
click the 'Stay open after run handler' checkbox
Open System Preferences and do the following
add the app to the Accessibility section in Security & Privacy➔Privacy (so it can send keyboard shortcuts)
add the app to the Login Items section for your account in Users & Groups (so it starts automatically at login)
You can then run it and test it out. The app waits in the background to receive notifications that an app has activated or deactivated, then it checks the app name and triggers the keyboard shortcut for the switching the input source when the Window VM app comes or goes.
use AppleScript version "2.4"
use framework "AppKit"
use scripting additions
property NSWorkspace : class "NSWorkspace"
on run
set workSp to NSWorkspace's sharedWorkspace()
set notifCent to workSp's notificationCenter()
tell notifCent to addObserver:me selector:"appHasActivated:" |name|:"NSWorkspaceDidActivateApplicationNotification" object:(missing value)
tell notifCent to addObserver:me selector:"appHasDeactivated:" |name|:"NSWorkspaceDidDeactivateApplicationNotification" object:(missing value)
end run
on idle
-- we don't use the idle loop, so tell the system to check in once an hour
return 3600
end idle
on appHasActivated:notif
set targetApp to (notif's userInfo's valueForKey:"NSWorkspaceApplicationKey")
set targetAppName to (targetApp's localizedName) as text
if targetAppName is "Windows VM" then
tell application "System Events"
tell process "Windows VM"
-- change this line to match your 'Select previous input source' keyboard shortcut
keystroke space using {command down, control down}
end tell
end tell
end if
end appHasActivated:
on appHasDeactivated:notif
set targetApp to (notif's userInfo's valueForKey:"NSWorkspaceApplicationKey")
set targetAppName to (targetApp's localizedName) as text
if targetAppName is "Windows VM" then
tell application "System Events"
tell process targetAppName
-- change this line to match your 'Select previous input source' keyboard shortcut
keystroke space using {command down, control down}
end tell
end tell
end if
end appHasDeactivated:
Once you are satisfied it works, you can run the following command in terminal:
defaults write '/path/to/$name.app/Contents/Info.plist' LSUIElement -bool yes
This will turn the app into a background agent, which never takes the foreground and is invisible in the dock. That way you can just forget about it.

Restore windows by application name (within a function) in AppleScript?

I have a function where I pass in the name of an application. Within the function, one of the things I'd like to do is restore the windows of the application:
on test(applicationName)
-- do some work
-- restore all windows
-- do some more work
end test
I've found references on how to restore the windows of an application by setting the miniaturized property, ala:
tell application "Maps"
set miniaturized of windows to false
end tell
(see Un-minimizing an app with Applescript)
But this requires one to specify the name of the app at compile time - I have to hard code the name of the app into the code - I can't use "tell application applicationName" even though applicationName is a string:
on test(applicationName)
-- do some work
-- restore all windows
tell application applicationName
set miniaturized of windows to false
end tell
--- do some more work
end test
(see tell application - string vs. string?)
Is it possible to restore the windows of an application, when I reference the name of the application as a variable?
There must be another way to do this, but the only examples I've found to do this is the "tell application/set miniaturized of windows" approach.
You might have more success using System Events to access the attributes of application process windows to control their miniaturised states.
Unlike trying to do it via the application objects themselves, the applications in question do not need to be AppleScript-able. I believe all processes running under System Events that contain windows have a set of attributes that are accessible via AppleScript, including one called AXMiniaturized, whose value is either true or false.
Although I didn't attempt to diagnose the problem with your method, I did draft this method (albeit on MacOS 10.13), which appears to corroborate what I've said. Hopefully the script is pretty self-explanatory:
use application id "com.apple.systemevents"
to getProcessesWithMiniaturizedWindows()
return the name of (every process whose value of ¬
attribute "AXMinimized" of every window ¬
contains true)
end getProcessesWithMiniaturizedWindows
to restoreAllWindowsForProcess:(procName as text)
local procName
set value of attribute "AXMinimized" of (every window ¬
of the process named procName whose value of ¬
attribute "AXMinimized" = true) to false
end restoreAllWindowsForProcess:
on run
repeat with processName in getProcessesWithMiniaturizedWindows()
restoreAllWindowsForProcess_(processName)
end repeat
end run
NB. You may need to grant Assistive Access Rights for System Events in System Preferences > Security & Privacy > Privacy > Accessibility (High Sierra).
The simple and straightforward solution would be this:
on test(applicationName)
-- do some work
-- restore all windows
tell application "System Events"
tell process applicationName
tell every window
set value of attribute "AXMinimized" to false
end tell
end tell
end tell
--- do some more work
end test

How can I use AppleScript to address dialog box in Ableton Live?

I have a collection of Ableton Live files (extension ".als") that I need to cycle through while playing a show. I'd like to dedicate a keyboard shortcut to launch each one, and had intended to use AppleScript for this.
The issue is that each file gets changed as I go through the process of playing the associated song, so that when I press the keyboard shortcut to launch the .als associated with the next song in my set, Ableton opens the "Save changes before closing?" dialog box (at which point what I want to do is select "Don't Save").
Simply pressing command + D at this point will do the trick, but I'd really like to automate this keypress. I can't seem to figure out how to get applescript to do this. I'm an applescript noob, and clicking the "Open Dictionary" option in AS seems to show that Ableton is not officially a scriptable app.
Any thoughts on this? Here's an example of an AppleScript I've been trying. This starts the process of opening the next .als in my set list, but won't click the "Don't Save" button.
tell application "Finder"
activate
open document file "Song 1.als" of folder "Desktop" of folder "User" of folder "Users" of startup disk
end tell
tell application "System Events"
keystroke "d" using command down
end tell
Interesting!
Finally came across tips that made it work:
Add both the Script Editor and Ableton Live to the Accessibility API:
System Preferences > Security & Privacy > Privacy...
Ignore application responses to continue the script during dialog.
LiveLoader.scpt:
-- open file
ignoring application responses -- don't wait for user input
tell application "Ableton Live 9 Suite" to open "Users:username:Desktop:LiveSet Project:LiveSet.als"
end ignoring
-- use delay if needed
-- delay 0.5
-- skip saving file
tell application "System Events"
set frontmost of process "Live" to true
key code 123 -- left
key code 123 -- left
keystroke return -- enter
end tell
Note:
Consider possible security impact.
Perhaps simply disable apps in Privacy List after use. (Could be scripted ;)
Can now also send mouse clicks, for more creativeness. :)
I know this is old. but in the interest of helping others who might find themselves here... heres what i have done.
use a program call Qlab. the free version will be fine.
make an applescript Cue. go to the 'trigger' tab. select midi trigger. hit the midi key you would like to assign the command too. this cue will now launch when it receives this midi note - even when running in the background.
go to the 'script' tab. copy and paste the script below.
you can make the relevant adjustments for each song. Basically each key will close all current ableton files without saving - as requested. and then launch a specific live set. which ever one you have assigned. in this case, the song 'Less Than Nothing'
the code...
tell application "System Events"
set frontmost of process "Live" to true
keystroke "q" using command down
tell application "System Events" to keystroke (ASCII character 28) --left arrow
tell application "System Events" to keystroke (ASCII character 28) --left arrow
keystroke return
end tell
delay 2.0
do shell script "open '/Users/CamMac/Desktop/Less Than Nothing 2 .als' "

Accessing "hidden" windows in AppleScript

I'm trying to set up an applescript which will handle a slightly-annoying VPN login process for me. I use Cisco AnyConnect, and every time I have to log back on, I have to hit connect, accept a certificate warning, enter my username, password, and select the right group, and accept ANOTHER certificate warning.
It's that second certificate warning I can't seem to get around.
Some digging indicates that the warning dialog is owned by a process named vpndownloader, but the odd thing is that Applescript seems convinced that it has no windows and does not exist!
I've been using Accessibility Inspector to get the window IDs and such to make this script work. For this particular dialog, it looks like this (accessibility inspector behind the actual dialog I'm attempting to control)
The strangeness comes in when I try to get the window's ID so I can do things with it.
tell application "System Events" to windows of process "vpndownloader"
{}
Blank. Okay, maybe there's something useful in the properties of the process?
tell application "System Events" to properties of process "vpndownloader"
{has scripting terminology:false
bundle identifier:"com.yourcompany.vpndownloader"
file:alias "Macintosh HD:opt:cisco:anyconnect:bin:vpndownloader.app:" of application "System Events"
creator type:"????"
subrole:missing value
entire contents:{}
selected:missing value
application file:alias "Macintosh HD:opt:cisco:anyconnect:bin:vpndownloader.app:" of application "System Events"
orientation:missing value
role:"AXApplication"
accepts high level events:true
file type:"APPL"
value:missing value
position:missing value
id:1212712
displayed name:"vpndownloader"
name:"vpndownloader"
class:application process
background only:true
frontmost:false
size:missing value
visible:false
Classic:false
partition space used:0
role description:"application"
maximum value:missing value
architecture:"i386"
short name:"vpndownloader"
focused:missing value
minimum value:missing value
help:missing value
title:"vpndownloader"
accepts remote events:false
total partition size:0
description:"application"
accessibility description:missing value
enabled:missing value
unix id:9053}
(for giggles, note that Cisco didn't set their bundle identifier..)
No contents, no visible windows. Despite the dialog I've got up right in front of me.
So, on one hand it obviously has a window (accessibility inspector can see it), but AppleScript is convinced that it does not.
How do I programmatically locate and access this "phantom" dialog?
Things that didn't work:
tell application "vpndownloader" to windows
error "vpndownloader got an error: Can’t get every window." number -1728 from every window
Again the theme of this application being in a weird state between existence and nonexistence comes up
tell application "vpndownloader" to properties
error "vpndownloader got an error: Can’t get every property." number -1728 from every property
It has a menu bar, but no windows.
tell application "System Events" to get UI elements of process "vpndownloader"
{menu bar 1 of application process "vpndownloader" of application "System Events"}
You can probably just use keystrokes. For example when that window comes up you yourself can probably physically hit the "connect anyway" button by first pressing the "tab" key to change the focussed button and then press the "space bar" to select the focussed button.
NOTE: you may have to enable this functionality by enabling full keyboard access. Do this by going to system preferences->keyboard->shortcuts and checking "all controls" at the bottom of the window.
If you can then you can applescript it. The following will work as long as that window is frontmost when you issue the commands.
tell application "System Events"
keystroke tab
delay 0.2
keystroke space
end tell
Good luck.
It works for me (I use anyconnect 3.1.06073).
https://gist.github.com/lotreal/ce43f4a85d8ae73781fa
So I found an interesting workaround for this problem that doesn't involve scripting. It turns out the vpndownloader is entirely optional, and it apparently only serves the purpose of updating the client when an update is pushed from the upstream server
A local policy XML can be defined to disable the process. This will cause breakage if an update is pushed (the connection will just abort), but it at least allows the login to be scripted.
On Mac/Linux systems, this file is located in /opt/cisco/anyconnect/AnyConnectLocalPolicy.xml - I had to create one based on the schema file, AnyConnectLocalPolicy.xsd in the same folder. If it already exists, just edit the existing one.
The key line is <BypassDownloader>, which is set to false by default. Setting it to true means that the downloader is simply not run, meaning the second dialog I was trying to access simply never appears!

Can I manipulate the location of a dialog displayed through osascript?

I've been playing around with various UNIX commands and came across this one to display a dialog:
osascript -e 'tell app "System Events" to display dialog "Hello World"'
I'd like to change the position of the dialog. For most commands, I can just use man command to figure out how to do something for that command. However, man osascript doesn't tell me how I can change the position of the dialog box. How can I modify the command to put the dialog in a different place?
First, to get help with applescript just open the AppleScript Editor application and look under the help menu. The applescript language guide is in there and other tools. Also under the file menu is "Open Dictionary" which will show you the specific commands for applications.
To see the information about display dialog, open the dictionary of StandardAdditions and use the search field to find the command.
To answer your question, no, you cannot specify the location of a "display dialog" window. There are no parameters in that command for positioning the window, as you'll see in the dictionary. In addition, no other commands will help you either because you can't issue other commands while the dialog window is open because the code pauses while the window is open waiting for a response from the dialog window (like when you press the OK button).
If you need to set the position of a window to display information to a user then you'll need to use some other dialog tool other than "display dialog". You could create your own window in cocoa or google for some alternatives like cocoaDialog, SKProgressBar, and Notification Center.
There is a round-about way to go about this, which may be useful in some scenarios. Try the following:
on displayMessage(msg)
tell application "Finder"
activate
set c to (count windows)
ignoring application responses
display dialog msg with icon note
end ignoring
end tell
tell application "System Events"
tell application process "Finder"
repeat until ((count windows) > c)
delay 0.2
end repeat
set position of window 1 to {0, 22}
end tell
end tell
end displayMessage
displayMessage("I'm over here!")
Credit for this little script goes to a post here.
In implimenting this myself, I found it was limited to whether or not the application that is being called (Finder, in the example) supports the count window command (or whether it has API support at all).
I realise I've dragged up a question from 2013. I am posting this answer here in case it is of use to the OP or, more likely, someone else with a similar question.

Resources