How to Use JXA with System Events App

learning
example
tutorial
jxa

#1

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" 
```

#2

Direct Comparison between AppleScript and JXA

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
*/

var seApp         = Application("System Events");
var oProcess       = seApp.processes.whose({frontmost: true})[0];
var appName       = oProcess.displayedName();
var appPath        = oProcess.applicationFile().posixPath()


/*
AppleScript:  6 lines
JXA:          4 lines
*/


#3

@JMichaelTX Thanks for this. It helped me get the syntax right for the frontmost process.
Apple's JXA documentation is still "sparse".
I subsequently realized that I had to attach Standard Additions to "oProcess" first to make clipboard processes etc. work

// to get the path to the front document using "oProcess" from above
var frontApp = Application(oProcess.name())
frontApp.includeStandardAdditions = true 
frontApp.setTheClipboardTo(frontApp.documents()[0].path())

Not the most elegant, but it works.
By the way, thanks for the JXA Cookbook. That was also a good learning resource.