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.
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 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.
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."
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> #definectrl
(c
) ((c
) - 0100) struct PTY {PTY
( );~PTY
( ); boolInit
( char *[]); virtual intGetData
( uchar [], uint) = 0; virtual intPutData
( uchar [], uint) = 0; virtual voidDone
( ) = 0; private: boolptyinit
( ); static int32feeder
( PTY *),drainer
( PTY *); struct termiostsaved
; intptyfd
,ttyfd
; thread_idfid
,did
; chartty
[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:
boolPTY
::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:
int32PTY
::feeder
( PTY *pty
) { uchardata
[1000]; inti
; while ((i
=pty
->GetData
(data
, sizeofdata
)) > 0)write
(pty
->ptyfd
,data
,i
);pty
->Done
( ); } int32PTY
::drainer
( PTY *pty
) { uchardata
[1000]; inti
; while ((i
=read
(pty
->ptyfd
,data
, sizeofdata
)) > 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.
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
).
boolPTY
::Init
( char *com
[]) { if ( !ptyinit
( )) {fprintf
(stderr
, "no available pty\n"); return (FALSE
); } struct termiost
; struct winsizew
;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_tpid
=fork
( )) { case -1:fprintf
(stderr
, "system too busy\n"); return (FALSE
); case 0: charte
[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.
structScript
:BApplication
,PTY
{Script
(char *sig
, char *c
[], char *log
):BApplication
(sig
) {com
=c
;logfd
=creat
(log
, 0666);Run
( ); } voidReadyToRun
() { if (logfd
< 0)fprintf
(stderr
, "cannot create logfile\n"); else if (!Init
(com
))Quit
(); } voidDone
() {mLock
();Quit
();Unlock
( ); } intGetData
(uchardata
[], uintn
) { return (read
(0,data
,n
)); } intPutData
(uchardata
[], uintn
) {write
(logfd
,data
,n
); return (write
( 1,data
,n
)); } char **com
; intlogfd
; };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.
To compile script, clip out the code to
script.cpp
, and type
gcc script.cpp -lbe
$ 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.)
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.
(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.
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.
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:
int32send_data
(const char *host
, shortport
, const void*data
, int32datalen
) {BNetEndpoint
comm
(SOCK_STREAM
); int32rc
= -1; if(comm
.InitCheck
() ==B_NO_ERROR
) { if(comm
.Connect
(host
,port
) ==B_NO_ERROR
) {rc
=comm
.Send
(data
,datalen
); } } returnrc
; }
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):
voidaddrs
() { BNetAddressaddr
; struct sockaddr_insa
; in_addria
;addr
.SetTo
("www.be.com", 80);addr
.SetTo
("www.be.com", "tcp", "http"); // www.be.com == 207.126.103.9ia
.s_addr
=inet_addr
("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:
voidsin_convert
(const struct sockaddr_in &sa
) {BNetAddress
addr
(sa
); charhost
[256]; unsigned shortport
;addr
.GetAddr
(host
,port
); cout << "Host: " <<host
<< "Port: " <<port
<< endl; }
Likewise, to resolve a host name into a sockaddr_in:
struct sockaddr_inresolve
(const char *host
) {BNetAddress
addr
(host
); struct sockaddr_insa
;addr
.GetAddr
(sa
); returnsa
; }
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; [...] int32forwardBMessageToServer
(constBMessage
&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 BMessage
s. 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
.
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: ftp://ftp.be.com/pub/samples/network kit/FtpClient.zip
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, 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:
It parses the command line to get the node IDs that will be passed to it, and launches control panels for each of the nodes it's editing.
It keeps track of all currently running control panels, and quits when all control panels have been closed.
It provides a virtual function CreateControlPanel()
(not related to
BMediaRoster::StartControlPanel()
!) which gets called when a new
control panel must be created for a node. By default, it creates a
simple window that shrinkwraps around the default media theme's view
for the node. However, you can override this function to create custom
BWindow
s for your own kinds of nodes if you wish.
Now, what does it take to get this awesome functionality in your add-on?
int main() { MediaControlPanelApp app; app.Run(); 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.
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.