Table of Contents
SSC, publisher of the monthly magazine Linux Journal, is in an early production stage of a new publication, Be Magazine. To help launch this premier print magazine, please take a few moments to tell the publisher a bit about yourself as a Be user/developer and what you would like to see in Be Magazine. Please visit http://www.bemagazine.com and fill out the short survey. Your answers and comments will help shape the editorial direction of Be Magazine.
Here Comes the First Big Marketing Wave By Roy Graham royg@be.com Executive Vice President and CMO
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 rico@be.com
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. History 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 http://cm.bell-labs.com/cm/cs/who/dmr/1stEdman.html 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. Purpose 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; private: bool ptyinit( ); static int32 feeder( PTY *), drainer( PTY *); struct termios tsaved; int ptyfd, ttyfd; thread id fid, did; char tty[40]; };
A pair of devices are employed in a pty connection:
master and slave. If the master is 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; 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. PTY::PTY( ) { 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, this); did = spawn thread( drainer, "d", B NORMAL PRIORITY, this); 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 log file\n"); else if ( ! Init( com)) Quit( ); } void Done( ) { Lock( ); Quit( ); 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; }; main( ) { 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. Usage 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"). Caveat 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 howard@be.com
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 libnet.so and libnetapi.so. 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. BNetEndpoint 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. BNetAddress 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("www.be.com", 80); addr.SetTo("www.be.com", "tcp", "http"); ia.s addr = inet addr("207.126.103.9"); // www.be.com == 207.126.103.9 addr.SetTo(ia, 80); sa.sin family = AF INET; sa.sin port = htons(80); sa.sin addr = inet addr("207.126.103.9"); addr.SetTo(sa); } 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; addr.GetAddr(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. BNetBuffer 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); packet.AppendUInt32(trans id); packet.AppendUInt64(timestamp); packet.AppendMessage(msg); size = packet.Size(); sizepack.AppendUInt32(size); serverConn.Send(sizepack); 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. Archiving 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. Debugging 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:
DEVELOPERS' WORKSHOP: Media Kit Basics: Muxamania Part 1: The Media Control Panel Application By Owen Smith orpheus@be.com "Developers' Workshop" is a weekly feature that provides
answers to our developers' questions, or topic requests.
To submit a question, visit
http://www.be.com/developers/suggestion_box.html.
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:
ftp://ftp.be.com/pub/samples/media_kit/Selector.zip
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, 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?
... 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.
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.
1997 Be Newsletters | 1995 & 1996 Be Newsletters Copyright © 1999 by Be, Inc. All rights reserved. Legal information (includes icon usage info). Comments, questions, or confessions about our site? Please write the Webmaster. |