Issue 2-42, October 22, 1997

Be Engineering Insights: Scripting Anyone?

By Peter Potrebic

Anyone interested in a brief example using the new scripting architecture in PR1 and PR2? I heard a few "yes's" so here it is.

For a long time developers have been asking for scripting support in the BeOS. We listened and we added such support in the PR1 release. In keeping with BeOS philosophy the goal was to add scriptability with minimal complexity. Hopefully we've achieved that goal. Let us know what you think.

For a full technical description see:

http://www.be.com/developers/may_dev_conf/transcripts/messagingandscripting .html

for a transcript of the messaging/scripting session at BeDC, or read about scripting in the Be Book:

http://www.be.com/documentation/be_book/app/scripting.html

Now I'll give a little example of how to create a class that is scriptable. Let's suppose you have a view class that can have one of three states. It also contains a rectangle that defines a hot spot inside the view. Let's suppose that you want to script those 2 properties. Here's what the class declaration might look like:

class TTriView : public BView {
      TTriView(BRect frame, int32 state, BPicture *pict)
  ...
  void  MessageReceived(BMessage *msg);
  BHandler  *ResolveSpecifier(BMessage *msg,
                int32 index,
                BMessage *specifier,
                int32 form,
                const char *property);
  ...
  void    SetState(int32 state);
  int32   State() const;
  void    SetHotSpot(BRect rect);
  BRect   HotSpot() const;
};

In the TTriView constructor you can see the 2 features of the class that we will make scriptable, its state and hot spot. The class also overrides the MessageReceived() hook function. It is in that function where the script messages will be handled.

The last overridden function is ResolveSpecifier(). This is the key function in terms of making a class scriptable. This is how a class advertises the "specifiers" that it supports. Let's start with this function:

BHandler *TTriView::ResolveSpecifier(
          BMessage *msg,
          int32 index,
          BMessage *specifier,
          int32 form,
          const char *property)
{
  BHandler  *target = NULL;

  if ((strcmp(property, "State") == 0) {
    if (form == B_DIRECT_SPECIFIER)
      target = this;
  } else if ((strcmp(property, "HotSpot") == 0) {
    if (form == B_DIRECT_SPECIFIER)
      target = this;
  }

  if (!target) {
    target = BView::ResolveSpecifier(msg, index, specifier,
                                     form, prop);
  }

  return target;
}

Let me explain a little of what's going on in the ResolveSpecifier() function. The class is declaring that it understands 2 properties, "State" and "HotSpot". For each of these properties it can handle DIRECT specifiers only. There are more kinds of specifiers, see the documentation in the Be Book for more details.

If the object can handle the message it should return itself (the this pointer). This tells the system that the scripting message should be handled by this object. Otherwise it is very important to call the inherited version of ResolveSpecifier() because some inherited class might actually understand the message, even if the derived class doesn't.

After getting directed to the correct object, a scripting message will be passed to the MessageReceived function. It is in this function that the message is actually processed:

void TTriView::MessageReceived(BMessage *msg)
{
  bool      handled = false;
  BMessage  reply(B_REPLY);
  status_t  err;

  switch (msg->what) {
    case B_GET_PROPERTY:
    case B_SET_PROPERTY:
    {
      BMessage  specifier;
      int32     form;
      const char *prop;
      int32      cur;
      err = msg->GetCurrentSpecifier(&cur, &specifier, &form,
        &prop);

      if (!err && (strcmp(prop, "State") == 0)) {
        if (msg->what == B_GET_PROPERTY) {
          reply.AddInt32("result", State());
          handled = true;
        } else {
          int32 new_state;
          err = msg->FindInt32("data", &new_state);
          if (!err) {
            SetState(new_state);
            reply.AddInt32("error", B_OK);
            handled = true;
          }
        }
      } else if (!err && (strcmp(prop, "HotSpot") == 0)) {
        if (msg->what == B_GET_PROPERTY) {
          reply.AddRect("result", HotSpot());
          handled = true;
        } else {
          BRect  new_spot;
          err = msg->FindRect("data", &new_spot);
          if (!err) {
            SetHotSpot(new_spot);
            reply.AddInt32("error", B_OK);
            handled = true;
          }
        }
      }
      break;
    }
    ...    // other 'cases' go here
  }

  if (handled)
    msg->SendReply(&reply);
  else
    BView::MessageReceived(msg);
}}

In the above code you can see that the class supports either GETing or SETing the 2 scriptable properties of TTriView. These properties can't be created or deleted (i.e. B_CREATE_PROPERTY or B_DELETE_PROPERTY). More complex classes will support these and other options.

When getting or setting the State of the view, the data being dealt with is an integer. For this reason the code uses FindInt32() and AddInt32() in the SET and GET cases respectively. In the HotSpot case the corresponding FindRect() and AddRect() functions are used.

Note that when setting an attribute, the data should always be found in an entry named "data":

  err = msg->FindRect("data", &new_spot);
  // —or --
  err = msg->FindInt32("data", &new_state);

When handling a GET message, the data being returned should always be placed in a entry called "result":

  reply.AddInt32("result", State());
  // —or --
  reply.AddRect("result", HotSpot());

Placing an error code in the entry "error" is another scripting convention worth noting.

The final step is to send a reply to the scripting message. Notice that a reply is sent whether the message is setting or getting data. In the GET case it is obvious that a reply is needed, because the requested data must be returned. In the SET case the class still returns a message to indicate the success or failure of the SET.

That's all there is to this simple scripting example. As always, if there are any questions just post them to BeDevTalk or send me a message.Content-Type: application/x-be_attribute; name="scripting.article"


News From The Front

By William Adams

Did you get the message? I'm sure I sent it, why haven't you responded? If you've been using BMessageFilters, then your brain cells can probably recognize this pattern of thought.

As written previously, BMessageFilters are wonderful things. They make dynamic extension using BLoopers and BHandlers relatively easy in the BeOS. I've gone over how you can use a filter to capture things like keydowns, mouse movements, and any other messages that are normally headed for your classes. Now I want to talk about some of the mechanics for using them in particular situations.

Did you know that after you use BLooper::AddCommonFilter(), your message filter is added to the bottom of a list. When a new message comes in to be processed, this list of filters is traversed from the top to the bottom. That is First in First Processed. This is an implementation detail, but it's necessary to know in order to achieve certain behaviors in message processing.

Here's a task. You want to create a user interface test harness. You need to capture and playback all keystrokes and mouse movement. You want to be able to create a script that will saves these events in some way for later playback. How can you do such a thing? With BMessageFilters, of course.

First of all, write a capture filter. All it has to do is get every message, examine it to see if it's a mouse or key message, and capture it if it is. It will not actually interfere with the processing of messages, it will just examine and capture the appropriate ones.

CaptureFilter::CaptureFilter()
  : BMessageFilter()
{}

filter_result
CaptureFilter::Filter(BMessage *msg, BHandler **target)
{
  switch (value)
  {
     case B_KEY_DOWN:
       // capture keydown
       break;
     case B_MOUSE_DOWN:
       // capture mouse down
       break;
  }

  return B_DISPATCH_MESSAGE;
}

Simply put, this filter will examine all messages, and capture for later playback all the ones that are either keydown, or mousedown. Simple enough. You can embellish it with the actual capture code. To use this filter, you can simply call AddCommonFilter on whatever window object whose events you want to capture. Easy enough.

Now let's say you want to do something a little bit more fancy, before messages are actually captured, you want to filter them out to eliminate excessive mouse movement, or you want to modify the events in some way before they are captured.

Well, you can simply call AddCommonFilter again and your filter is in the chain...but not in the right order. Remember, they are processed in first-in, first-processed order. We need to be able to change this processing order. The only really good way to do this is to sub-class the BLooper, or BHandler, and add the appropriate message filter list processing code yourself.

What we want to achieve is the reversal of ordering in the message list. There is no real easy way for you to change the processing order, so you need to change the order in which the items are added to the list.

virtual void AddCommonFilter(BMessageFilter *filter);

We can achieve what we want by implementing this single method. Reading the comments of this code will give you the flavor of what is going on.

void
MessageLooper::AddCommonFilter(BMessageFilter *filter)
{
  // Check for locking. We have to be locked before we
  // can proceed or multiple threads could be trying to
  // create the filter list at the same time.

  BLooper *loop = Looper();

  if (loop && !loop->IsLocked())
  {
    debugger("MessageLooper::AddCommonFilter - Owning Looper
              must be locked before calling
              AddCommonFilter");
    return;
  }

  // BLooper::RemoveCommonFilter doesn't reset the looper to
  // NULL when the filter is removed, so this check is not
  // valid after the filter is removed. This is a bug and
  // will be fixed in future releases. Until then, you might
  // want to disable the check.

  //if (filter->Looper() != NULL)
  //{
  //  debugger("A MessageFilter can only be used once.");
  //  return;
  //}

  BList *newList = CommonFilterList();
  if (0 == newList)
  {
    newList = new BList();
    SetCommonFilterList(newList);
  }

  // Most important line, this makes sure the items
  // are added to the top of the list instead of the
  // bottom.

  newList->AddItem(filter, 0);
}

Once you use this new AddCommonFilter, your filter list will act as a stack, with last-in, first-out processing. So you can add a filter that captures all messages, and doesn't send them on to the Looper until it has sent them out to the network and back, or whatever else you want to have happen. The main benefit of having your filters processed in this order is that you can layer on and supersede functionality, which you can't do with the default processing order.

It's only one method to implement, and you benefit from it tremendously in certain situations. I have found it to be quite useful, and my code is more flexible and dynamic for it.

As this newsletter approaches its 100th issue, there will be more authors participating than ever, bringing you more details of the BeOS to help you write that tractor driving killer app. I'll see you at the front.


One Step Behind...

By Jean-Louis Gassée

That's how I felt the morning after writing last week's column discussing the little contractual misunderstanding between Sun and Microsoft. A few hours after our newsletter hit the Net, Sun and Microsoft posted the text of their Java licensing agreement on the Web.

As my e-mail correspondents vigorously suggested, I read the contract, and I read it again, and I read it again. And I decided to wait for the Agenda conference held in Phoenix, Arizona, on October 20-21. Scott McNealy, Bill Gates, Stewart Alsop, Bob Metcalfe, John Doerr, Andy Grove and other luminaries were bound to help me understand the finer points of this dispute.

Right at the end of the Monday morning coffee break, I went back to the big ballroom, looking forward to Eric Schmidt's "Java as a soap opera" presentation for enlightenment—and entertainment. As I settled down, the conference organizers were playing one of the satirical videos customarily used to fill idle times.

On the big screen, Janet Reno was being ribbed for her ineffectual DOJ anti-trust pursuits and her political tin ear—I thought, until I realized it was a genuine real-time CNN feed and our Attorney General was taking on Microsoft again.

This time, Janet alleges, Microsoft is in breach of the 1995 consent decree overseeing the practice of bundling software products, forcing, I'm sorry, "incentivizing" customers to buy more Microsoft products together and crowding the competition out. Specifically, the DOJ alleges Microsoft uses its dominant market position to "force" manufacturers and consumers to buy the Explorer browser together with the Windows operating system in violation of the 1995 consent decree. Further, the DOJ complains, Microsoft uses non-disclosure agreements to compel their business partners to give them advance warning of government inquiries and to prevent them from volunteering information to the DOJ.

Burned by my poor timing last week, I'll wait. So far, Wall Street seems to be shrugging the complaint off: This morning Microsoft's stock is up and Netscape's is down—whatever that means.

Returning to Java, Eric Schmidt gave a very clever presentation, taking turns at all sides in the issue, Sun's, Microsoft's, and the rest of us. Later, we heard directly, very, from Scott McNealy as well as his mother, a rather assertive and golden-hearted individual who makes her son blush. And we were treated to a fireside chat with the Chairman. And the result is...more confusion.

Regarding the contract, listening to one side or the other is more instructive on issues of motive than on fact. Legal eagles are more helpful: Most of the ones not affiliated with either side seem to agree Microsoft is, or can easily put itself in at least narrow compliance with the agreement by posting missing bits, classes somewhere on their site without necessarily bundling them with their product. An irony at a time when Microsoft is sued for bundling other bits. But let's move from contractual Talmud to marketplace Kriegspiel. What are the possible moves?

Microsoft could decide to pick up its marbles and not offer a Java implementation at all. With 95% of desktop PCs, would this kill Java? Not necessarily, Sun or others can offer a Java VM for Windows and, voilà, Pure Java programs run on Windows, Java-powered Web pages work. This would allow Sun and its ally to proceed with their game plan: Expand the Java definition, and the underlying Java VM to the point of making Windows unnecessary and irrelevant. Java would morph into Java OS, sporting a myriad of applications, an alternative to Windows or, even better, its successor, more in tune with the new world of hardware-blind distributed computing applications.

This assumes much passivity from Microsoft. Developing a Microsoft-unfriendly platform by starting on top of Windows isn't an easy proposition. All sorts of technical glitches could "develop" along the way and lawyers salivate today when they fantasize about this direction. As in the old IBM days, the Java camp would need to force full disclosure of all internal Windows APIs. This could take a long time.

Another direction for Microsoft would be to abandon the Java label, call their "dialect" J++ and happily continue on their merry way having "embraced and extended" the language as advertised all along. Once the outcry dies, the Java consortium can offer a competing "Pure Java" VM and pursue the course outlined above. This is probably an even more difficult challenge: Imagine a J++ engine and a Java VM sitting on the same system. How would most customers feel about this? Which engine would the developers pick if they want to make money—as opposed to making a statement?

There are a few more options left. Microsoft could capitulate and tell Sun "I've sinned against you," Pure Java would run everywhere and progressively make Windows passé and hardware irrelevant. Possible in another universe, but not likely in this one.

What is perhaps a little more likely is some kind of muddy compromise. Sun is hinting darkly they don't want to settle for Microsoft to just pay the contractual $35 million in liquidated damages and go home. Suns wants Microsoft to be compelled by the courts to fully honor the licensing agreement, or, if I read correctly, pay much bigger damages for the harm allegedly done to the industry. Or settle for less in a mealy-mouthed compromise.

In the mean time, Sun is already reminding us that Java is much more important to new, emerging genres of computing devices such as appliances, network computers, smart cards, mobile communicators and rings opening doors, ski lifts and ATMs. This is, after all, a return to the original premise of First Person and Oak, respectively the originator and original name of Java. Please remember, I promised confusion.

One thread says Java is a way to re-open the market by creating a new computing platform capable of making Windows less of a toll gate. Another line of discourse states desktop computers matter a lot less than the many new genres of computing devices enabled by semiconductor technology and the communications infrastructure—and, above all, desired by consumers.

Which one will work? Your guess is better than mine.


BeDevTalk Summary

BeDevTalk is an unmonitored discussion group in which technical information is shared by Be developers and interested parties. In this column, we summarize some of the active threads, listed by their subject lines as they appear, verbatim, in the mail.

To subscribe to BeDevTalk, visit the mailing list page on our web site: http://www.be.com/aboutbe/mailinglists.html.

NEW

Subject: BeOS security

Speaking of security: What level of enforcement should an OS provide? Are security issues a matter of personal administration (as opposed to OS considerations)? Can security be enforced (programmatically) at the user level?

The discussion became more pertinent as our listeners exhorted Be to do the right thing. But which right thing? Some suggestions included:

  • Monitor and log all user activity.

  • Provide an encrypted TCP/IP stack with strong two-key encryption, strong cryptographic authentication, and variable-domain certificates

Paraphrasing Andrew van der Stock, who typed in a laundry list that fairly summarized the levels of security:

  1. Application: Apps should have a way of invoking security features.

  2. Underlying kernel services: should check that a desired resource is available to a client app.

  3. A coherent file system security model that allows acl's to be set, and that denies access to intruders.

  4. A coherent tree strategy that allows plug-ins for tree access.

  5. A TCP/IP stack that is immune from all known protocol exploits.

Subject: opinion on UI issue

From Josh Berdine:

...an app is running with a window open in one workspace and the user requests the app to open another window from a different workspace. What is the desired behaviour; should the new window open in the workspace the app is already running in or in the workspace the user is working in?

The current behaviour is that the desktop switches to the "old" workspace. Many readers think that switching workspaces is almost always undesirable—if you invoke an entity from workspace N, it should appear in workspace N. A proposed better way: Window clones in multiple workspaces (a la the Workspaces preference itself).

Relatedly, what should happen when you double-click an app icon for an app that's already running? Some folks think that the Single Launch approach is misguided: When you double-click an app, you want a new document (right?)—you don't want to be led to the documents that are already open (if you want the latter, so the argument goes, you can ask DeskBar to take you there).

Why bother with the programming pain of single-launch/multiple-doc? Apps are small and fast: Why not just launch another copy of the app for every document? The counter-argument: In an object-oriented world, it's not a pain to support multiple documents, and managing multiple documents (so you can quickly switch between them, for example) is certainly easier from a single running app. Regarding "small and fast": App images may be light in the BeOS, but that's no reason to waste resources.

Still relatedly, what happens to the Single Launch model when the BeOS is multi-user with X-style remote windows? You launch an app, then your friend launches the same app from a remote workspace. Here the "right thing" is (more) obvious: You clearly don't want to compete for the same app image.

And more: Duncan Wilcox doesn't like apps that steal window focus (such as CW's error message window). We second the disgruntlement.

Subject: What the?!?

Currently, the BeOS limits its RAMification to 512 meg. Dominic Giampaolo explains why:

The problem on PPC is that we use BAT registers to map physical memory and they can only map 256 megs each. We chose to use one [BAT] for the video card and [another for] ... other devices. We will boot with more than 512 megs of RAM but we just won't use it all. This is definitely not the ideal situation but since such large ram sizes aren't that common, the solution is acceptable until we can do better.

So what are the "better" solutions, and is Be working on it?

THE BE LINE: Mr. Giampaolo and Bob Herold (Be's Colonel of Kernels) reassured the crowd that, yes, the solutions are known and Be will fix this situation.

Subject: Live updates

Tracker features
Tracker questions

Tracker suggestions and observations (solicited by Be's Pavel Cisler):

  • How about a "Round Up" option that moves all icons (inside a Tracker window) within a spitting distance of each other? The "Clean Up" item snaps each icon to the nearest point in a grid, but if an icon is over yonder, it stays way over there.

  • Dragged icons should cast a shadow. Also, highlighted icons should glow rather than grow dim.

  • Command+V should create a file into which Pasteboard's current data is copied. Similarly, dragging cut data and dropping on the Desktop should create a file (on the Desktop) that holds the data.

  • The Tracker should provide an interface that lets the user set a file's icon. (Pavel says: Use the FileTypes app).

  • The pointer in a modified drag should change to indicate the type of operation that will be performed (i.e. move, copy, link).

  • Currently, the Tracker doesn't do live stat() updates—as an (open) file grows in size, the Tracker doesn't update the window that lists the file (it's only updated when the file is closed). This is a feature, sort of. As Dominic Giampaolo pointed out:

    The problem is that while your ftp transfer might be nice to see updated frequently, those same Tracker updates would be disastrous if you were trying to stream live video to disk.

Okay, so general updating may be overkill—But what if updates were on demand, where demand is signalled by an open Get Info window (as suggested by Chip Paul)?

Subject: Realtime Audio mixing and modification

Kuraiken started a thread about realtime audio signal processing. He'd like to see an API for 3D sound, room simulation, mixing, networked sound, and so on. Many folks wrote in to point out that much of what Mr. Kuraiken wants is here! Now! On your desktop!

Be doesn't have any built-in 3D sound or room simulation API, but mixing is trivial (although, as was pointed out, some apps do it incorrectly), and getting audio from the net is simply a matter of reaching out and touching someone.

The discussion broached the subject of multiple audio apps sharing a common data stream. Is it better to build separate processing *add-ons* that plug into a master app, or separate processing *apps* that subscribe to a global stream? The argument centered on the efficiency of multiple vs. single address spaces.

The major controversants, while exhibiting commendable politesse, remained respectively unmoved by the counterpoints. Look for this one to drag on into the night, break into a brawl, and end with up everybody drunk, overly sentimental, and pledging undying brotherhood.

Subject: BLoopers losing messages

A BLooper's message queue isn't infinite. It throws away messages that overflow its port's 100 message limit. Is there anything you can do to change the limit?

For a BLooper, yes—use the constructor; but for a BWindow, no -- you're stuck with the 100 message default. However, suggests Brian Stern, an app that needs a queue that's deeper than 100 messages is either badly designed or poorly implemented.

Peter Potrebic mentioned that he's looking into making BWindow more flexible with regard to message queue depth. John Cooke promptly rained on that little parade:

It is fine for Be to set whatever limit they like, but programmers must always be aware that *for all limits there exists a scenario where the limit will be reached* ...So whacking up the limit only gives one the illusion of solving the problem.

For calibration, Michael Crawford sent along a data point:

Just for comparison, the Mac OS event queue holds 20 (count 'em) 20 events. If the queue overflows, the new ones are still added to the end, while the ones at the head are dropped!

Back to Be: What happens in FBC land if the BWindow constructor is improved to accept a queue-setting argument? Will this break compatibility? Says Jon Watte:

Adding an additional constructor will not trigger the FBC problem, except it will make applications compiled with a newer version of the system, using newer features, not run on an older version of the system —which is to be expected in any system.

Subject: GUI Dreams

User Interface talk: Alexander Stigsen has been thinking about the principles behind an ideal interface. The keyword is configurability: Nearly every element of the UI should be user-definable. Mr. Stigsen breaks the UI world into two hemispheres: Desktop UI and Application UI, and says of them:

It should be possible to totally customize all the system elements of the desktop... It should be possible for the user to totally customize the GUI of every individual application on his/hers system.

Comments? Plenty. Jay O'Conor pointed out an obvious problem of ultra-configurability:

Documentation, ease of learning, usability, support, are all areas that suffer when an interface is too configurable... Appearances are generally ok to customize, as this effects only the 'look' of an interface, not the 'feel'. How the interface operates (feels) should remain consistent.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.