RubyOSA Tutorial

This tutorial will explain how to use RubyOSA to control scriptable Mac OS X applications.

We expect you to have some Ruby programming background before starting reading it.

We also expect you to have properly installed RubyOSA on your machine. You can read information about the installation of RubyOSA on the main page.

The Basics

RubyOSA is a bridge between Ruby and the Open Scripting Architecture (OSA). The key idea is to create a Ruby API on the fly based on a target application’s scriptable definition.

Once you target an application with RubyOSA you get a special kind of proxy object, which is the entry point of the generated Ruby API.

Let’s start with something easy:

require 'rbosa'               # (1)
app = OSA.app('iTunes')       # (2)
puts app.current_track.name   # (3)

We need to require the RubyOSA bridge first (1). Note that if you installed RubyOSA via RubyGems you might want to require ‘rubygems’ before.

Then we get an handle on the iTunes application (2), under the form of an OSA::ITunes::Application object. The OSA::ITunes module and everything inside is automatically generated during OSA.app().

Finally we use the application object to get the name of the current track, and we print the result to the standard output (3).

Getting an Application Object

As you just learnt, you can locate an application by passing its name to OSA.app(). But this is not the only way.

You can also locate an application by providing its signature (as a four characters string), or bundle ID:

OSA.app(:signature => 'hook')
OSA.app(:bundle_id => 'com.apple.iTunes')

More interestingly, if the application you want to control hasn’t been installed in one of the standard applications locations, RubyOSA may have some trouble locating it. You can provide the full path of the application to work around this:

OSA.app(:path => '/Somewhere/Applications/MyApp.app')

Note that OSA.app(‘iTunes’) is a convenience shortcut to OSA.app(:name => ‘iTunes’).

Generating the API Documentation

Getting an application object is not very useful if you don’t know which messages it responds to, as well as their purposes.

RubyOSA comes with a tool called rdoc-osa which allows you to generate some documentation about the API RubyOSA dynamically generated for a given scriptable application.

For example:

$ rdoc-osa --name iTunes
[...]
$ open doc/index.html

rdoc-osa acts as a front-end to the RDoc tool. By default it generates HTML documentation in the doc directory of the current working directory.

Here is an example of what is generated to describe iTunes’s Ruby API.

You can also generate RI documentation:

$ rdoc-osa --name iTunes --ri
[...]
$ ri current_track

To learn more about rdoc-osa, type rdoc-osa -h at the command line.

Merging Scripting Additions

You may want to call commands defined in Scripting Additions terminologies. A popular one is StandardAdditions, delivered in Mac OS X as /System/Library/ScriptingAdditions/StandardAdditions.osax.

In order to do that you use the #merge method on your application object:

app = OSA.app('iTunes')
app.merge('StandardAdditions')
app.beep
puts app.choose_file

The StandardAdditions API will be merged into the application object, allowing you to call new methods on it.

This will ask iTunes to emit a beep, then to present the file chooser window. The chosen path will be printed to the standard output on success, otherwise an exception will be raised (if the user cancels the window for example).

#merge works like OSA.app(). You can also provide either the signature, bundle ID or full path to a scriptable addition using respectively the :signature, :bundle_id or :path keys/values.

Controlling Remote Applications

RubyOSA can controls scriptable applications running on other machines.

The Remote Apple Events option must be checked in the Sharing preferences pane on the remote machine.

Then, you need to supply to RubyOSA the address of the machine when getting your application object:

app = OSA.app('iTunes', :machine => 'my-remote-machine.local')
app.play 

An authentication window will appear asking you for the username and password of your remote account. If the authentication succeeded, RubyOSA will download the scriptable definition of the remote application, and then acts as normal, except that events will be sent to the remote computer instead of the local one.

If you want to avoid the authentication window you can pass the username and password to RubyOSA directly:

app = OSA.app('iTunes', 
  :machine => 'my-remote-machine.local', 
  :username => 'John Foo', 
  :password => 'johnfoo42')
app.play

Note that you can only control remote applications by providing their names. Trying to provide the signature, bundle ID or even the path on the remote machine won’t work (for now).

Global Settings

The OSA module provides some global variables that you can set to change the behavior of RubyOSA.

Name Default value Description
OSA.lazy_events true Controls whether OSA proxy objects are resolved on demand, or always. By default objects are only resolved on demand, when necessary. Setting this variable to false will force every object returned by RubyOSA to be resolved. This may affect the performance of your program as one or more extra Apple events will be sent.
OSA.utf8_strings false Controls whether strings will be encoded as Unicode or ASCII. By default strings are encoded in ASCII as some applications cannot handle Unicode strings.
OSA.timeout -1 Controls the timeout duration of Apple events sent by RubyOSA. The value is expressed in ticks. By default it’s set to -1 which means about one minute. -2 means that there is no timeout.
OSA.wait_reply nil Controls whether RubyOSA should expect a result from the Apple events it will send. When set to nil, the default, it will determine this from the scriptable definition. However, it’s pretty rare but you may encounter a malformed application command. You can therefore either set this variable to true or false to force RubyOSA to send back or not a return value.

You set these settings like this:

OSA.timeout = -1
app.do_something

For your convenience, we added an API to change one or more settings only for a given scope:

OSA.set_params(:timeout => -1, :lazy_events => false) do
  app.do_something
  app.do_something_again 
end
# Here the settings will get their original values