macosjavascript-automation

JXA: Manipulating windows by their ID


Instead of

Application("Preview").windows[0].bounds = {"x": 1440, "y":25, "width":1440, "height": 2535}

I'd like to move the window but instead of using an index, I'd like to use the window's ID. I got the ID with

Application("Preview").windows[0].id();

But when I replace the index with the ID:

Application("Preview").windows.whose({id: 15596}).bounds = {"x": 1440, "y":25, "width":1440, "height": 2535}

I get

Error -1728: Can't get object.


Solution

  • There's a difference between the window id given by the Application object, the window id tracked by the Application('System Events') object, and the window id tracked by the CoreGraphics framework.

    Unless the developer of the application wrote a scripting exposure for manipulating window bounds into their app, you can't use the Application object to adjust bounds, you have to use Application('System Events') or use the Objective-C bridge to access the AXUIElement sub-system directly.

    For example, using Application('System Events'):

    const window = Application('System Events')
      .applicationProcesses
      .whose({name: 'Safari'}).at(0)
      .windows.at(0);
    
    window.position.set([0, 0]);
    window.size.set([1200, 1000]);
    
    return 0;
    

    Note that there isn't any addressing of the window by its id, because the System Events process isn't aware of such application-tracked IDs.


    If you needed to narrow the scope of your window selection by its title or other characteristic, you can do so using JavaScript's Array.prototype.find() or Array.prototype.filter():

    const SafariEvents = Application('System Events')
      .applicationProcesses
      .whose({name: 'Safari'})
      .at(0);
    
    const window = SafariEvents.windows()
      .find((window) => !!window.attributes
                          .byName('AXTitle').value()
                          .match(/JXA/));
    
    window.position.set([0, 0]);
    window.size.set([1200, 1000]);
    
    return 0;
    

    Take care to notice the extremely-verbose syntax of extracting accessibility attributes from the System Events scripting object. While it's lengthy, it does give you the ability to use things like regular expressions to help find what you want — a much better alternative to native AppleScript's hodgepodge of search/find mechanisms.


    Saving the best for last, you can use the Objective-C bridge to access the accessibility subsystem directly. This requires dealing with pointers and is a little more complex than the previous examples, but it is significantly faster than using System Events (I can't emphasize that enough — it's nuts how responsive this approach is).

    I've made a gist with helper functions to make this easier. It includes an example of adjusting the position of a Visual Studio Code window. You can find it here if you'd like to try it.

    Cheers.