(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! |
Before you install heymodule you must install Python 1.5.2. Currently, it's only distributed as source code:
$(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.
If you're on a PowerPC system:
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:
$ 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.
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:
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. |
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). |
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( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | SetColor() for those of us who can spell English words properly. |
SetDouble( specifier, value ) | 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( specifier, value ) | Set the given specifier to the floating-point
value.
See Specifier, below. |
SetInt( specifier, value ) | Set the given specifier to the integer
value.
See Specifier, below. |
SetInt8( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | 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( specifier, value ) | 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. |
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 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.
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).
The Add() method lets you add any kind of specifier to the stack:
Add( property ) | Add a direct specifier for property. |
Add( property, name ) | Add a name specifier for the property named name. |
Add( property, number ) | Add an index or reverse index (if number is negative) specifier for the property. |
Add( property, start, run ) | 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. |
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" )
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).
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, )
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).
Last modified: $Date: 1999/01/01 15:09:20 $
Copyright © 1998-1999, Chris Herborth (chrish@qnx.com)