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: | Currently, Python only runs on PowerPC systems; Steven Black (one of Be's QA engineers) is helping to change this, but it's not ready for prime time yet. |
If you've already got Python 1.5.1 installed, you can grab the py-heymodule-ppc.zip or py-heymodule-ppc.pkg from BeWare's Languages section, or from my software pages.
If you haven't already installed Python 1.5.1, grab the python+heymodule-ppc.zip or python+heymodule-ppc.pkg from BeWare's Languages section, or from my software pages.
If you've downloaded one of the .pkg files, install it with SoftwareValet in the usual way.
If you've downloaded one of the .zip files, install it by unzipping it from the root of your boot drive:
$ cd /boot $ unzip /path/to/py-heymodule-ppc.zip
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.0 directory in /boot/apps; look inside for this documentation, and a few example scripts.
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. :-)
Before you can use heymodule, you've got to import it into Python:
import BeOS.hey
heymodule contains one Python object, so it's probably going to be easier to use if you import that object directly into your namespace:
from BeOS.hey import Hey
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.
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. |
The Hey object is your window onto the BeOS scripting world; you need one of these to do anything useful with heymodule.
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). |
The Hey object supports the following methods:
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.
This, if you got back a dictionary from one of the Hey methods, you could check the reply's error value like this:
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!"
Note: | BRect object and rgb_color objects both look like four-item tuples to Python. The BRect will have floating-point values though. |
The Specifier object is used by Hey's methods to target a specific part of an application, such as a view inside a window.
You can create a full, ready-to-use Specifier by using Hey's Specifier() method with a string:
spec = hey_object.Specifier( "Title of Window 0" )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 isn a 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.
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 the same syntax.
Step one is to create an empty Specifier using Hey's Specifier() method:
spec = hey_object.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).
The Add() method lets you add any kind of specifier to the stack:
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. |
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" ) 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" )
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: 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 prints:
The window Untitled 1 has a frame rectangle of (7.0, 26.0, 507.0, 426.0).
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 x = Hey( "text/plain" ) frame_spec = x.Specifier( "Frame of Window 0" ) (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? 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: 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, )
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. 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).
Copyright © 1998, Chris Herborth (chrish@qnx.com)