0

script1.js:

function run() {
  const chromeApp = Application('Google Chrome');
  const window = chromeApp.windows[0];
  console.log(window.name());
  window.bounds = {
    x: 0,
    y: 0,
    width: 500,
    height: 500,
  };
  chromeApp.activate();
}

Run:

osascript -l JavaScript script1.js

And it works

script2.js:

function run() {
  const systemEvents = Application('System Events');
  const ap = systemEvents.processes().find(ap => ap.name() === 'Google Chrome');
  console.log(ap.name());
  const window = ap.windows[0];
  console.log(window.name());
  window.bounds = {
    x: 0,
    y: 0,
    width: 500,
    height: 500,
  };
}

Run:

osascript -l JavaScript script1.js

It does NOT work:

script2.js: execution error: Error: Error: Can't set that. (-10006)

But I really need to get script2.js work. Because in my real application, I don't know the application name in advance and I need to fetch the process dynamically base on user interaction. Because I don't know application name, I cannot use script1.js.

Any input is appreciate!

RobC
  • 22,977
  • 20
  • 73
  • 80
Tyler Liu
  • 19,552
  • 11
  • 100
  • 84
  • Window bounds are normally a list of 4 integers, not a record. – foo Sep 21 '21 at 08:34
  • @foo It's not the root cause because script1.js works. And the code is JavaScript instead of AppleScript. – Tyler Liu Sep 21 '21 at 14:42
  • Did you try it? Every “AppleScriptable” app implements its own Apple event interface how it sees fit. You can’t assume that what works on one app will work the same on others. Also, JXA is pants; it mostly just obfuscates Apple event behaviors so that when it does fail you’ve no idea if the problem is with the app or if it’s JXA that’s broken. Honestly, you’re best to solve the problem in AppleScript, because at least it works right, so if something fails you know the issue is with the app. – foo Sep 22 '21 at 09:21
  • The problem isn't JXA vs. AppleScript. `bounds` is a property of `window` elements of an AppleScriptable application. The `window` class object of _System Events_ doesn't have a `bounds` property, and instead has `size` and `position` properties. The `window` class of a sciptable app and the `window` class of _System Events_ may share a name, but they are completely different objects: the former is a child element of an `application` class object; the latter is a kind of `UI element` class object; thus their properties are not interchangeable. – CJK Sep 24 '21 at 00:47

4 Answers4

3
function run() {
  const systemEvents = Application('System Events');
  const p = systemEvents.processes().find(ap => ap.frontmost() === true);
  const ap = Application (p.bundleIdentifier());
  const window = ap.windows[0];
  window.bounds = {
    x: 0,
    y: 0,
    width: 500,
    height: 500,
  };
  }
Robert Kniazidis
  • 1,760
  • 1
  • 7
  • 8
  • Thanks. It's a good idea. But as I tested `const ap = systemEvents.processes().find(ap => ap.frontmost() === true)` doesn't work if frontmost app is Visual Studio Code. As I said I don't know the app name in advance, I need to position the frontmost app. It's better to make it work by app ID instead of by app name. – Tyler Liu Sep 21 '21 at 14:57
  • I don't think AppleScript vs JavaScript is the root cause, please check my answer. I could translate your code to JavaScript and it still works. – Tyler Liu Sep 21 '21 at 15:50
  • @Tyler Long: yes, you are right, it works as pure JXA. I updated my answer to application be identified by ID instead of the name. – Robert Kniazidis Sep 21 '21 at 19:34
  • Thanks. I accepted your answer. But I've turned to AppleScript. Because most documents/samples available online is AppleScript. – Tyler Liu Sep 21 '21 at 23:57
2

It’s years since I’ve had the displeasure of using GUI Scripting so didn’t immediately spot the real problem.

The real problem is that System Events is a bloated badly designed mess that shoehorns a dozen libraries’ worth of functionality into one #BigBallOfMud.

SE includes Cocoa Scripting’s Standard Suite window class definition, including a standard bounds property, but doesn’t actually implement it (since SE has no windows of its own). So while SE’s dictionary claims windows elements have a bounds property, trying to get/set that property throws an error. It is very confusing.

Therefore, ignore the window class definition in SE’s Standard Suite, and only look at the window class definition in its Process Suite (aka GUI Scripting). It doesn’t have a bounds property; instead it has separate position and size properties:

position (list of number or missing value) : the position of the window

size (list of number or missing value) : the size of the window

Here is code that works:

tell application "System Events"
    set ap to first process whose name is "Firefox"
    set win to window 1 of ap
    -- set bounds of win to {100, 100, 600, 600} -- this doesn't actually work
    set position of win to {100, 100}
    set size of win to {500, 500}
end tell

In other words, just because an app’s dictionary says it supports something doesn’t mean it actually does. Trying to figure out an app’s brokenness while also wrestling with JXA’s brokenness is at best a Sisyphean’ exercise.

Figure out how you get your code working in AppleScript first; if it doesn’t work there then you know it’s the app that’s the problem. If you still want to use JXA, then you can attempt to port your working code to that afterwards (although generally I don’t recommend wasting time on JXA as it’s crippled and abandoned, and the whole AppleScript stack is slowly dying anyway).

foo
  • 664
  • 1
  • 4
  • 4
  • Also bear in mind that using GUI Scripting requires the user to enable Accessibility access in System Preferences’ Security & Privacy settings (which is rightly locked down by default as it’s a huge potential security hole). – foo Sep 22 '21 at 09:50
  • I appreciate your answer! Yes as you said it is kind of buggy. And Apple doesn't release any updates to JXA in the recent years either. Some one said Swift will be the future language for macOS automation but nobody knows. For now we have to be used to what we have right now, sadly. – Tyler Liu Sep 22 '21 at 17:49
  • Don’t expect any updates to JXA (or to AppleScript) either: Apple disbanded the Mac Automation team and fired the project manager responsible back in 2016. The future of end-user automation across Apple platforms is most likely Siri with Shortcuts as an extensible backend providing a degree of customization. So it goes. – foo Sep 22 '21 at 19:52
0

Inspired by Robert's answer, I found that the following works:

function run() {
  const systemEvents = Application('System Events');
  const p = systemEvents.processes().find(ap => ap.name() === 'Google Chrome');
  console.log(p.name());
  const app = Application(p.name());
  const window = app.windows[0];
  window.bounds = {
    x: 0,
    y: 0,
    width: 500,
    height: 500,
  };
  app.activate();
}

So I can convert a Process into Application by name: const app = Application(p.name()); Then script2.js become script1.js.

But it doesn't always work. For example, the following doesn't work:

function run() {
  const systemEvents = Application('System Events');
  const p = systemEvents.processes().find(ap => ap.frontmost() === true);
  console.log(p.name());
  const app = Application(p.name());
  const window = app.windows[0];
  window.bounds = {
    x: 0,
    y: 0,
    width: 500,
    height: 500,
  };
  app.activate();
}

If the frontmost application is Visual Studio Code. Because the process name will be "Electron" and convert process to application will be wrong.

I'd like to find a way to convert process to application by ID. I am still investigating and I will post updates later.

Update: please check the accepted answer.

Tyler Liu
  • 19,552
  • 11
  • 100
  • 84
0

You can set the position and size to a two-dimensional array. Because Application("Name of App") doesn't always seem to work, it's best to go through System Events.

let sys = Application("System Events")
let app = sys.applicationProcesses
                    .whose({ name: "AppYouWant"})[0]
app[0].windows[0].size = [200, 200]
app[0].windows[0].position = [200, 200]
Sean Nilan
  • 1,745
  • 1
  • 14
  • 21