py-appscript manual | 7. Application objects

#

## 7. Application objects

### Creating application objects

Before you can communicate with a scriptable application, you must call the `app` object to create an application object. For example:

    textedit = app('TextEdit')

The arguments to this call are as follows:

    app(name=None, id=None, pid=None, url=None,
                terms='aete', newinstance=False, hide=False)
        name : str -- name or POSIX path of local application
        id : str -- bundle id for a local application
        pid : int -- Unix process id for a local process
        url : str -- eppc URL for a remote process
        terms : 'default' | 'aete' | 'sdef' | module
    				-- if a module, get terminology from it;
    				   if 'aete' or 'sdef', get terminology from target application;
                       if 'default', use built-in terminology only
        newinstance : bool -- when specifying application by name or bundle id,
                              launch a new application instance?
        hide : bool -- when specifying application by name or bundle id,
                       hide after launch?

To target an application you must supply at most one of the following arguments:

`name`
  The application's filename or POSIX path, e.g. `'TextEdit'`, `'TextEdit.app'`, `'/System/Applications/TextEdit.app'`. Where a filename is provided, appscript uses LaunchServices to locate the application. Paths beginning with `~`, e.g. `'~/Dev/MyApp.app'`, will be expanded automatically. An `.app` suffix is optional. For example, given the name `'TextEdit'`, appscript will first search for an application with that exact name. If none is found, it'll automatically add a `.app` suffix (`'TextEdit.app'`) and try again. (Caution: this argument is not supported in sandboxed processes where the sandbox restricts access to file system information.)
`id`
  The application's bundle id, e.g. `'com.apple.textedit'`. Cocoa applications often have unique bundle ids.
`pid`
  A valid Unix process id, e.g. `5678`.
`url`
  A URL of form `eppc://[user[:password]@host/Application%20Name[?[uid=#]&[pid=#]`, e.g. `eppc://johnsmith:foobar@office-mac.local/TextEdit?uid=501`. The host name/IP address and the process name (case-sensitive) are required. The username and password are optional: if omitted, the OS will obtain this information from the user's keychain or display a dialog asking the user to input one or both values. The user id and process id are also optional; if the process id is given, the process name will be ignored.

If none of these arguments is given, the host process (current application) is targetted.

By default, `app` objects obtain the target application's terminology by sending an `ascrgdte` ("Get AETE") Apple event, which best replicates AppleScript's own behavior. Unfortunately, this handler may be broken in some applications (specifically older apps that implement Carbon-based Apple event handling plus SDEF-based terminology). When dealing with these problem applications, pass `terms='sdef'` to the `app` constructor, e.g. `app(id='com.adobe.photoshop', terms='sdef')`.

Examples:

    from appscript import *

    ical = app('Calendar')

    textedit = app('TextEdit.app')

    safari = app('/Applications/Safari')

    addressbook = app(id='com.apple.addressbook')

    finder = app(url='eppc://192.168.2.3/Finder')

    music = app(url='eppc://Jan%20Smith@192.168.2.10/Music')

    from urllib import quote
    address = 'eppc://%s:%s@192.168.2.15/Terminal' % (quote(user), quote(pass))
    terminal = app(url=address)

### Basic commands

All applications should respond to the following commands:

    run() -- Run an application. Most applications will open an empty,
             untitled window.

    launch() -- Launch an application without sending it a 'run' event.
                Applications that normally open a new, empty document
                upon launch won't.

    activate() -- Bring the application to the front.

    reopen() -- Reactivate a running application. Some applications
                will open a new untitled window if no window is open.

    open(value) -- Open the specified object(s).
        anything -- list of objects to open, typically a list of
                    mactypes.Alias

    print_(value) -- Print the specified object(s).
        anything -- list of objects to print, typically a list of
                    mactypes.Alias

    quit(saving=value) -- Quit an application.
        saving : k.yes | k.ask | k.no -- specifies whether to save
                                         currently open documents

Note that appscript will automatically run an application in order to get its terminology. To start an application with a `launch` event instead of the usual `run` event, the `launch` command must be used immediately after an application object is created (i.e. before constructing any references or sending other commands).

Some applications may provide their own definitions of some or all of these commands, so check their terminology before use.

Appscript also defines `get` and `set` commands for any scriptable application that doesn't supply its own definitions:

    get(value) -- Get the data for an object.
        reference -- the object for the command
        Result: anything -- the reply for the command

    set(value, to=value) -- Set an object's data.
        reference -- the object for the command
        to : anything -- The new value.

Note that these commands are only useful in applications that define an Apple Event Object Model as part of their Apple event interface.

### Determining permission to automate

In macOS 10.14 and later, the first time one application tries to send an Apple event to another, macOS will normally ask the user to allow/deny this and subsequent Apple events to be sent. For example, running the following Python code in Terminal:

    $ python3
    >>> from appscript import *
    >>> app('Contacts').people.name()

triggers a system dialog if this is the first time a Terminal script has attempted to control Contacts.app:

Note that this security restriction only applies to commands defined by the application. The standard commands recognized by all Mac applications (`run`, `open`, `quit`, etc.) are not affected.

To check if the current process already has permission to control another application, check the target `app` object's `permissiontoautomate` property:

    permissiontoautomate : k.yes | k.no | k.ask -- has permission
                           already been granted or denied, or has
                           it yet to be requested?

The `permissiontoautomate` property returns `k.yes` if permission has already been granted, or `k.ask` if sending a command will trigger a permissions dialog, enabling a script to decide whether or not to proceed. (In macOS 10.13 and earlier, this property always returns `k.yes`.)

In macOS 10.14 to 10.14.2, the `permissiontoautomate` property returns `k.ask` instead of `k.no` when permission is denied.

See the System Integrity Protection section in Chapter 14 for more information.

### Local application launching notes

Note: the following information only applies to local applications as appscript cannot directly launch applications on a remote Mac. To control a remote application, the application must be running beforehand or else launched indirectly (e.g. by using the remote Mac's Finder to open it).

#### How applications are identified

When you create an `app` object by application name or bundle id, appscript uses LaunchServices to locate an application matching that description. If you have more than one copy of the same application installed, you can identify the one you want by providing its full path, otherwise LaunchServices will identify the newest copy for you.

#### Checking if an application is running

You can check if the application specified by an Application object is currently running by calling its `isrunning` method. This is useful if you don't want to perform commands on an application that isn't already running. For example:

    te = app('TextEdit')
    # Only perform TextEdit-related commands if it's already running:
    if te.isrunning():
        # all TextEdit-related code goes here...

Remember that appscript automatically launches a non-running application the first time your script makes reference to any of its properties, elements or commands. To avoid accidental launches, all code relating to that application must be included in a conditional block that only executes if `isrunning` returns `True`.

#### Launch errors

If the application can't be launched for some reason (e.g. if it's in the Trash), an `appscript.CantLaunchApplicationError` error will be raised. This provides a description of the problem (if it's a standard LaunchServices error) along with the original OS error number, which you can also obtain by casting the error object to an integer.

#### Using `launch` vs `run`

When appscript launches a non-running application, it normally sends it a `run` command as part of the launching process. If you wish to avoid this, you should start the application by sending it a `launch` command before doing anything else. For example:

    te = app('TextEdit')
    te.launch()
    # other TextEdit-related code goes here...

This is useful when you want to start an application without it going through its normal startup procedure. For example, the above script will launch TextEdit without causing it to display a new, empty document (its usual behaviour).

`launch` is also useful if you need to send a non-running application an `open` command as its very first instruction. For example, to send an `open` command to a non-stay-open application that batch processes one or more dropped files then quits again, you must first start it using `launch`, then send it an `open` command with the files to process. If you forget to do this, the application will first receive a `run` command and quit again before the `open` command can be handled.

#### Restarting applications

As soon as you start to construct a reference or command using a newly created `app` objects, if the application is not already running then appscript will automatically launch it in order to obtain its terminology.

If the target application has stopped running since the `app` object was created, trying to send it a command using that `app` object will normally result in an invalid connection error (-609), unless that command is `run` or `launch`. This restriction prevents appscript accidentally restarting an application that has unexpectedly quit while a script is controlling it. Scripts can restart an application by sending an explicit `run` or `launch` command, or by creating a new `app` object for it.

An `app` object's auto-relaunching behaviour can be modified by setting its `relaunchmode` property to one of the following string values:

'never'
  Sending any command to a previously quit application will produce an 'invalid connection' error.
'limited'
  A previously quit application will be automatically relaunched when a `run` or `launch` command is sent; other commands will produce an 'invalid connection' error. This is the default behaviour.
'always'
  A previously quit application will be automatically relaunched when any command is sent. This is the same behaviour as AppleScript.

For example:

    music = app('Music')
    music.relaunchmode = 'always'
    # rest of code goes here...

Note that you can use `app` objects to control applications that have been quit and restarted since the `app` object was created. Appscript will automatically update the `app` object's process serial number information as needed, as long as the application is running at the time.

There is a known problem with quitting and immediately relaunching an application via appscript, where the relaunch instruction is sent to the application before it has actually quit. This timing issue appears to be the OS's fault; the current workaround for scripts that need to quit and restart applications is to insert a few seconds' delay between the `quit` and `run`/`launch` using the `time` module's `sleep` function.

#### Launching multiple instances of an application

The optional `newinstance` argument can be used to launch a new instance of the application, even if another instance is already running.

Caution: OS X applications are usually designed to operate as single instances and may not work correctly/at all if run as multiple instances.

### Transaction support

Application objects implement three additional methods, `begintransaction`, `endtransaction` and `aborttransaction` that allow a sequence of related commands to be handled as a single operation by applications that support transactions, e.g. FileMaker Pro.

Once the application object's `begintransaction` method is called, all subsequent commands to that application will be sent as part of the same transaction until `endtransaction` or `aborttransaction` is called.

The `begintransaction` method takes an optional `session` argument that indicates the specific transaction session to open (in applications that support this).

Remember to call `endtransaction` or `aborttransaction` at the end of every transaction. (This includes transactions interrupted by a raised exception.) If a transaction is accidentally left open, appscript will try to end it automatically when the application object is garbage-collected, but this is not guaranteed to succeed.