Restore windows by application name (within a function) in AppleScript? - 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

Related

AppleScript running Atom text-editor every time script is run instead of when if statement is true

I'm new to AppleScript and I'm trying to make a simple AppleScript that will pop-up a dialog asking for user input and based on the input it will open one application or another. To do this I've used:
tell application "atom"
activate
end tell
One of the applications is stored on an external hard drive called Tardis. The problem with my code below is that it automatically opens Atom every time I run the script AND the dialog window at the same time. I only want it to open Atom when the input a is given. Thanks for the help! Code is below:
on run {input, parameters}
set inputText to text returned of (display dialog "Options: p - a - j" default answer "")
set p to "p"
set a to "a"
set j to "j"
if (inputText = p) then
tell application "Terminal"
if it is running then
do script "cd Python_Workspace; source ~/tensorflow/bin/activate"
end if
activate
end tell
tell application "Finder" to open POSIX file "/Volumes/Tardis/EXTRA Applications/Sublime Text.app"
end if
if (inputText = a) then
tell application "Atom"
activate
end tell
tell application "Terminal"
if it is running then
do script ""
end if
activate
end tell
end if
if (inputText = j) then
tell application "Terminal"
if it is running then
do script "cd /Java_Workspace"
end if
activate
end tell
tell application "IntelliJ IDEA CE"
activate
end tell
end if
return input
end run
So some applications that support Applescript do - or did, require the application to launch to compile the Applescript at all (if I remember correctly these are applications that support Applescript poorly, which I'm betting is Atom).
This may not be exactly what's happening, especially since if you are launching a compiled Applescript, but maybe you are running a .applescript (text) file.
What I would do, instead of tell application "Atom" to activate (a one line version of what you have), instead: activate application "Atom".
If that does work for you, activate vs tell here's what's going on:
Applescript notices you're talking about Atom. It needs a dictionary to translate your typing into... events.
Applecript can't find the dictionary, so it launches the application, hoping that it can get more information from it when it launches
Atom launches, Applescript realizes that every application supports activate.
Where activate application you're not trying to do anything Atom specific... just activate an app. No dictionary lookup required.
(It's also worth noting that I've been doing this a very long time. I could actually be quoting you information that's a decade or more out of date, or "new" in 10.4 - not 10.14, but 10.4)

AppleScript (or alternative) that checks running apps and closes the last opened instance

I have an OS X app, let's call it TestOSX.app. This is its displayed name (taken from the Info.plist CFBundleName key as far as I can tell).
For a variety of reasons (it can be circumvented by copying the app to another place or by opening it from Terminal; it does not work if CFBundleExecutable is not the binary per-se but a script that sets up some stuff before launching the binary itself...), I cannot rely on OS X's built-in policy to block someone from starting a second instance of the app, nor can I use the LSMultipleInstancesProhibited key. But I do want to make sure that every second instance started by the same user is going to quit before being able to modify some resources. Different users should be able to run their own single instace of the app at the same time (this is why LSMultipleInstancesProhibited is no-go).
(I wanted to build a mechanism relying on flock(1) but it does not exist under OS X.)
So, the strategy is: when a user launches my app, first check whether an older version is already running; if there is, send this latest app instance (that the script has been executed "from") a request to quit, and bring the old instance to foreground.
I cannot use the name of the process per-se, as the app may use some embedded tools (like a proprietary updater) which will have a different name than the app itself. This is why something like this won't work:
tell application "System Events"
set listOfProcesses to (name of every process where background only is false)
end tell
, as the identified process may simply say updater (which is a part of the TestOSX bundle).
I have a snippet, probably parts of the "big thing", but it doesn't work as expected:
tell application "System Events"
set theProcess to first application process whose displayed name is "TestOSX"
set theOtherProcess to second application process whose displayed name is "TestOSX"
set frontmost of theOtherProcess to true
end tell
, this one always brings to front only the 1st app's process.
And I don't get it why it doesn't work as expected, as long as:
tell application "System events"
set listOfProcesses to (name of every process whose (dsiplayed name is "TestOSX"))
end tell
returns both instances. I guess somewhere the mapping between the process and the name is being lost.
[Edit]
Tried to modify the snippet above using:
tell application "System Events"
set theOtherProcess to id of second application process whose displayed name is "TestOSX"
set frontmost of theOtherProcess to true
end tell
, yet I get the error:
"Can't set frontmost of 680102 to true."
(This may be because I have a script that actually launches the binary, as said above?)
Okay, I came up with an ugly solution.
The bash script that is launched by double-clicking on the app icon is going to check how many instances of my app are running (with the aid of an AppleScript). If the answer is more than one, the bash script will end, thus my whole app terminating.
My launcher.command script:
#!/bin/bash
val=$(osascript ./instances_counter.scpt "TestOSX")
and the instances_counter.scpt, making use of the argument I passed:
on run argv
tell application "System Events"
set theProcessList to every application process whose displayed name is item 1 of argv
set noOfProcesses to count of theProcessList
end tell
return noOfProcesses
end run
That's it.

Bring application to front by ID

I want to use AppleScript to bring an app to the front. If I run the following script
tell application "System Events"
tell process id 916
activate
end tell
end tell
the process doesn't come to front. Instead, only the active window of the currently front-most app loses focus, but that app stays in front.
Is it even possible to do this with a process ID rather than an application name? I have tried this on Mac OS X 10.6.8 and 10.7.5.
I am looking for a plain AppleScript solution. It should not use any shell commands or any other solution. I want to use the process ID number because I might have running multiple instances of the same application (in the same file location).
I have found the following solution:
tell application "System Events"
set myProcesses to every process whose unix id is myPocessID
repeat with myProcess in myProcesses
set the frontmost of myProcess to true
end repeat
end tell
Foo's answer works too:
tell application "System Events"
set frontmost of every process whose unix id is myProcessID to true
end tell
set processID to 432--currently firefox for me
tell application "System Events" to set a to file of 1st item of (processes whose unix id = processID)
activate application (a as alias as string)
This uses the path to the app file, which is apparently necessary (not just the name).
I have another answer which uses do shell script; I could add that if you want.

Add selected hyperlink to Safari reading list using 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.

Applescript opening wrong version of OS X app

My script is opening an old version of the app (which I wrote). I have searched the HD and cannot find the old app. The new version is in the applications folder. The script is in the scripts folder.
I made a new folder and put the script and the new app in it. Running the script still opens the wrong app. App2 is the problem child.
Where is the wrong path coming from?
How to:
Point the script at the correct app? I don't want a path in the script.
Find the old app and delete it?
property checkInterval : 5 -- Number of seconds between checks. Adjust to requirement.
global App1WasOpen, GUIScriptingWasEnabled, previousText
on run
-- When the script starts up, note if App1 is running and GUI Scripting is enabled.
tell application "App1" to activate
tell application "System Events"
set App1WasOpen to (application process "App1" exists)
set GUIScriptingWasEnabled to (UI elements enabled)
end tell
-- Enable GUI Scripting if nec. and give the 'previousText' variable an initial value.
switchGUIScripting(true)
set previousText to ""
end run
on idle
-- Each time the script's polled by the system, check that App1's open.
tell application "System Events" to set App1IsOpen to (application process "App1" exists)
if (App1gIsOpen) then
set App1WasOpen to true
-- Get the latest value for 'beam'.
tell application "App1"
set hdg to getHeading
set beam to hdg as string
end tell
if ((count beam) < 3) then set beam to text -3 thru -1 of ("000" & beam)
-- If it's changed since the last check, enter it into App2 and keep it for later checks.
if (beam is not previousText) then
--display dialog "Opening App2" buttons {"OK"}
tell application "App2" to launch
--activate application "App2"
tell application "System Events"
set startTime to current date
repeat until exists (text field 1 of window "App2" of application process "App2")
if (current date) - startTime is greater than 5 then
error "Could not find text field 1 of window AppB of application process App2"
exit repeat
end if
delay 0.2
end repeat
tell application process "App2"
try
set value of text field 1 of window "App2" to beam
end try
end tell
end tell
--tell application "App1" to activate
set previousText to beam
end if
-- Request the next 'idle' event in 'checkInterval' seconds' time.
return checkInterval
else if (App1WasOpen) then
-- App1 was open at the last check, but isn't now. Tell this script applet to quit.
quit
-- It'll actually quit on the next idle event, so request a short interval.
return 1
end if
end idle
-- Unless GUI Scripting was already on when the script started to run, turn it on or off as per the parameter.
on switchGUIScripting(onOff) -- 'onOff' is 'true' for on, 'false' for off.
if (not GUIScriptingWasEnabled) then
tell application "System Events"
activate
set UI elements enabled to (onOff)
end tell
end if
end switchGUIScripting
-- When this applet's told to quit, restore the previous GUI Scripting state before it does.
on quit
switchGUIScripting(false)
continue quit
end quit
Followup:
I used "Show in Finder" on the app's icon in the dock to point me to 17 Xcode archives, which I deleted. Now the script has a recurring message of "An error of type -10660 has occurred", and App2 never opens.
I opened the script editor, copied the code to a new script window and compiled it with a different name. Still doesn't open App2.
Try finding the Applications id with:
set appID to get id of application "/Applications/Safari.app"
Once you have found it, refer to the id rather than
tell application id "com.apple.Safari" to launch

Resources