Issue 2-21, May 28, 1997

Be Engineering Insights: BScreens and Puppy Paws

By Scott Bronson

The beauty and sensibility of a final result often belies the time and effort that went into creating it. Good dancers appear to have been born moving effortlessly. An attractive, well-dressed human being likely didn't wake up looking quite so polished. Everything we see, from the most majestic landscape to the least significant spore, required a period of growth. Whatever the refinement of the result, this means that everything went through periods of lankiness and growing pains. Puppy paws appear far too big for the legs they're mounted on. A baby's head dwarfs its tiny body. When growing, dependencies require that some parts grow faster and be in place sooner than others. A browse through the DR9 Advanced Access header files will hint at things to come, like multiple displays. The header file Screen.h describes the new API for applications to obtain information about screens in a remarkably dynamic multiple screen environment.

Imagine a full multitasking operating system that will allow displays to come and go at will. Let's say the user is done at the office (imagine!) and—without restarting—unplugs his BeOS notebook from his 17" monitor, tucks it under his arm, and takes it home. Without any help, the OS notices that the 17" CRT is gone and the 14" built-in LCD display should be the new main screen. In a flurry of windows moving and updating, everything in the system shifts to the new configuration with no surprises for the user.

Certainly a nice image. But not without a few minor problems to solve. For instance, how does a program refer to specific screens when they can appear and disappear at will? It cannot use a pointer, as it is hard to tell when the pointer is invalid, and the consequences for misuse are dire. Neither can it use a "GetNthScreen" call, as the screen indexes will change when screens are added and removed (when monitor 1 is removed, monitor 2 becomes monitor 1, monitor 3 becomes monitor 2, and so on).

The solution is the screen_id, a unique, opaque 32-bit identifier, representing a screen attached to the computer. When a screen is disconnected, its screen_id falls into permanent disuse. When it is reconnected, it is assigned a new ID. Because screen_ids are meant to be lightweight, they are not persistent across boots. A solution involving far more than 32 bits will be developed later to persistently identify screens.

And, now the star of the show. The BScreen is an extremely lightweight, easy to construct class that represents a single screen. BScreens are meant to be constructed immediately when information is needed, and to exist for as short a time as possible. Although construction and destruction take insignificant time, some of the calls (such as BaseAddress and BytesPerRow) are slower than you might expect because they involve a synchronous round-trip AppServer request.

To prevent race conditions with the results of the BScreen functions, the display will not change when any BScreen objects referring to it are in existence. The display is locked in the BScreen's constructor and freed in the destructor so neither the Screen preferences panel nor anybody else will be able to change the horizontal or vertical size, bit depth, color palette, or anything else that might affect client applications. This is why it is extremely important to destroy the BScreen, allowing the display to change again, as soon as possible. A user won't react kindly to an application hogging his screen, and when informed which is the culprit (it's not hard to figure out who is misusing the BScreen), will remorselessly blow it away.

class BScreen {

    BScreen( screen_id id=B_MAIN_SCREEN_ID );
    BScreen( BWindow *win );
    ~BScreen();

The best way to construct a BScreen object is to pass in a window. The constructor will return the screen that contains that window (or the majority of the window). If you need more flexibility, you can pass the ID of the desired screen. Because BScreens are meant to be created on the stack, the destructor should be called automatically. Manually creating and deleting BScreens on the heap is a sure recipe for leaks (again, likely leading to an irate user and your application's unceremonious termination).

    bool    IsValid();

When screens can come and go at will, it will be possible for the BScreen constructor to be passed a screen_id which is no longer valid. The BScreen will be constructed anyway, but will refer to the main screen instead of the screen you asked for. This is so you don't have to do error checking every single time you construct a BScreen. Accidentally using the main screen will prevent crashing, and will generally produce acceptable results anyway. IsValid will return false if the BScreen refers to the main screen instead of the screen you asked for.

    color_space  ColorSpace();
    BRect        Frame();
    screen_id    ID();
    void*        BaseAddress();
    uint32       BytesPerRow();
    status_t     WaitForRetrace();

These calls are pretty much self-explanatory, except that WaitForRetrace is temporarily unimplemented (it just returns B_ERROR right now).

    uint8      IndexForColor( rgb_color rgb );
    uint8      IndexForColor( uint8 r, uint8 g, uint8 b,
               uint8 a=0 );
    rgb_color  ColorForIndex( constuint8 index );

The BeOS has a system color table that applies to every bitmap, every 8-bit screen, basically everywhere indexed color is used. VRAM is cheap enough now that if you need realistic color, it is much easier to simply set the display to 32-bit color than to manage and arbitrate the palette (even with full OS support). So why not make these calls global? Because even though this capability will likely go unused in the BeOS, the hardware DOES allow each individual screen to have its own color environment.

    uint8        InvertIndex( uint8 index );

Passed a color index, InvertIndex returns the index of a suitable inverse. It uses the inversion map returned by ColorMap()->inversion_map.

    constcolor_map* ColorMap();

ColorMap() allows you to save time by doing your own table lookups. Don't use the color map after its BScreen has been destructed. Suitably bad things will happen.

    rgb_color    DesktopColor();
    void         SetDesktopColor( rgb_color rgb,
                 bool stick=true );

These set the screen's desktop color. If stick is true, then it saves the change to disk. If not, then the desktop color change will last only until the computer is restarted.

};

inline uint8 BScreen::IndexForColor( rgb_color rgb )
  { return IndexForColor( rgb.red, rgb.green, rgb.blue,
    rgb.alpha ); }

Finally, a pinch of syntactic sugar.

Let's see how to use BScreen. First, a simple example that draws a rectangle, colored the inverse of the desktop color, in a view.

void MyView::Draw( BRect where )
{
    BRect area( 10, 10, 110, 110 );
    rgb_color deskcolor;

    BScreen screen( Window() );

    // Now, the screen containing the majority of the window
    // has been locked. It will not change while the
    // function is executing.

    deskcolor = screen.DesktopColor();
    if( screen.ColorSpace() == B_COLOR_8_BIT ) {
        // do the inversion calculation using indexed colors
        uint8 icol = screen.InvertIndex(
            screen.IndexForColor(deskcolor) );
        SetHighColor( screen.ColorForIndex(icol) );
    } else {
        // Invert the rgb_color
        SetHighColor( ~rcol.red, ~rcol.green, ~rcol.blue );
    }

    FillRect( area );

    // The screen variable is destroyed, and its display
    // released and allowed to change again, when this
    // function returns.
}

If the screen is currently in 8-bit mode, the inversion calculation is done using indexed colors. This is not strictly necessary—inverting the rgb_color would provide visually acceptable results—but this is a good example of how to manipulate color indexes.

Otherwise, if the screen is not in 8-bit-mode, the screen must be in a direct mode, and we'll calculate the inverse directly from the color's individual components. Finally, the rect is filled in, and the desktop color's inverse is on screen.

A somewhat more advanced usage of BScreen (snipped from the Connect source code):

void Border::MessageReceived( BMessage *m)
{
    constcolor_map *cm = BScreen( Window( )).ColorMap( );
    if (m->what < sizeof(cm->color_list)/
        sizeof(cm->color_list[0])) {
        SetViewColor( cm->color_list[m->what]);
        Invalidate( );
    }
}

First, notice that the BScreen object is anonymous—it was never given a name. The rules of construction and destruction still apply, so the BScreen will not be destroyed until the flow of execution leaves the enclosing brackets; in this case, when the function returns. Even in the worst case, Border::MessageReceived takes a tiny amount of time to execute, so this is acceptable. The variable cm will go out of scope when the BScreen is destroyed, so its validity is ensured for the duration of the function.

The conditional makes sure that the index m->what falls within the bounds of the color table. Then, using a simple table lookup (instead of the slightly more expensive ColorForIndex call), the appropriate rgb_color is passed to SetViewColor. Simple, straightforward, and there is no chance that the color table will change or disappear until the function returns.

BScreen is not meant to be used to lock the screen (though it can certainly be misused to do that for now). A more sensible way to allow applications to directly and asynchronously modify screen bits (i.e. DMA) is in development.

Do you remember get_screen_info and friends from the DR8 InterfaceDefs.h? They're still there in the Advanced Access DR9, but they are tiny little functions implemented using BScreens. They will go away in the full DR9 release. Use the old API at your peril.

So, yes, with BScreen I've added a tiny bit of lankiness to the DR9 BeOS. Lanky not because it's hard to use (at least, I sure tried to make it simple), but because significant parts of it are not used today. However, just as a dog's legs eventually grow to fit its paws, the BeOS will grow to fit its header files, and the result will be lean, fast, and powerful.


Be Engineering Insights: Unicode UTF-8

By Don Larkin

You may have heard that DR9 adopts the Unicode Standard for encoding characters and, in particular, the UTF-8 transformation of Unicode character values. You can read all about UTF-8 in "The Unicode Standard, Version 2.0" published by Addison-Wesley, but here's a synopsis for those of you that don't have the book.

Unicode is a universal encoding scheme for all the characters in the major scripts of the world—including, among others, extended Latin, Cyrillic, Greek, Devanagiri, Telugu, Hebrew, Arabic, Tibetan, and the various character sets used by Chinese, Japanese, and Korean. It assigns a unique and unambiguous 16-bit value to each character, making it possible for characters from various languages to co-exist in the same document. Unicode makes it simpler to write language-aware software (though it doesn't solve all the problems). It also makes a wide variety of symbols available to an application even if it's not concerned with internationalization.

Unicode's one disadvantage is that all characters have a width of 16 bits. Although 16 bits are necessary for a universal encoding and a fixed width is important for the standard, there are many contexts in which byte-sized characters would be easier to work with and take up less memory (besides being more familiar and backwards compatible with existing code). UTF-8 is designed to address this problem.

UTF-8 stands for "UCS Transformation Format, 8-bit form" (and UCS stands for "Universal Multiple-Octet Character Set," another name for Unicode). UTF-8 transforms 16-bit Unicode values into a variable number of 8-bit units. It takes advantage of the fact that for values less than 0x0080, the Unicode character set matches the 7-bit ASCII character set—in other words, Unicode adopts the ASCII standard, but encodes each character in 16 bits. UTF-8 strips ASCII values back to 8 bits and uses two or three bytes to encode Unicode values over 0x007f.

The high bit of each UTF-8 byte indicates the role it plays in the encoding: If the high bit is 0, the byte stands alone and encodes an ASCII value. If the high bit is 1, the byte is part of a multiple-byte character representation.

In addition, the first byte of a multibyte character indicates how many bytes it takes to represent the character: The number of high bits that are set to 1 (before a bit is 0) is the number of bytes in the encoding. Therefore, the first byte of a multibyte character will always have at least two high bits set. The other bytes in a multibyte encoding have just one high bit set.

To illustrate, here is how UTF-8 arranges the bits in one-, two-, and three-byte encodings (where a '0' or '1' indicates a control bit specified by the standard and an 'x' is a bit that contributes to the character value):

1:    0 x x x x x x x

2:    1 1 0 x x x x x   1 0 x x x x x x

3:    1 1 1 0 x x x x   1 0 x x x x x x   1 0 x x x x x x

Note that any 16-bit value can be encoded in three UTF-8 bytes. However, UTF-8 discards leading zeroes and always uses the fewest possible number of bytes, so it can encode Unicode values less than 0x0080 in a single byte and values less than 0x0800 in two bytes.

In addition to the codings illustrated above, UTF-8 takes four bytes to translate a Unicode "surrogate pair"—two conjoined 16-bit values that together encode a character that's not part of the standard. Surrogates are extremely rare. See "The Unicode Standard" for details.

The UTF-8 encoding scheme has several advantages:

The BeOS assumes UTF-8 encoding in most cases. For example, a B_KEY_DOWN message reports the character that's mapped to the key the user pressed as a UTF-8 value. The message has a data array named "byte" with each byte of the encoding as a separate item. The bytes are extracted from the array and passed as a string to the new version of KeyDown(), along with the byte count:

virtual void KeyDown(const char *bytes, int32 numBytes);

You can expect the "bytes" string to always contain at least one byte. And, of course, you can test it for any ASCII value without caring that it's UTF-8:

if ( bytes[0] == B_TAB )
    . . .

Similarly, BeOS objects that display text in the user interface—such as window titles and button labels—expect to be passed UTF-8 encoded strings, and will pass you a UTF-8 string if you ask for the title or label. Although the BFont class allows other encodings, which you may need to use from time to time, the system fonts are stuck with UTF-8. It's recommended that you stick with it as well wherever possible.


News From The Front

By William Adams

What kind of programmer are you? That's what I ask myself on a regular basis. Am I motivated by fame, fortune, prestige, or something like a desire to change the world?

Well, honestly, it's a combination of all of these. I've done the mission critical custom app thing to great success, and now I've moved on to more interesting things. When I program the BeOS, I'm excited. I mean really. Staying up until 4am for that "one more compile" just so you can see picture in picture video running on a standard BeOS machine?

Well, that's what I did this weekend.

ftp://ftp.be.com/pub/dr9/samples/videomania.tgz

I don't watch TV that much, but when a TV tuner/video capture card costs only $127 at Fry's, how could I possibly resist. The interesting thing about the Hauppauge board is that it has a tuner, and composite video inputs. You can switch between them on the fly. With two of these boards in your machine, you basically have 4 video input sources. This is better than my TV at home, and costs a lot less. I'm telling you, if you haven't already gone out and bought one of these capture cards yet, now would be a good time.

The other interesting thing about the Hauppauge board is that it works better at full screen rather than smaller. Why is that? DMA my fine fellow, DMA. The Hauppauge board, or more accurately the Brooktree Bt848 video capture chip, does DMA transfers directly into video memory if you tell it to. So full screen 60 fields per second 32 bit color looks pretty cool, and you don't need high energy machines to achieve this performance. When you display at a smaller scale, the Bt848 has to do some scaling. This doesn't really slow it down, but the quality isn't as good. The beauty of DMA is that the CPU doesn't really get involved. A lowly PowerPC 603 66MHz could do the job.

The thing that keeps me programming is the leap frog inspiration that really cool technology causes. Once you have one of these capture cards, you might think of ways to improve your "entertainment system." You might add easy access to the web and TV Guide, or you might find it interesting to add control of your FireWire-based VCR and CD players. Whatever your motivation, there's nothing like getting all excited about driving a tractor all over the apps that other poor farmers are still struggling with.

Judging from our mail and BeDevTalk postings, you're all very active with DR9. We're pumping out sample apps as fast as we can, and working on documentation. Keep sending us your input as to how we can better serve you. Without your efforts, this platform will be a technology looking for a solution. Keep up the good work and keep sending in that valuable praise and criticism. We in turn will continue to try and provide you with the best platform upon which you can dream up your killer apps.


Back From Europe

By Jean-Louis Gassée

I spent a week in the old country, meeting software developers, investors and business partners, mostly from France, but also from other parts of Europe. We've invested early in Europe, based on a combination of beliefs and connections. The connections relate to my checkered past in the computer industry over there. I worked in the French as well as the European HQ of my alma mater Hewlett-Packard, at Data General, at Exxon Office Systems (briefly, quickly recognizing the error) and starting Apple France, before fulfilling an old dream and moving to the Silicon Valley. As a result, when we started the company, we benefited from a hospitable European network. Friends invested in the company and introduced us to other investors; we abducted people to California and linked up again with associates from earlier lives.

In many respects, the company probably wouldn't be around if it weren't for our European connections. In 1991, when we started raising money, US investors were understandably reluctant. Microsoft was already perceived as the invincible juggernaut—the agility came later when they became a born-again Internet company. At the time, PDAs, set-top boxes and 3DO game consoles were in fashion and we couldn't quite demonstrate our OS side by side with older platforms. The "no legacy, no baggage" equation was hard to explain.

In so called "high-tech" industries, Europe is perceived as having fallen behind the US. In several instances, it is even true as in the case of the PC business. Companies such as Olivetti or Nixdorf were once industry leaders. Their misfortunes haven't dulled European appetites for new technology. In many respects "conservative" Europeans are more daring than New World players. VCRs, CDs and, more recently, the Macintosh and digital cellular telephones enjoyed a better early acceptance in the old continent than in the US. (Once the new standards get accepted in the US, the market quickly makes up for a late start.)

In Be's case, our unconventional proposition found early takers in Europe. Some investors were not awed by the Microsoft "Über alles" mystique, others saw in us the Silicon Valley connection. One of them told us bluntly he'd "never invest in a deal like this based in Europe"...

European developers react in a similar fashion. First, in an apparent paradox, just like the blunt investor, many local developers would be reluctant to bet their time and energy on a European media OS. And, second, our evangelism efforts got "traction" earlier in Europe than in the US. Does it mean the famous "tractor app" will come from Europe rather than the US? It is still too early to tell, but we see another positive factor in our European connection. It is even harder to make money in the software business in Europe than it is in the US. As a result of our use of the Net to market and deliver software, European developers see the BeOS as a way to break into the US market unavailable with the conventional distribution network. Our goal is to make their hopes come true.

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