As some of you know, I'm in the process of migrating my scripting to JavaScript for Automation (JXA), learning a lot as I go. I stil have a lot to learn, but wanted to share what I learned today.
Both JXA and AppleScript work very well with KM "Execute Script" Actions. For more info, see manual: Scripting [Keyboard Maestro Wiki].
How to Use JXA with System Events App
A great friend of the forum, @ccstone, has shown us numerous ways to use the "System Events" app with AppleScript. Here is one I'm sure I either got or derived from, one of Chris's scripts:
tell application "System Events"
tell (first process whose frontmost is true)
set appName to displayed name
set appPath to ((path to frontmost application) as text)
end tell
end tell
tell application appPath
set appID to id
end tell
return ("NAME: " & appName & return & "PATH: " & appPath & return & "ID: " & appID)
Chis may have a more optimized version, but this will do for my purpose today, which is to share with you guys how to do the same (and more) using JXA.
First, I'll post the entire JXA script, and then discuss it.
If you have any questions, suggestions, or improvements, please feel free to post.
###JXA Script to Get Info About FrontMost App
```javascript
(function run(){
//'use strict'; // Comment this line out to Create Variables in Safari Debugger
/*
⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶
Demo Use of JXA System Events
⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶
DATE: Wed, Feb 15, 2017
AUTHOR: JMichaelTX
REF: Numerous. I'll try to post later.
⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶⩶
*/
/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
REPRODUCE THE BELOW AppleScript USING JXA
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tell application "System Events"
tell (first process whose frontmost is true)
set appName to displayed name
set appPath to ((path to frontmost application) as text)
end tell
end tell
*/
//-- Same as tell application "System Events" --
var seApp = Application("System Events");
//-- (first process whose frontmost is true) --
var oProcess = seApp.processes.whose({frontmost: true})[0];
//--- GET ALL PROPERTIES OF THIS PROCESS ---
// which then can be addressed as oPropProcess.<PropertyName>
// see below for a compete list of properties
// Some properties are actually functions (like applicaitonFile() )
var oPropProcess = oProcess.properties();
//--- GET ALL PROPERTIES OF applicaitonFile() ---
var oAppFile = oPropProcess.applicationFile().properties();
//--- YOU CAN NOW CHOOSE WHICH METHOD YOU PREFER TO GET A PROPERTY ---
// oAppFile.path // () not needed since "path" is an object item
// OR
// oProcess.applicaitonFile().path() // note you have to use () with path like this
var appName = oProcess.displayedName();
var appID = oProcess.bundleIdentifier() //same as oPropProcess.bundleIdentifier
var appPathHFS = oProcess.applicationFile().path() //same as oAppFile.path
var appPathPOSIX = oProcess.applicationFile().posixPath() //same as oAppFile.posixPath
/*
debugger;
console.log("*** PAST DEBUGGER ***");
*/
return (
"appName: " + appName
+ "\nappID: " + appID
+ "\nappPathHFS: " + appPathHFS
+ "\nappPathPOSIX: " + appPathPOSIX
);
})();
/* ========= RESULTS ============ */
/*
appName: Script Editor
appID: com.apple.ScriptEditor2
appPathHFS: Macintosh HD:Applications:Utilities:Script Editor.app:
appPathPOSIX: /Applications/Utilities/Script Editor.app
*/
```
First, let me refer you to the [Apple JXA Guide for Accessing Applications](https://developer.apple.com/library/content/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW6). This tells us how to use the JXA equivalent of
`tell application "Name of Some App"`
If you have not read (and reread) the Apple [JavaScript for Automation Release Notes](https://developer.apple.com/library/content/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/Introduction.html#//apple_ref/doc/uid/TP40014508-CH111-SW1), let me strongly encourage you to do.
In building this JXA script the first line was pretty easy:
```javascript
//-- Same as tell application "System Events" --
var seApp = Application("System Events");
```
But the next part, not so much:
```javascript
//-- (first process whose frontmost is true) --
var oProcess = seApp.processes.whose({frontmost: true})[0];
```
One of the most powerful features of AppleScript is the "whose" clause.
It is not obvious how to implement this in JXA.
See [JXA Guide: Filtering Arrays using the whose function](https://developer.apple.com/library/content/releasenotes/InterapplicationCommunication/RN-JavaScriptForAutomation/Articles/OSX10-10.html#//apple_ref/doc/uid/TP40014508-CH109-SW10)
#### 1. Get the Frontmost Process
**So how do we get the "first process whose frontmost is true"?**
The first part is to get a list (JavaScript array) of all of the Mac processes that System Events can see:
`seApp.processes`
but we want ONLY the ones (really there is only one) that is _frontmost_.
So this is where the "whose" function comes in:
`whose({frontmost: true})`
Even though this returns only one process, it does so in an array.
So, we want the first one, which is array element `[0]`:
Easy and simple enough to put in one statement now:
`var oProcess = seApp.processes.whose({frontmost: true})[0];`
**So "oProcess" will be the "first process whose frontmost is true".**
#### 2. Get The Process Properties of Interest
Great! Now all we have to do is get the properties of this process that we want!
Simple, right? Not always. :wink:
So, I'll give you answer first:
If you know the property name, you can often (but not always) just use it directly, like:
`oProcess.displayedName();`
But what if you don't know the property name? Of if the property of interest is actually part of another object?
This is where using the Safari debugger really helps.
There is some setup required (see [Debugging JXA Scripts with Safari Debugger](https://www.evernote.com/l/ABu9nrsi101Ce4FfWhofVktcD6JMY-ZhGlE)), but basically if you just put a
`debugger;`
line in your script, it should pause the script and show it in the Safari debugger:
Here is what you see:
<img src="/uploads/default/original/2X/c/c2ce9c0dd9b07676b18914c43c9848b5f947d00d.png" width="690" height="392">
So, I was expecting "oProcess" to be an object, but actually it is a "function".
This means that its properties are NOT immediately available.
So this will require a command like:
`oProcess.properties()`
When I type in this command in the debugger console:
<img src="/uploads/default/original/2X/c/c6f85f643165e8828e1773509b4b23bb5352a1ee.png" width="231" height="30">
I get this (after expanding the result):
<img src="/uploads/default/original/2X/d/d65f35057fb3d7dc7b63afc6b1d5da5df2a68319.png" width="299" height="452">
I just selected this entire list of properties and pasted into a text document (see below) to have a reference of the property names.
Also notice that there is one property, "`applicationFile`" which is also a function.
So we have to call it to get its list of properties:
<img src="/uploads/default/original/2X/1/1c0e0179c6ab67941b2a5120b4802df4877bcf9b.png" width="430" height="368">
**So, now I know how to construct the JXA statements to get the properties I want:**
```javascript
var oPropProcess = oProcess.properties();
//--- GET ALL PROPERTIES OF applicaitonFile() ---
var oAppFile = oPropProcess.applicationFile().properties();
//--- YOU CAN NOW CHOOSE WHICH METHOD YOU PREFER TO GET A PROPERTY ---
// oAppFile.path // () not needed since "path" is an object item
// OR
// oProcess.applicaitonFile().path() // note you have to use () with path like this
var appName = oProcess.displayedName();
var appID = oProcess.bundleIdentifier() //same as oPropProcess.bundleIdentifier
var appPathHFS = oProcess.applicationFile().path() //same as oAppFile.path
var appPathPOSIX = oProcess.applicationFile().posixPath() //same as oAppFile.posixPath
```
Note that once you know the actual properly name, and its parent object, you can reference it directly.
So,
```javascript
//--- INSTEAD OF ---
var oPropProcess = oProcess.properties();
var oAppFile = oPropProcess.applicationFile().properties();
var appPath = oAppFile.path // () not needed since "path" is an object item
//--- I CAN USE ---
var appPath = oProcess.applicationFile().path() //same as oAppFile.path
```
How you decide to write this depend on your preference, and how may references you make to the parent object.
If you make a lot of references, then it is more efficient (faster) to create one reference to the parent, and then get the child properties you need.
Well, that's it for now.
**If you have any questions, suggestions, or improvements, please feel free to post.**
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#### List of Properties for the Process Object and applicationFile Object
```javascript
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// System Event process.properties()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
var seApp = Application("System Events");
var oProcess = seApp.processes.whose({frontmost: true})[0];
oProcess.properties()
acceptsHighLevelEvents: true
acceptsRemoteEvents: false
accessibilityDescription: null
applicationFile: // function()
architecture: "x86_64"
backgroundOnly: false
bundleIdentifier: "com.apple.Safari"
class: "applicationProcess"
{}
classic: false
creatorType: "sfri"
description: "application"
displayedName: "Safari"
enabled: null
entireContents: [] (0)
file: // function()
fileType: "APPL"
focused: null
frontmost: true
hasScriptingTerminology: true
help: null
id: 42395756
maximumValue: null
minimumValue: null
name: "Safari"
orientation: null
partitionSpaceUsed: 0
position: null
role: "AXApplication"
roleDescription: "application"
selected: null
shortName: "Safari"
size: null
subrole: null
title: "Safari"
totalPartitionSize: 0
unixId: 38169
value: null
visible: true
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
applicationFile().properties()
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
busyStatus: false
class: "alias"
{}
container: // function()
creationDate: Mon Jun 22 2015 15:25:25 GMT-0500 (CDT)
creatorType: "sfri"
defaultApplication: // function()
displayedName: "Safari.app"
fileType: "APPL"
id: "Safari.app,-100,56355339"
kind: "Application"
modificationDate: Wed Feb 08 2017 19:48:12 GMT-0600 (CST)
name: "Safari.app"
nameExtension: "app"
packageFolder: true
path: "Macintosh HD:Applications:Safari.app:"
physicalSize: null
posixPath: "/Applications/Safari.app"
productVersion: ""
shortVersion: "10.0.3"
size: null
stationery: false
typeIdentifier: "com.apple.application-bundle"
url: "file:///Applications/Safari.app/"
version: "10.0.3, Copyright © 2003-2016 Apple Inc."
visible: true
volume: "Macintosh HD"
```