Last edited · 1 revision  

 


How to Use JXA with System Events App

[Copied from my post in the Keyboard Maestro Forum]

A great friend of the KM 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 questions in the jxa@apple-dev.groups.io Messages area.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

JXA Script to Get Info About FrontMost App

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

1. Get a Reference to Application Object

First, let me refer you to the Apple JXA Guide for Accessing Applications. 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, let me strongly encourage you to do.

In building this JXA script the first line was pretty easy:

//-- Same as tell application "System Events" --
var seApp         = Application("System Events");

But the next part, not so much:

//-- (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


2. 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".


3. 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? Or 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), 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:

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:

I get this (after expanding the result):

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:


So, now I know how to construct the JXA statements to get the properties I want:

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,

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


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