The current handler, appInfo, returns properties of one or more applications efficiently. The applications may be running or non-running. If non-running, properties will be retrieved without launching the application.
The handler’s input argument is an application reference, or a list of application references. Each application reference may be in any of the following forms:
- Application name with or without a “.app” extension (e.g., “Safari” or “Safari.app”)
- Bundle identifier (e.g., “com.apple.Safari”)
- Full HFS path to the application bundle (e.g., “[startup disk name]:Applications:Safari.app”)
- Full POSIX path to the application bundle (e.g., “/Applications/Safari.app”)
- AppleScript alias to the application bundle (e.g., alias “[startup disk name]:Applications:Safari.app:”)
- «class furl» file reference to the application bundle (e.g., “/Applications/Safari.app” as POSIX file)
- NSURL object referring to the application bundle (e.g., current application’s |NSURL|'s fileURLWithPath:“/Applications/Safari.app”)
- Unix process ID as an integer (e.g., 60839) or text string (e.g., “60839”), if the application is running
The handler’s return value is an AppleScript record of application properties of the form {appName:…, executableName:…, bundleIdentifier:…, posixPath:…, hfsPath:…, bundleURL:…, versionString:…, isAppleScriptEnabled:…, allowsUserInteraction:…, isBackgroundOnly:…, isRunning:…, processID:…, processIDs:…}
appName
→ application bundle name = [app name].app
executableName
→ executable name = /…/[app name].app/Contents/MacOS/[executableName]
bundleIdentifier
→ Apple’s bundle identifier in reverse DNS notation, e.g., “com.apple.Safari”
posixPath
→ POSIX path to .app bundle = “/…/[app name].app”
hfsPath
→ HFS path to .app bundle = “[startup disk name]:…:[app name].app”
bundleURL
→ NSURL object referring to [app name].app bundle
versionString
→ application version as text string (for some applications, this value is available only if the application is running)
isAppleScriptEnabled
→ boolean true value if the application is AppleScript-enabled (i.e., if its Info.plist item NSAppleScriptEnabled = YES or 1 or true)
allowsUserInteraction
→ boolean true value if the application runs in the background but allows user interaction (i.e., if its Info.plist item LSUIElement = YES or 1 or true)
isBackgroundOnly
→ boolean true value if the application runs in the background and does not allow user interaction (i.e., if its Info.plist item LSBackgroundOnly = YES or 1 or true)
isRunning
→ boolean true value if the application is running
processID
→ Unix ID of the newest running instance of the application, or the missing value if the application is not running
processIDs
→ list of the Unix IDs of all running instances of the application, or the empty list if the application is not running
NOTES:
- If the input argument is a single application reference, the output property values are the values themselves; if the input argument is a list of application references, the output record properties are lists of values corresponding to the input application references.
- Any missing output property values will be set to missing value instead.
- If the application can’t be found, all its output properties will be set to missing value.
The details of how the handler works are described in the handler’s comments. Some highlights are as follows:
- The handler retrieves an application’s properties efficiently, generally about 0.1 seconds per application, whether the application is running or not
- The bundle identifier is the first application property to be retrieved, from which the remaining properties are obtained
- If the application reference is in the form of a running application’s Unix process ID, the bundle identifier is retrieved using NSRunningApplication’s runningApplicationWithProcessIdentifier: method
- If the application reference is in any other form, the bundle identifier is retrieved from the application’s id property
- Retrieval via the id property allows efficient retrieval with the added benefit of not launching the application if it isn’t running
- The retrieval is performed within an osascript command wrapper, which has the benefit of preventing a choose application… dialog window from opening if the application isn’t found
- If the application reference is in the form of a bundle identifier, the bundle identifier is retrieved (i.e., validated) with the construct: application id [application reference]'s id
- If the application reference is in any other form (i.e., neither a process ID nor a bundle identifier), the bundle identifier is retrieved (after converting the reference to a text string if necessary) with the construct: application [application reference]'s id
Here’s an example of handler usage with a single application reference as the input argument:
appInfo("Finder")
-->
{
appName:"Finder",
executableName:"Finder",
bundleIdentifier:"com.apple.finder",
posixPath:"/System/Library/CoreServices/Finder.app",
hfsPath:"[startup disk name]:System:Library:CoreServices:Finder.app",
bundleURL:(NSURL) file:///System/Library/CoreServices/Finder.app/,
versionString:"10.13.6",
isAppleScriptEnabled:true,
allowsUserInteraction:false,
isBackgroundOnly:false,
isRunning:true,
processID:1438,
processIDs:{1438}
}
Here’s an example of handler usage with multiple application references as the input argument:
appInfo({"com.apple.Dock", "/Applications/QuickTime Player.app", "Nonexistent App"})
-->
{
appName:{
"Dock",
"QuickTime Player",
missing value
},
executableName:{
"Dock",
"QuickTime Player",
missing value
},
bundleIdentifier:{
"com.apple.dock",
"com.apple.QuickTimePlayerX",
missing value
},
posixPath:{
"/System/Library/CoreServices/Dock.app",
"/Applications/QuickTime Player.app",
missing value
},
hfsPath:{
"[startup disk name]:System:Library:CoreServices:Dock.app",
"[startup disk name]:Applications:QuickTime Player.app",
missing value
},
bundleURL:{
(NSURL) file:///System/Library/CoreServices/Dock.app/,
(NSURL) file:///Applications/QuickTime%20Player.app/,
missing value
},
versionString:{
"1.8",
"10.4",
missing value
},
isAppleScriptEnabled:{
false,
true,
missing value
},
allowsUserInteraction:{
true,
false,
missing value
},
isBackgroundOnly:{
false,
false,
missing value
},
isRunning:{
true,
false,
missing value
},
processID:{
1423,
missing value,
missing value
},
processIDs:{
{1423},
{},
missing value
}
}
Here is the handler:
on appInfo(appRef)
-- Returns properties of running and non-running applications
-- Wrap the code in a try block to capture any errors
try
-- Constants
set classNSURL to current application's |NSURL|'s |class|()
set sharedWorkspace to current application's NSWorkspace's sharedWorkspace()
set noValueToken to "¦NO¦VALUE¦"
---- Be sure the input argument is in the form of a list
tell appRef to if its class ≠ list then set appRef to {it}
-- Create the shell script that will return the application bundle identifiers, one bundle identifier per line of returned text
set theScript to ""
repeat with currRef in appRef
tell currRef's contents to set {currRef, currClass} to {it, its class}
try
-- If the application reference is in the form of a process ID, be sure it is in the form of an integer
set currRef to currRef as integer
end try
tell currRef
try
-- Create the shell comnmands that will return the current reference's bundle identifier, or, if the bundle identifier can't be found, a "no value" token instead
if its class = integer then
-- If the application reference is in the form of a process ID, get the bundle identifier from Cocoa's NSRunningApplication class
-- If the bundle identifier can't be found, an error will be thrown, thereby setting the value to the "no value" token
tell ((current application's NSRunningApplication's runningApplicationWithProcessIdentifier:it)'s bundleIdentifier())
if it = missing value then error
set theScript to theScript & linefeed & "echo " & (it as text)'s quoted form
end tell
-- The following is an alternative Bash solution that works by reading the bundle identifier directly from the application's Info.plist file; however, it runs about 1.7x slower than the NSRunningApplication solution above
-- set theScript to theScript & linefeed & "/usr/libexec/PlistBuddy -c \"Print :CFBundleIdentifier\" \"$(ps -p " & it & " -o comm= | egrep -o -m 1 '^.+\\.app\\/Contents\\/')Info.plist\""
else if {its class} is in {text, alias, «class furl», classNSURL} then
-- If the application reference is supplied in any other form, coerce it to a text string, then get the bundle identifier from the application "id" property
-- Wrap these actions inside an osascript command to prevent a "choose application" dialog window from opening if the application isn't be found
-- If the bundle identifier can't be found, a "no value" token is returned instead
-- Note that the first construct, "application ...'s id", handles all application reference forms except bundle identifer, which is handled by the second construct, "application id ...'s id", and Unix process ID, which is handled separately by Cocoa's NSRunningApplication class
set theScript to theScript & linefeed & "osascript -e \"application \\\"" & it & "\\\"'s id\" || osascript -e \"application id \\\"" & it & "\\\"'s id\" || echo " & noValueToken
else
-- For any invalid application reference construct, force an error, thereby setting the value to the "no value" token
error
end if
on error
set theScript to theScript & linefeed & "echo " & noValueToken
end try
end tell
end repeat
-- Execute the shell script, and capture the bundle identifiers (or "no value" tokens for any bundle identifiers that can't be found) from the returned lines of text
set bundleIdentifierCandidates to (do shell script theScript)'s paragraphs
-- Derive the remaining output properties from the bundle identifiers
set {bundleIdentifier, bundleURL, posixPath, hfsPath, appName, executableName, versionString, isAppleScriptEnabled, allowsUserInteraction, isBackgroundOnly, isRunning, processID, processIDs} to {{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}}
repeat with currBundleIdentifier in bundleIdentifierCandidates
-- Set the baseline value of all properties of the current application reference (except the bundle identifier property) to the missing value
set currBundleIdentifier to currBundleIdentifier's contents
set {currBundleURL, currPOSIXPath, currHFSPath, currAppName, currExecutableName, currVersionString, currIsAppleScriptEnabled, currAllowsUserInteraction, currIsBackgroundOnly, currIsRunning, currProcessID, currProcessIDs} to {missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value, missing value}
if currBundleIdentifier = noValueToken then
-- If a bundle identifier could not be found for the current application reference, change its bundle identifier property value from the "no value" token to the missing value, and abort any further processing
set currBundleIdentifier to missing value
else
-- If the current application reference has a valid bundle identifier, get its remaining properties
-- To get properties stored in the application's Info.plist file, convert to plist file to an NSDictionary, and retrieve properties via NSDictionary's objectForKey: method
try
set currBundleURL to (sharedWorkspace's URLForApplicationWithBundleIdentifier:currBundleIdentifier)
end try
try
tell ((currBundleURL)'s |path|()) to if it ≠ missing value then set currPOSIXPath to it as text
end try
try
set currHFSPath to currPOSIXPath as POSIX file as text
end try
try
set currInfoPlistDictionary to (current application's NSDictionary's dictionaryWithContentsOfFile:(currPOSIXPath & "/Contents/Info.plist"))
try
tell (currInfoPlistDictionary's objectForKey:"CFBundleName") to if it ≠ missing value then set currAppName to it as text
end try
try
tell (currInfoPlistDictionary's objectForKey:"CFBundleExecutable") to if it ≠ missing value then set currExecutableName to it as text
end try
try
tell (currInfoPlistDictionary's objectForKey:"CFBundleShortVersionString")
if it = missing value then error
set currVersionString to it as text
end tell
on error
try
tell (currInfoPlistDictionary's objectForKey:"CFBundleVersion") to if it ≠ missing value then set currVersionString to it as text -- handles the occasional application without a short version string
end try
end try
try
tell (currInfoPlistDictionary's objectForKey:"NSAppleScriptEnabled") to set currIsAppleScriptEnabled to {it as text} is in {"true", "1", "YES"}
end try
try
tell (currInfoPlistDictionary's objectForKey:"LSUIElement") to set currAllowsUserInteraction to {it as text} is in {"true", "1", "YES"}
end try
try
tell (currInfoPlistDictionary's objectForKey:"LSBackgroundOnly") to set currIsBackgroundOnly to {it as text} is in {"true", "1", "YES"}
end try
end try
-- Get the Unix process IDs of all running instances of the application
try
tell ((current application's NSRunningApplication's runningApplicationsWithBundleIdentifier:currBundleIdentifier) as list)
set currProcessIDs to {}
repeat with currObj in it
try
set end of currProcessIDs to currObj's processIdentifier() as integer
end try
end repeat
if currProcessIDs = {} then error
-- Save the newest running instance of the application in a separate return property from the property containing the full list of running application instances
set {currIsRunning, currProcessID} to {true, currProcessIDs's last item}
end tell
on error
set currIsRunning to false
end try
end if
-- Add the current application's values to the output variable lists
set {end of bundleIdentifier, end of bundleURL, end of posixPath, end of hfsPath, end of appName, end of executableName, end of versionString, end of isAppleScriptEnabled, end of allowsUserInteraction, end of isBackgroundOnly, end of isRunning, end of processID, end of processIDs} to {currBundleIdentifier, currBundleURL, currPOSIXPath, currHFSPath, currAppName, currExecutableName, currVersionString, currIsAppleScriptEnabled, currAllowsUserInteraction, currIsBackgroundOnly, currIsRunning, currProcessID, currProcessIDs}
end repeat
-- Return the results; if the input argument consisted of only one application reference, delist the output variable values before returning them to the calling program
if bundleIdentifier's length = 1 then set {appName, executableName, bundleIdentifier, posixPath, hfsPath, bundleURL, versionString, isAppleScriptEnabled, allowsUserInteraction, isBackgroundOnly, isRunning, processID, processIDs} to {appName's first item, executableName's first item, bundleIdentifier's first item, posixPath's first item, hfsPath's first item, bundleURL's first item, versionString's first item, isAppleScriptEnabled's first item, allowsUserInteraction's first item, isBackgroundOnly's first item, isRunning's first item, processID's first item, processIDs's first item}
return {appName:appName, executableName:executableName, bundleIdentifier:bundleIdentifier, posixPath:posixPath, hfsPath:hfsPath, bundleURL:bundleURL, versionString:versionString, isAppleScriptEnabled:isAppleScriptEnabled, allowsUserInteraction:allowsUserInteraction, isBackgroundOnly:isBackgroundOnly, isRunning:isRunning, processID:processID, processIDs:processIDs}
on error m number n
if n = -128 then error number -128
if n ≠ -2700 then set m to "(" & n & ") " & m
error ("Problem with handler appInfo:" & return & return & m)
end try
end appInfo
Edit note: A minor cosmetic change was made to the originally submitted version for the code that retrieves the value of the Info.plist item CFBundleShortVersionString.
Edit note 2: An error in the «class furl» file reference example in the introductory discussion (not the handler code) was corrected.