Issue 4-16, April 21, 1999

Here Comes the First Big Marketing Wave

By Roy Graham

I'm nearing the end of my first month at Be and it has been the most exciting and inspiring start that I have had at any company. I've been particularly inspired by the passion shown by you, our Developers. Never before have I seen such commitment to help a company succeed. Your suggestions and comments come in thick and fast—some negatively worded, most positive—but all with a passion that tells me you're trying to help us all be even more successful. I love that.

Be has, you tell me, delivered great technology. Now it's time to complement this with aggressive marketing, so we can rapidly expand the user base to which you can market your applications. We're planning a major marketing campaign for the second half of this year, with the express goal of raising the market awareness of Be significantly, and also of achieving a major expansion of the user base. Make plans to attend PCExpo and be a part of our campaign kickoff.

In order to start our campaign with a bang, we needed a great product platform. When I started at Be I spent a lot of time with Be developers, learning the 4.1 release and where we were in the release cycle. It quickly became apparent that it would not be ready for prime time until June. You've come to expect very high quality from us and we're not about to change that. So, late June is when to look for our next release.

Next we needed to decide what to call the release. Unfortunately, we'd backed ourselves into a corner by calling it 4.1. Not any more. From now on, all future releases will have a project name that is unrelated to release numbers. Accordingly, 4.1 is now called Genki (Japanese for "all is well"!!).

Closer to the release date we'll decide the exact version number (OK, it's marketing, but give me some slack—I'm trying to help you by expanding the user base for your applications!!). The good news is that Genki (like 4.1) will remain an update, so it's free to all BeOS Release 4.0 users.

Other news that I announced at the BeDC included our expanded commitment to the Japanese market, as attested to by the hiring of our first employee in Japan, Takeaki Akahoshi, as Channel Sales Manager. Also, I've initiated a crash program to get BeDepot into reasonable shape for now, with a view to replacing it later this year with a longer term solution.

Finally, this quarter you'll see changes on the Be web site. We are adding a commercial front end to give it a more polished look and feel, with a view toward appealing to a wider audience.

So, I'm delighted to be on board and I look forward to working with you as we ride the first big marketing wave.

Be Engineering Insights: Son of Teletype Meets Multimedia

By Rico Tudor

I write this article a few days after the BeDC devoted to the Media Kit. There, the air was abuzz with MP3, AVI, EXIF, and other esoteric encodings. And yet, the lowly data format called ASCII lives on in the waning light of the second millennium. Long after everyone has forgotten how to interpret the data flavors of this month, there will still be a way to read those old e-mail messages. If you can imagine the BeOS Terminal app as the MediaPlayer for ASCII, then I will discuss part of the ASCII kit cryptically named the pseudo-tty driver.


The term "tty" is a contraction of Teletype, maker of such famous icons as the Model 37 terminal. Printing noisily on paper, with margin bell and optional paper tape punch, it was a multimedia device in its own right. Historians can review its output in the early service of UNIX at

The standard configuration in those days was

shell—tty driver == tty

where "--" indicates a user/kernel space interface, and "==" is a serial cable. Hard copy terminals were replaced in time by "glass" ttys, and ttys were replaced altogether by the PC (running a terminal emulator program):

shell—tty driver == tty driver—tty emulator

As the PC gained CPU power and OS sophistication, there was no need for the command line (shell) to reside on a distant, and expensive, mini-computer or mainframe. This has resulted in entirely local plumbing like so:

shell—pty driver—tty emulator

In essence, the pseudo-tty, or pty, driver acts as a tty driver with serial line looped back. This concept was pioneered by the Berkeley version of Unix, and is now universally available on UNIX and UNIX-like systems.


Two distinct, but related, reasons for using the pty driver are to direct interactive programs, and to provide tty services for them. These reasons apply to command line programs only; BeOS apps that function graphically are not going to be interested.

An interactive program tends to write stdout unbuffered or line-by-line. This is less efficient, but operates sensibly with alternating prompts and user input. The user can better see realtime progress, too. Posix libraries determine whether the output is a "terminal" and, if not, buffer output fully. Unfortunately, this means that running an interactive program through a pipe doesn't work well:

(sleep 1; echo bc; echo 1; echo '2^99999/2^99998') | sh
(sleep 1; echo bc; echo 1; echo '2^99999/2^99998') | sh | cat

In the first case, the shell is instructed to start the desk calculator bc, which evaluates arithmetic expressions. Since the output is a terminal, we see each result as it's ready. In the second case, bc is writing to a pipe, and all results are buffered until it exits.

Some programs behave even more radically when the output is a terminal. Compare

ls /bin
ls /bin | cat

The general desire in all these cases is to control the shell and its subprocesses with our own program, while appearing to be a terminal at the far end of a serial line.

The second reason for using the pty driver is to provide tty services, which includes input echoing, character erase (ctrl+H), signals (ctrl+C), etc. The services in BeOS are a subset of the Posix "termios" standard. As an example, the Backspace key (ctrl+H) erases the last character on the current line of input; a program like bc will see the fully formed line only when you hit Return.

Current BeOS clients of the pty driver include "telnetd" (incoming telnet connections), /bin/getty (shell session on serial port), Terminal, and Pavel's Eddie (C++ development environment).

The remainder of this article documents the code for a useful app called "script." Modeled after the similarly named UNIX utility, it starts an interactive subshell such that all I/O activity is recorded verbatim to a log file, called "typescript." The code consists of two parts: first, a reusable class called PTY and second, the particular use of this code for "script."

The Code

Here is the declaration for the PTY class, which conveniently wraps the pty driver. A program like "script" has quite a few chores, but these are handled almost entirely by PTY. To use, you must derive your own class from PTY, and define the hook functions GetData(), PutData(), and Done(). Their purpose is described later.

#include        <Application.h>
#include        <signal.h>
#include        <stdlib.h>
#include        <stdio.h>
#include        <string.h>
#include        <termios.h>
#include        <dirent.h>

#define ctrl( c)        ((c) - 0100)

struct PTY {
                     PTY( );
                     ~PTY( );
     bool            Init( char *[]);
     virtual int     GetData( uchar [], uint)        = 0;
     virtual int     PutData( uchar [], uint)        = 0;
     virtual void    Done( )                         = 0;
     bool            ptyinit( );
     static int32    feeder( PTY *),
                     drainer( PTY *);
     struct termios  tsaved;
     int             ptyfd,
     thread_id       fid,
     char            tty[40];

A pair of devices are employed in a pty connection: master and slave. If the master is /dev/pt/p0, then the slave will be /dev/tt/p0. The shell will inherit file descriptors to the slave, while "script" directs through the master side. Finding an available pair uses the traditional (ok, baroque) method: try to open each master device until one succeeds. That master device is now yours exclusively. Here is the code:

bool PTY::ptyinit( )
   DIR *dd = opendir( "/dev/pt");
   while (dirent *d = readdir( dd))
      if (d->d_name[0] != '.') {
         sprintf( tty, "/dev/pt/%s", d->d_name);
         ptyfd = open( tty, O_RDWR);
         if (ptyfd > 0) {
             sprintf( tty, "/dev/tt/%s", d->d_name);
             ttyfd = open( tty, O_RDWR);
             if (ttyfd > 0) {
                     closedir( dd);
                     return (TRUE);
             close( ptyfd);
   closedir( dd);
   return (FALSE);

The "." and ".." directory entries need to be skipped.

script must maintain two data paths. The first comes from the stdin of script, goes through the pty driver from master side to slave side, and finally to the shell's stdin. The second path originates from the shell's stdout, goes through the pty driver from slave side to master side, and finally to the stdout of script. It is that stdout which is cloned off to the log file, typescript. Each data path has a dedicated BeOS thread, using the following code:

int32 PTY::feeder( PTY *pty)
    uchar   data[1000];
    int     i;

    while ((i = pty->GetData( data, sizeof data)) > 0)
            write( pty->ptyfd, data, i);
    pty->Done( );

int32 PTY::drainer( PTY *pty)
    uchar   data[1000];
    int     i;

    while ((i = read( pty->ptyfd, data, sizeof data)) > 0)
            pty->PutData( data, i);
    pty->Done( );

The read and write system calls typically block until data is available, which is why we need the dedicated threads. This also ensures the desired data flow control. In the idle state of script, the feeder thread is blocked in a read of stdin, while the drainer thread is blocked in a read of the master side pty. Meanwhile, the team's main thread can service BApplication.

The constructor/destructor code saves and restores the tty mode of script stdio. The destructor must kill the feeder and drainer threads, since they are most likely blocked where polite methods, like semaphores, cannot help.

    fid = did = -1;
    tcgetattr( 0, &tsaved);

PTY::~PTY( )
    tcsetattr( 0, TCSANOW, &tsaved);
    if (did > 0)
        kill_thread( did);
    if (fid > 0)
        kill_thread( fid);

Take a deep breath! This code exercises both BeOS and Posix functions, and is where the bureaucratic details are hiding. Our pty connection consists of slave side file descriptor "ttyfd" and master side "ptyfd". The slave side tty mode is set to something human friendly. Between the fork() and the execv(), we have a chance to customize the execution environment of the subshell. This includes its stdin, stdout, stderr, name of controlling tty device, and its own process group for signals. The subshell will have no knowledge of "ptyfd", or the execution environment of "script." If the subshell exits, "script" will get master side i/o errors; if script exits, the entire subshell session will get a hangup signal (SIGHUP).

bool PTY::Init( char *com[])
   if ( ! ptyinit( )) {
       fprintf( stderr, "no available pty\n");
       return (FALSE);
   struct termios t;
   struct winsize w;
   memset( &t, 0, sizeof(t));
   t.c_iflag = ICRNL;
   t.c_oflag = OPOST | ONLCR;
   t.c_cflag = B19200 | CS8 | CREAD | HUPCL;
   t.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHONL;
   t.c_cc[VINTR] = ctrl( 'C');
   t.c_cc[VQUIT] = ctrl( '\\');
   t.c_cc[VERASE] = ctrl( 'H');
   t.c_cc[VKILL] = ctrl( 'U');
   t.c_cc[VEOF] = ctrl( 'D');
   tcsetattr( ptyfd, TCSANOW, &t);
   if (ioctl( 0, TIOCGWINSZ, &w) == 0)
       ioctl( ptyfd, TIOCSWINSZ, &w);

   switch (pid_t pid = fork( )) {
   case -1:
       fprintf( stderr, "system too busy\n");
       return (FALSE);
   case 0:
       char te[30];
       pid = getpid( );
       setpgid( pid, pid);
       ioctl( ttyfd, 'pgid', pid);
       signal( SIGHUP, SIG_DFL);
       sprintf( te, "TTY=%s", tty);
       putenv( te);
       close( 0);
       close( 1);
       close( 2);
       dup( ttyfd);
       dup( ttyfd);
       dup( ttyfd);
       close( ptyfd);
       close( ttyfd);
       execv( com[0], com);
       fprintf( stderr, "cannot exec %s\n", com[0]);
        exit( 0);
   close( ttyfd);

   t = tsaved;
   t.c_iflag &= ~ ICRNL;
   t.c_oflag &= ~ ONLCR;
   t.c_lflag &= ~ (ISIG|ICANON|ECHO|ECHOE|ECHONL);
   t.c_cc[VMIN] = 1;
   t.c_cc[VTIME] = 0;
   tcsetattr( 0, TCSANOW, &t);

   fid = spawn_thread(feeder, "f", B_NORMAL_PRIORITY,
   did = spawn_thread(drainer, "d", B_NORMAL_PRIORITY,
   if (fid < 0 || did < 0) {
       fprintf( stderr, "cannot spawn a thread\n");
       return (FALSE);
   resume_thread( fid);
   resume_thread( did);
   return (TRUE);

With the subshell launched, script must complete its own initialization. Its own tty mode is set to "no echo" and "raw" (no interpretation). This gives complete control of tty services to the remote shell and its minions ("stty," text editors, etc). Finally, the feeder and drainer threads are started.

That concludes the PTY class. With so much work already done, the "script" app is almost a footnote. The boffo Script class, below, is compactly defined with multiple inheritance.

struct Script: BApplication, PTY
    Script(char *sig, char *c[], char *log): BApplication(sig)
        com = c;
        logfd = creat( log, 0666);
        Run( );

    void ReadyToRun()
        if (logfd < 0)
            fprintf( stderr, "cannot create logfile\n");
        else if (!Init(com))

    void Done()
        Unlock( );

    int GetData(uchar data[], uint n)
        return (read(0, data, n));

    int PutData(uchar data[], uint n)
        write( logfd, data, n);
        return (write( 1, data, n));

    char **com;
    int logfd;

    static char *com[] = { "/bin/sh", 0 };
    Script s( "application/script", com, "typescript");

    return (0);

The GetData() hook is called by PTY to get more data for the slave side. The PutData() hook is called when data from the slave side has arrived. The Done() hook is called when PTY has detected that the subshell has exited. The Be app can initiate a shutdown at any time by inducing a PTY destructor call.


To compile script, clip out the code to script.cpp, and type

gcc script.cpp -lbe

Example 1: Capture some command output.

$ script
sh-2.02# date
Fri Apr 16 02:01:26 CDT 1999
sh-2.02# exit
$ cat typescript
sh-2.02# date
Fri Apr 16 02:01:26 CDT 1999
sh-2.02# exit

(Hmm, says something about my schedule.)

Example 2: A fully scripted Terminal.

Terminal script

Of course, this doesn't work if you are accessing your machine remotely (e.g., using "telnet"); see Example 1 for this situation.

Example 3: Interactive program under program control.

(sleep 1; echo bc; echo 1; echo '2^99999/2^99998'; sleep 60)
| script

Compare the behavior and output with the earlier bc example; the "sleep 60" is needed because an early EOF to script will cause it to shut down, and lose the long calculation. Given a command language and bi-direction data flow to/from the target program, you can create a powerful program-driving utility (like the popular "expect").


The content of "typescript" is a chronological record of all output, both from programs and your own echoed input. It will likely contain ASCII Return (octal 015), Bell (007), Backspace (010), Tab (011), and any ANSI escape sequences. All these effects are faithfully reproduced with

cat typescript

You can see the uninterpreted stream by using a text editor or with

od -c typescript

"typescript" is quite different from Terminal copy-n-paste, where you are selecting inert glyphs—different tools for different purposes.

Be Engineering Insights: Duty Now for the Future

By Howard Berkey

Besides being the name of a great DEVO album, the title of this article represents what I'd like to do with my space in this week's newsletter. That is, describe some of the upcoming features in BeOS networking.

For Genki, the most visible change from a developer's standpoint is the new C++ networking API. This was shipped in the 4.1 beta CD's Experimental folder, and will be a part of the main Genki distribution. The entire kit may be accessed by including NetworkKit.h and linking against and The new API introduces three main classes: BNetEndpoint, BNetAddress, and BNetBuffer. These classes simplify creating and using network data connections, addresses, and data, respectively. Documentation is forthcoming; until then, this newsletter article and comments in the headers should be enough to get you started.


The BNetEndpoint class abstracts network communication endpoints—think of it as a "socket object." BNetEndpoint takes care of all the necessary initialization in its constructor; it makes creating network connections very simple:

int32 send_data(const char *host, short port,
const void*data, int32 datalen)
    BNetEndpoint comm(SOCK_STREAM);
    int32 rc = -1;

    if(comm.InitCheck() == B_NO_ERROR)
        if(comm.Connect(host, port) == B_NO_ERROR)
            rc = comm.Send(data, datalen);
    return rc;

The above code creates a TCP connection to the specified hostname and port, sends the data passed in, and automatically closes the connection, returning the amount of data sent, or -1 on failure. As you can see, much of the setup and housekeeping typically required when doing network programming is taken care of by the BNetEndpoint object.

Many BNetEndpoint member functions map orthogonally to the BSD sockets API; it's also possible to get the socket descriptor out of a BNetEndpoint for use with the standard sockets API. See NetEndpoint.h for details.


Just as you can think of BNetEndpoint as a class that encapsulates the BSD sockets API, BNetAddress is a class that encapsulates the functionality found in netdb.h. BNetAddress is a convenient way to store, resolve, and manipulate network addresses.

BNetAddress is used to represent network addresses, and provide useful access to a network address in a variety of formats. BNetAddress provides various ways to get and set a network address, converting to or from the chosen representation into a generic internal one.

BNetAddress::SetTo() is used to set the object to refer to the address passed in, which can be specified in a variety of formats (omitting InitCheck()ing):

void addrs()
    BNetAddress addr;
    struct sockaddr_in sa;
    in_addr ia;

    addr.SetTo("", 80);
    addr.SetTo("", "tcp", "http");

    // ==
    ia.s_addr = inet_addr("");
    addr.SetTo(ia, 80);

    sa.sin_family = AF_INET;
    sa.sin_port = htons(80);
    sa.sin_addr = inet_addr("");

All calls to addr.SetTo in the above example are equivalent.

Similarly, BNetAddress::GetAddr() returns the address referred to by the BNetAddress object in a variety of formats. For example, the following code converts sockaddr_in structures into human-readable hostnames:

void sin_convert(const struct sockaddr_in &sa)
    BNetAddress addr(sa);
    char host[256];
    unsigned short port;

    addr.GetAddr(host, port);

    cout << "Host: " << host << "Port: " << port << endl;

Likewise, to resolve a host name into a sockaddr_in:

struct sockaddr_in resolve(const char *host)
    BNetAddress addr(host);
    struct sockaddr_in sa;


    return sa;

As you can see from these examples, BNetAddress has a number of constructors that mirror its SetTo() member functions. BNetAddress also takes care of any byte order conversion that's necessary with network addresses.

BNetEndpoint is designed to work closely with BNetAddress; any addresses it expects can be represented by a BNetAddress, such as in BNetEndpoint::SendTo().


Unlike BNetAddress and BNetEndpoint, BNetBuffer has no analog in the BSD sockets API. BNetBuffer is a convenient way to manage network data that is to be sent across the wire, including mundane tasks such as byte order conversion. BNetBuffer is a network data container class. As I mention in NetBuffer.h: BNetBuffer is a dynamic buffer useful for storing data to be sent across the network. Data is inserted into and removed from the object using one of the many Append() and Remove() member functions. Access to the raw stored data is possible. The BNetEndpoint class has a Send() and Receive() function for use with BNetBuffer. Network byte order conversion is done automatically for all appropriate integral types in the Append() and Remove() functions for that type.

BNetBuffer is best illustrated by an example. Suppose you have a transaction protocol between a network client and a server application which specifies that whenever the client receives a BMessage, it forwards it to the server over the network connection, along with a client timestamp and a transaction ID, which is a monotonically increasing uint32. It could be coded thusly:

static uint32 gTransID = 0;


int32 forwardBMessageToServer(const BMessage &msg,
BNetEndpoint &serverConn)
    BNetBuffer packet, sizepack;
    bigtime_t timestamp;
    uint32 trans_id, size;

    timestamp = system_time();
    trans_id = atomic_add(&gTransID, 1);


    size = packet.Size();

    return serverConn.Send(packet);

Voila! Networked BMessages. BNetBuffer does all the necessary byte order conversion, so the server simply needs to call the matching Remove() member functions to get at the data the client was sending.


Astute readers of the header files will have noticed by now that the C++ Networking API classes are all derived from BArchivable. In the case of BNetAddress and BNetBuffer, the archived data is just what you would expect; a byte-gender-neutral archive of the address info or network data, respectively.

For BNetEndpoint, it goes one step further—network connections are archived. For example, if you're connected to a host and port when you archive the BNetEndpoint object, later instantiation of that archive returns a BNetEndpoint that is connected to the same host and port.


There's also a class called BNetDebug, which provides (surprise!) some debugging functionality. Calling BNetDebug::Enable(true) turns Net API debugging on; you can then call BNetDebug::Dump() with buffers of data for a nice hd-like output when you run your program from the Terminal. Many BNetEndpoint calls generate debugging output when debugging is enabled; play around and you'll see what I mean. The debug messages are printed to stderr.

Wrapping It Up

This overview should be enough to get you started with the new C++ networking API. But what would a newsletter article be without some sample code for you to play with? You can find the source code for a simple FTP client I wrote using the new networking API at: kit/

Developers' Workshop: Media Kit Basics: Muxamania Part 1: The Media Control Panel Application

By Owen Smith

This week I've cooked up one of several Media Kit goodies that was requested at the BeDC. It's a node, completely encapsulated in a media add-on, that allows you to select one of several inputs to pass to the output.

This code works with the upcoming beta release, genki/5. Go grab it from:

In this article, I'll illustrate how to create a simple UI for the node. In the next installment, I plan to show you how the node handles buffers and manages connections.

In Selector, I want users to be able to select the input they want routed to the output. But, since the acrid smoke from BeDC preparations still hasn't cleared from my Kube, I don't want to spend a lot of time working on the UI. I'm looking for the simplest fire-and-forget mechanism I can come up with.

In this case, BMediaRoster::StartControlPanel() is definitely the answer. It's designed to take care of all the details of running a UI for our node, with no sticky residue. Unless your node says otherwise, StartControlPanel() simply launches your add-on as an application. The ID of the node that you're editing will be passed to the newly launched application as a command line argument.

There's one piece that is left for you, the add-on writer, to fill in: you must determine what the add-on does once it's launched. Generally, this involves displaying a window containing controls for editing the node.

I've created a simple class to do most of the work for you: MediaControlPanelApp. This BApplication-derived class does the following:

Now, what does it take to get this awesome functionality in your add-on?

int main()
    MediaControlPanelApp app;
    return 0;

... Not too much, I guess.

In conclusion, I'd like to extend greetings to all of the developers I managed to meet in person at the BeDC. Even jacked up on a week's worth of Dew and adrenaline, I somehow managed to relax a bit (especially after the presentations were over!), and it was a singular treat for me to hang out with you, the people that make this job really worthwhile.

More Better Formats...and a New Wing for the Great Roach Motel

By Jean-Louis Gassée

It is a thing of beauty. We must take our hats off to Microsoft, again -- this time for two reasons: last week's announcement of MS Audio and the soon-to-be-released Office 2000. Seriously, one has to admire the strategic foresight and speedy execution, not to mention the way that Microsoft moves the battle to a new field, leaving behind browser and contractual skirmishes.

Let's start with the roach motel metaphor. It refers to file formats and their use to trap files and their owners—and their wallets. As the old slogan goes, "They can get in, but they can't check out."

Let's say that your business is CAD software. Files store complex designs in a structured way, ready to be fed again into a kind of interpreter that can further edit the design and render it on a screen or on paper, on some PCB layout, or on chip-making equipment. The file format helps your business in two ways, apart from offering rich constructs, reliability and speed: 1) It can keep your competitors from seducing your customers; and 2) It can help you generate revenue with new releases.

Both outcomes exhibit the same wonderful, hard to unscramble, mix of legitimate and manipulative characteristics. In some cases, the law is on your side and lets you protect trade secrets or even grants you a patent, as we've seen for some formats. Or, you can keep your competitors guessing or running if you hide and modify at the appropriate pace. In addition, a new and richer format "encourages" customers to upgrade if they want to continue exchanging files. In any given group, one or two users of the new version is all it takes, and the newer/richer CAD cannot be read by the old version, without some information being lost.

We saw this game played with Office 97. Initially, in spite of using the same three-letter suffix, Word 95 could not read the .doc files stored by Word 97 users, thus encouraging users to move to the new version. But users were not encouraged, they were enraged, thus in turn encouraging Microsoft to issue a fix to allow interoperation of .doc files. In another instance, the use of the RTF format is now suggested by the Word assistant. RTF is referred to as an exchange format that other non-MS word processors can read. But if my information is correct, this is only partly true. If you use "shapes" in your Word 97 document and store it in RTF format, you might believe the document will "interoperate" with RTF-capable word processors, but you might lose the drawings inserted in the text. Again, I'm not saying this is automatically a bad thing, but you see how it could be manipulated, used to thwart competitors and to get more revenue from the installed base. A good example of the hard-to- unscramble mix. But this is the old style roach motel.

Windows begat Explorer and Explorer begat Microsoft's version of HTML, thus advancing the fulfillment of Nathan Myrhvold, Microsoft's chief scientist. In a white paper discussed in a celebrated New Yorker article, Nathan saw an opportunity in the future for Microsoft to get a "vig" on Net transactions. Controlling the de facto standard for HTML is a good first step. Try raising money for a Web company whose site will use a version of HTML Explorer doesn't "speak." Explorer is a roach motel of larger dimension than Office. And it's expanding.

Last week we had the announcement of MS Audio, a new format presented as full of goodies: smaller than MP3, higher sound quality, copy protection, and permission features. One has to salute again the speed and quality of response to the threat posed by MP3, a format Microsoft didn't control, and to the Secure Digital Music Initiative, a future standard being designed by a consortium of music publishers, their own response to the dangerously libertarian MP3. Explorer begat MS Audio, and we can imagine what could happen to the non-MS audio formats.

And next month, or in June, we'll have Office 2000, which takes the game to yet another level. The default format for Office will embrace an extension of HTML, Microsoft's own XML. The good news is that it uses the Web as a publishing and exchange medium. The other news is that Microsoft now has even more control over the formats used for business and entertainment on the Web.

In my country of birth, government officials (and cultural pundits -- when different) worry about US domination through the primacy of American English, the US dollar, and Hollywood entertainment. The way they see it, if you mint all these tokens of human commerce, it gives you an advantage. I don't think all French people will convert to English any time soon, but, for computers, all you need is a Windows or Office upgrade and the new tokens are in circulation. This is what I meant when I wondered if Microsoft hadn't succeeded in moving past the antitrust suit—even before its conclusion—in a truly awesome way.

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