heymodule - BeOS Application Scripting from Python

Version 1.1
January 1, 1999

(See the Changes section for a list of changes since Heymodule 1.0.)

BeOS R3 introduced a new BMessage based scripting system, that lets you remote control applications by sending them special scripting messages.

In theory, you can use anything you want to script your applications, as long as it can send and receive BMessage objects. In practice, you either have to write a C++ application, or use Attila Mezei's hey utility from the command-line.

Until now.

heymodule lets you do BeOS application scripting from Python. As far as I know, this is the very first third-party programming language to support BeOS's scripting!


Note: As of BeOS R4 and Python 1.5.2 (in public beta shortly before Christmas 1998), Python runs on both PowerPC and x86 systems!

Installing heymodule

Install Python

Before you install heymodule you must install Python 1.5.2. Currently, it's only distributed as source code:

  1. Download the Python 1.5.2b1 source code from the official Python website.
  2. Unpack the Python 1.5.2b1 archive, and follow the instructions in BeOS/README exactly. The BeOS port isn't 100% finished yet, and it's the build process that still needs work.
  3. Before doing a the make ... install step, edit the top-level Makefile and change two lines in the libainstall: target. Originally, they look like this:
    		$(INSTALL_DATA) $(LIBRARY) $(LIBPL)/$(LIBRARY)
    		$(RANLIB) $(LIBPL)/$(LIBRARY)
    

    And you should change them to look like this:

    		#$(INSTALL_DATA) $(LIBRARY) $(LIBPL)/$(LIBRARY)
    		#$(RANLIB) $(LIBPL)/$(LIBRARY)
    

    Then do the make OPT=-DUSE_DL_EXPORT CCSHARED=-UUSE_DL_EXPORT MACHDEP=beos install step and Python should install properly.

Install heymodule

If you're on a PowerPC system:

  1. Download the heymodule package for Software Valet or the heymodule ZIP archive.
  2. Install heymodule:
    If you've got this... Do this:
    SoftwareValet package Double-click the package file and follow the on-screen instructions as you normally would with Software Valet.
    ZIP archive Unzip the archive:
    $ cd /boot
    $ unzip -o /path/to/py-heymodule-1.1-ppc.zip
    

If you're on an x86 system:

  1. Download the heymodule ZIP archive.
  2. Install heymodule:
    $ cd /boot
    $ unzip -o /path/to/py-heymodule-1.1-x86.zip
    

Note: The -o option to unzip means overwrite existing files. Be careful when you use it!

Installing heymodule puts all the magic bits (the heymodule.so shared library) into the right places (/boot/home/config/lib/python1.5/BeOS). It also installs a heymodule-1.1 directory in /boot/apps; look inside for this documentation, and a few example scripts.

Now what?

Well, now you can do application scripting from Python.

This isn't saying much though; scripting isn't well documented yet, and there isn't really a whole lot you can do that's useful.

The BeOS Bible will have an excellent chapter on Scripting when it's published in the next month or two. Sorry for the plug, I wrote that chapter. :-)

If you've never used Python before, you can find tutorials at http://www.python.org/, and there are several great Python books for beginners:

Using heymodule

Importing heymodule

Before you can use heymodule, you've got to import it into Python:

import BeOS.hey

heymodule contains two Python objects, so it's probably going to be easier to use if you import those objects directly into your namespace:

from BeOS.hey import Hey, Specifier

This will let you refer to the Hey object directly, instead of with a qualified name, BeOS.hey.Hey. Much nicer if you're using heymodule from an interactive session; you can do whatever you want in a Python program.

You probably won't need to use the Specifier directly; the Hey object's Specifier() method knows how to create one for you.


Note: heymodule creates a BApplication object; do not attempt to use it with any BeOS-specific module that also creates a BApplication. Something bad will happen. I don't know what, exactly, but you probably won't like it.

Creating a Hey object

The Hey object is your communications portal into the BeOS scripting world; you need one of these to do anything useful with heymodule. This handles all of the communication between your Python script and a BeOS application.

You can have as many Hey objects communicating with as many applications as you want, or they can all be communicating with the same application.

Hey's constructor requires a string; this string can be:

Creating a Hey object from an application signature or MIME type will attempt to launch the appropriate application if it's not already running.


Note: Creating a Hey object from an application signature or MIME type is reliable; using a thread name may fail for no apparent reason (not all threads are named equally, and programmers can change the names of their threads at will, even while the program is running).

Hey object methods

The Hey object supports the following methods; each of these communicates with the application specified in the Hey constructor:

Count( specifier ) Return a count of the objects specified by the specifier.

See Specifier, below.

Create( specifier ) Create an new instance of a property. This is untested.

See Specifier, below.

Delete( specifier ) Delete an instance of a property. This is untested.

See Specifier, below.

Get( specifier ) Return the given specifier's data.

See Specifier, below.

GetSuites( specifier ) Return a dictionary detailing the suites supported by the given specifier. Call with no specifier to get the application's supported suites. The most useful part of this message will be the names in the list with the key "suites".

See Specifier, below.

Load( path ) Tell the application to load the file specified by path. In hacker terms, this sends a B_REFS_RECEIVED message to the application.
Quit( specifier ) Tell the window specified by the given specifier to close; call with no specifier to tell the application to quit. Using a specifier doesn't currently work properly; quitting an entire application works fine.

See Specifier, below.

Save( specifier ) Tell the application to save the document specified by the given specifier. Call with no specifier to tell the application to save all of its documents. This is untested.

See Specifier, below.

Send( message ) Send an arbitrary message to the application. Currently you can only specify a "what" code for the message, you can't include extra data. message can be a number (such as 0x5f414252) or a four-character string (such as "_ABR"). Send() will return anything that's appropriate for the sent message.
SetBool( specifiervalue ) Set the given specifier to the Boolean value. The value should probably be one of 0, 1, "true", "false", "yes", "no", etc. Try your favourites but be warned; anything that isn't recognized as a true value will be considered false.

See Specifier, below.

SetColor( specifiervalue ) Set the given specifier to the colour value. The value can be a tuple of red/green/blue/alpha values (such as ( 255, 255, 255, 127 )), or you can specify the red/green/blue/alpha values separately (such as 255, 255, 203, 255). In either case, the alpha is optional and defaults to fully opaque (255).

See Specifier, below.

SetColour( specifiervalue ) SetColor() for those of us who can spell English words properly.
SetDouble( specifiervalue ) Set the given specifier to the double-precision floating-point value. Your application's documentation will tell you when you need to use a double instead of a normal float.

See Specifier, below.

SetFloat( specifiervalue ) Set the given specifier to the floating-point value.

See Specifier, below.

SetInt( specifiervalue ) Set the given specifier to the integer value.

See Specifier, below.

SetInt8( specifiervalue ) Set the given specifier to the 8-bit integer value. Your application's documentation will tell you when you need to use an int8 instead of a normal int.

See Specifier, below.

SetInt16( specifiervalue ) Set the given specifier to the 16-bit integer value. Your application's documentation will tell you when you need to use an int16 instead of a normal int.

See Specifier, below.

SetInt32( specifiervalue ) Set the given specifier to the 32-bit integer value. Your application's documentation will tell you when you need to use an int32 instead of a normal int.

See Specifier, below.

SetPath( specifiervalue ) Set the given specifier to the entry_ref that represents the path in value. You probably wanted to use Load( path ) instead, but SetPath() is handy if you want to send an entry_ref to a specific window.

See Specifier, below.

SetPoint( specifiervalue ) Set the given specifier to the BPoint value. The value can be specified by a tuple of X and Y values (such as ( 53, 192 )) or you can specify the X and Y values directly (such as 192.0, 53.0). You can also use integers or floating-point if you're feeling sassy.

See Specifier, below.

SetRect( specifiervalue ) Set the given specifier to the BRect value. The value can be specified by a tuple of left, top, right, bottom values (such as ( 5, 10, 15, 20 )) or you can specify them directly (such as 25, 30, 35, 40). Use integers or floating-point at will.

See Specifier, below.

SetString( specifiervalue ) Set the given specifier to the string value.

See Specifier, below.

Specifier( specifier ) Create a Specifier object used by many of these methods. If you call it with no arguments, you'll have to populate it using the Specifier Stack style with Specifier's Add() method. If you don't like that, you can pass in a string matching the hey command-line tool's specifier syntax.

See Specifier, below.

Unless stated otherwise, these methods will all return something reasonable for the given specifier. For example, if you're after the Frame of a Window, you're going to get back a rectangle (which will be a four-item tuple in Pythonese). If you ask for the Title, you'll get back a string.

If the Hey object doesn't know what the hell the other app was talking about in its reply, you'll get the entire message back; this is a Python dictionary, with keys matching the BMessage item names. Each key indexes a tuple, since BMessage can have multiple items under a single name.

Thus, if you got back a dictionary from one of the Hey methods, you could check the reply's error value like this:

import os
...

if reply.has_key( "error" ):
    # Get the first item out of the tuple... there isn't likely to be
    # more than one "error" entry anyway.
    value = reply["error"][0]
    
    if value != 0:
        print "It's the end of the world as we know it!"
        print os.strerror( value )

Note: BRect objects and rgb_color objects both look like four-item tuples to Python. The BRect will have floating-point values though.

Specifier objects

The Specifier object is used by Hey's methods to target a specific part of an application, such as a view inside a window.

Creating a Specifier - the hey way

You can create a full, ready-to-use Specifier by using Hey's Specifier() method or the Specifier constructor with a string:

spec = hey_object.Specifier( "Title of Window 0" ) ... spec2 = Specifier( "Title of Window 1" )

The string uses exactly the same syntax as the hey command-line tool. Specifically:

specifier_1 [of specifier_2 ... specifier_n]

Where a specifier is a property name (such as Title or Window; see your application's documentation for more information about the properties it supports) optionally followed by a qualifier:

Qualifier Description
number This is an index specifier.
-number This is an reverse index specifier.
[ start to end ] This is a range specifier.
[ -start to -end ] This is a reverse range specifier.
string This is a name specifier.

Properties without extra qualifiers are direct specifiers.

Consult your application's docs to find out what kind of specifiers it supports, and look in The Be Book's scripting topic for more information about properties and specifiers.

Creating a Specifier - the Specifier Stack way

The other way to create a usable Specifier is to create what The Be Book's scripting topic calls a Specifier Stack. Doing it this way is handy if you've got some docs that talk about application scripting for a C++ using audience; you can use almost the same syntax.

Step one is to create an empty Specifier using Hey's Specifier() method or the Specifier constructor:

spec = hey_object.Specifier()
...
spec2 = Specifier()

Now you use the Specifier object's Add() method to populate the Specifier:

# We're after the title of the first window:
spec.Add( "Title" )
spec.Add( "Window", 0 )

title = hey_object.Get( spec )
print "The title is:", title

The specifier stack is created from the most specific item (in this case, the Title) to the least specific (the Window).

Specifier's Add() method

The Add() method lets you add any kind of specifier to the stack:

Add( property ) Add a direct specifier for property.
Add( propertyname ) Add a name specifier for the property named name.
Add( propertynumber ) Add an index or reverse index (if number is negative) specifier for the property.
Add( propertystartrun ) Add a range or reverse range (if start is negative) specifier covering run items for the property.

Note: Do not mix Specifier's Add() method with a Specifier that was created from a hey-style string. I don't think it will work the way you expect.

Examples

Hiding and showing windows

Say you start campus from your UserBootscript, and you'd like to hide it's status window:

from BeOS.hey import Hey
x = Hey( "campus" )

# Windows have a "Hidden" property; if it's "true",
# the window is hidden, otherwise the window is
# shown.

s = x.Specifier( "Hidden of Window 0" )
reply = x.SetBool( s, 1 )

To make it reappear:

from BeOS.hey import Hey
x = Hey( "campus" )
s = x.Specifier( "Hidden of Window 0" )
reply = x.SetBool( s, "false" )

Getting the Title and Frame of a window

Let's start the preferred handler for text/plain files, and find the Title and Frame rectangle of its first window:

from BeOS.hey import Hey

x = Hey( "text/plain" )

# Set up a specifier for the Frame, using the hey style.
# The Frame is the rectangle where the window dispalys
# stuff, just inside the window border.

frame_spec = x.Specifier( "Frame of Window 0" )

# Set up a specifier for the title, using the specifier stack.

title_spec = x.Specifier()
title_spec.Add( "Title" )
title_spec.Add( "Window", 0 )

# Get the frame and title.

frame_rect = x.Get( frame_spec )
title = x.Get( title_spec )

# Now say what we got.

print "The window %s has a frame rectangle of %s." % ( title, frame_rect )

# Now tell the text/plain handler to quit.

x.Quit()

On my system, StyledEdit is the text/plain handler, and this script prints:

The window Untitled 1 has a frame rectangle of (7.0, 26.0, 507.0, 426.0).

Moving windows around

Let's start the preferred handler for text/plain files again and move its window around.

from BeOS.hey import Hey
from time import sleep

# Start the text/plain handler if it's not already running.

x = Hey( "text/plain" )

# Set up a specifier for its Frame.

frame_spec = x.Specifier( "Frame of Window 0" )

# Get the left, top, right, and bottom values for its Frame rectangle.

(left, top, right, bottom) = x.Get( frame_spec )

for foo in range( 5, 105, 5 ):
    # Looks like you need to refresh the specifier every time you want
    # to use it... bug or feature?
    #
    # It turns out that this is a bug of mine; with heymodule 1.1,
    # you don't need this:

    #frame_spec = x.Specifier( "Frame of Window 0" )

    # Move the window 5 pixels at a time:

    reply = x.SetRect( frame_spec,
        ( ( left + foo ), ( top + foo ), 
          ( right + foo ), ( bottom + foo ) ) )

    # Just to show that this is the same as using the "hey" style...
    # If you need to re-use a specifier like this, it'd be more
    # efficient to set it up outside the loop!

    frame_rect = x.Specifier()
    frame_rect.Add( "Frame" )
    frame_rect.Add( "Window", 0 )

    # Get and print the current frame rectangle.

    now_rect = x.Get( frame_rect )
    print "rect is now '%s'" % ( now_rect, )

    # Snooze for a second so we can actually see the window moving.

    sleep( 1 )

# Tell us where the script left the window.

frame_rect = x.Specifier( "Frame of Window 0" )
new_rect = x.Get( frame_rect )
print "The window is now at %s." % ( new_rect, )

Listing an application's windows

Say you want to know the titles of every window in the currently running Pe session:

from BeOS.hey import Hey

# Create a Hey object to talk to Pe.
pe = Hey( "pe" )

# Count its windows.
wind = pe.Specifier( "Window" )
num = pe.Count( wind )

print "Pe has %d windows..." % ( num )

# For every window, find its title.  We need to set up
# a specifier every time because we're aiming this
# message at a different window every time.

for i in range( num ):
    s = pe.Specifier()
    s.Add( "Title" )
    s.Add( "Window", i )

    name = pe.Get( s )

    print "Pe's window %d is named '%s'" % ( i, name )

Right now on my system I get something like this:

Pe has 7 windows...
Pe's window 0 is named 'Open'
Pe's window 1 is named 'Find'
Pe's window 2 is named '/boot/home/config/settings/pe/Worksheet'
Pe's window 3 is named 'Preferences'
Pe's window 4 is named '/boot/src/Python/Python-1.5.1/Modules/heymodule.cpp'
Pe's window 5 is named '/boot/src/Python/Python-1.5.1/Modules/heymodule.html'
Pe's window 6 is named '/boot/src/Python/Python-1.5.1/Modules/testhey.py'

Since I can't see windows 0, 1, and 3 on my screen, Pe must keep them around as hidden windows. As an exercise for the reader, change this to make all of the windows visible (or if you're feeling saucy, show all the hidden windows and hide all the visible windows).

Changes

1.1 for x86 (January 1, 1999)
1.1 (November 10, 1998)
1.0 (November 1, 1998)

Last modified: $Date: 1999/01/01 15:09:20 $


Copyright © 1998-1999, Chris Herborth (chrish@qnx.com)