The way OS/2 handles the keyboard eliminates the interrupt 9 problems that have plagued TSRs under DOS, though it does involve a certain amount of MonKey business.
Years from now, when programmers reminisce about the old days, someone is likely to ask, "Remember interrupt 9?" And everyone else will groan.
Interrupt 9 is generated by the PC's keyboard hardware whenever a key is pressed. Although most DOS application programs use interrupt 16h to obtain keyboard information, many RAM-resident popup programs intercept interrupt 9 to watch for their hotkeys. RAM-resident keyboard macro programs seize interrupt 9 in order to translate certain key combinations into character strings. Even some normal DOS application programs intercept interrupt 9 in order to be able to use key combinations that are not defined by the PC BIOS.
Interrupt 9 has come to symbolize for me some of the real problems in DOS. Anybody who has written a RAM-resident popup program or even used multiple RAM-resident popups knows what I'm talking about. Very often the programs must be loaded in a particular order, and even then they often interact with each other in undesirable ways. When a program that intercepts interrupt 9 wishes to discard a keystroke, it must reset the 8259 interrupt controller, which is a device that should be accessed only by the operating system.
Under DOS, the most recent program that grabs interrupt 9 gets first divvies on the keystrokes. Thus, when a normal application program intercepts interrupt 9, it effectively disables all the popup programs loaded earlier. Around PC Magazine we used to call this phenomenon "the XyWrite problem," in honor of one program that did this. [XyWrite Versions III and III Plus no longer commandeer interrupt 9, but a number of programs still do--Ed.]
The problem with the older versions of XyWrite and with other applications led to the extraordinary measures taken by Borland with SideKick. SideKick always ensures that it gets first divvies on keystrokes by intercepting the hardware timer (interrupt 8) and reinstalling itself. This process, of course, often causes problems with other popups.
INTERCEPTING I/O UNDER OS/2 Under the protected mode of OS/2, programs can no longer intercept interrupt 9. In fact, interrupt 9 no longer exists as a hardware interrupt. Instead, OS/2 provides a built-in generalized mechanism that allows programs to examine and alter a input or output stream, such as the input stream coming from the keyboard or the output stream going to the printer.
Let's get the lingo down first. Under OS/2, rather than "intercept an interrupt," you "install a monitor." The monitor gives a program access to an input or output stream as it is being processed by the device driver. OS/2 monitors do not solve all the interrrupt 9 problems we've experienced under DOS, but they make major steps in solving some of those problems.
The device driver itself determines whether it supports monitors. Currently, only the keyboard and printer device drivers do so. (Installing a printer monitor under OS/2 is similar to intercepting interrupt 17h under DOS. OS/2 includes a program called SPOOL that installs a printer monitor to save printer output to files. SPOOL then transfers printer output to the printer while other programs are running.) In this column I'll concentrate on keyboard monitors.
USES OF KEYBOARD MONITORS You'll recall from my last column that the KbdCharIn function normally used by application programs to obtain keyboard input is very similar to the PC BIOS interrupt 16h under real mode. It reports only key presses (not key releases) and it doesn't report undefined key combinations.
OS/2 application programs that need information about every keyboard event can install a keyboard monitor. For example, a protected-mode version of XyWrite (which I understand is under development at this time), can still use all the undefined keystrokes the DOS version uses.
The OS/2 Presentation Manager (the forthcoming OS/2 windowing system, similar to Microsoft Windows) itself installs a keyboard monitor so it can report detailed keystroke information to Presentation Manager programs.
Keyboard monitors can also be used by OS/2 programs running in the background, including keyboard macro programs and hotkey popups. However, popups don't make much sense in the Presentation Manager. This is because a program running in the Presentation Manager can load itself in a "minimized" state and be displayed as an icon at the bottom of the screen. The icon can then be expanded into a window using the mouse or the keyboard. It doesn't need its own hotkey.
Even under the command line interface of the OS/2 Kernel, many current popups can written as normal programs to run in their own screen group. The user simply switches screen groups to get at the program.
OS/2 MONITOR FUNCTIONS OS/2 has five functions that pertain to monitors. All of them begin with the preface DosMon.
When a program wishes to install a monitor, the first step is to call DosMonOpen. Here's the C syntax for opening a keyboard monitor:
DosMonOpen ("KBD$", &MonitorHandle) ;
The first parameter is the ASCII name of the device and the second parameter is a pointer to an integer variable. This variable receives the "monitor handle" that is then used in some of the other monitor functions. DosMonOpen returns a non-zero value if the function fails (for example, if the device driver does not support monitors).
The MonitorHandle parameter is the value obtained from the DosMonOpen function. The InBuffer and OutBuffer variables are the addresses of two-character arrays. The documentation I have states, somewhat ambivalently that these arrays "must be at least 64 bytes long, although 128 bytes is the recommended length." Before calling DosMonReg you set the first word to the length of the array. During the DosMonReg call, OS/2 formats these buffers to contain information it needs internally to maintain the keyboard monitor. You don't actually use these buffers for anything in your program.
The PosFlag parameter of DosMonReg allows a program to specify its position in the monitor chain if multiple keyboard monitors are installed. I'll discuss this in more detail in the section below on "Multiple Keyboard Monitors."
The meaning of last DosMonReg parameter depends on the particular device you're monitoring. For keyboard monitors, this is a number indicating the screen group for which the monitor is applicable. A keyboard monitor only sees keystrokes for one screen group. I'll have more to say about this in the section below on "Background Monitors."
Following the DosMonReg call, the keyboard monitor is installed. The program can then sit in a loop calling two functions, DosMonRead and DosMonWrite.
DosMonRead obtains the next keystroke from the input stream. The syntax is:
Normally you'll set the NoWaitFlag to 0. In that case the DosMonRead function does not return until a keystroke is available. The third parameter is a pointer to a structure of type MonKeyData that the device driver fills with information about the keystroke. I'll discuss the format of this structure in the section below on "The MonKeyData Structure."
The last parameter of DosMonRead is a pointer to a variable containing a integral multiple of the size of the MonKeyData structure. This indicates the maximum number of keystrokes you can handle in one call. When DosMonRead returns, ByteCount is set to the number of keystrokes that were actually passed to your program.
The program can then examine the fields of the MonKeyData structure. The keystroke can be altered by changing the fields. The program calls DosMonWrite to put the keystroke back into the input stream:
DosMonWrite (OutBuffer, &MonKeyData, ByteCount) ;
The ByteCount field is an integral multiple of the size of MonKeyData and indicates how many keystrokes the program is returning to the device driver.
If your program simply calls DosMonRead and then DosMonWrite, without making any changes to the MonKeyData fields, then the keystrokes remain unaltered. Programs can remove a keystroke from the input stream by not calling DosMonWrite for the keystroke. This is what a popup program would do for its hotkey. A keyboard macro program would make multiple DosMonWrite calls based on a single keystroke obtained from DosMonRead to insert keystrokes into the input stream.
When the program wants to de-install the keyboard monitor, it calls
DosMonClose (MonitorHandle) ;
MULTIPLE KEYBOARD MONITORS
Just as under DOS more than one program can intercept interrupt 9 and form a chain of keyboard handles, multiple OS/2 programs can each install a keyboard monitor for a particular screen group.
As I indicated earlier, under DOS, the most recent program that intercepts interrupt 9 always gets the first shot at the keystrokes. In OS/2, a program that installs a keyboard monitor can specify where in the chain the keyboard monitor is installed. The desired position of the keyboard monitor is specified in the PosFlag word of the DosMonReg function. This flag can have one of three values:
■ No preference
■ First in chain
■ Last in chain
If two or more keyboard monitors each specify "first in chain" or "last in chain," then the first keyboard monitor to be registered remains first or last. The passing of keystroke information among the multiple keyboard monitors is handled by OS/2 and the keyboard device driver, as shown in Figure 1.
When a keyboard event occurs, the device driver passes the keystroke to the first keyboard monitor in the chain. The program returns from its DosMonRead call and processes the keystroke. Any keystrokes it passes back to the device driver via DosMonWrite are then passed to the next monitor in the chain.
Keyboard macro programs will probably install themselves as the first in the chain. Application programs that use keyboard monitors can install themselves last in the chain. This allows the keyboard macros to work within the application program.
OS/2 popups should probably install themselves with "no preference." This allows them to be popped up in response to a hotkey combination that a keyboard macro program generates. The "no preference" ordering also allows the popup to discard its hotkey and not pass it on to an application program.
Because the chaining of one keyboard monitor to another is not handled within your program, you can de-install your monitor whenever you want by calling DosMonClose. By constrast, the de-installation of RAM-resident programs under DOS is a job that currently requires separate utilites that save the interrupt vector states at various points during the loading of the programs. The DOS programs must be de-installed in a reverse order from the order in which they're loaded. Under OS/2, this is not a problem.
The MonKeyData Structure
Both DosMonRead and DosMonWrite require a pointer to a structure of type MonKeyData. You can define this structure in a C program, as shown in Figure 2.
When the DosMonRead function returns control to your program, this structure is filled in with information about the keystroke. When you call DosMonWrite this structure contains keyboard information that is passed to the next monitor or (ultimately) placed in the keyboard queue for normal access by the KbdCharIn function.
The MonKeyData structure has a KeyData structure imbedded between two flag words. This KeyData structure is the same structure I discussed in the last Environments column used to return keyboard information to your program when you call the KbdCharIn function.
The current OS/2 documentation I have states that the low byte of MonFlagWord contains the hardware scan code. Actually, it is the high byte of MonFlagWord that has this code. (Note that this is the real hardware scan code, not the scan_code field of the KeyData structure, which is the extended keyboard code familiar to DOS programmers.) The high bit of this hardware scan code is set if the key is being released.
The KbdDDFlagWord provides some additional information about the keystroke, as shown in Figure 3.
The "key identifier" is a code that indicates the general type of keystroke. For example, 00h is a normal key, 07h is a shift key, 08h is pause, 13h is print screen, and so forth. 01h through 04h are used for some of the special codes that are generated by the hardware of the IBM AT keyboard. In the Enhanced keyboard, these codes can be used to differentiate between the separate cursor movement keys and the cursor movement keys that are part of the numeric key pad.
For example, pressing and releasing the down-arrow key on the numeric keypad generates two keystrokes. The scan code in the MonKeyFlag is 50h when the key is pressed and D0h when the key is released. The KbdDDFlagWord is 0 when the key is pressed and 40h (indicating a release) when the key is released.
But when you press and release the separate down-arrow key on the Enhanced keyboard, you get four keystrokes. The first and third have a scan code of E0h and a KbdDDFlagWord of 02h, indicating a secondary key prefix. The second and fourth keystrokes are the same as those returned for the number key pad down-arrow key except that the KbdDDFlagWord is 80h when the key is pressed and C0h when the key is released. This bit indicates that the previous key was a prefix.
BACKGROUND MONITORS
An OS/2 session is divided into multiple screen groups, each of which can run one or more programs. A particular keyboard monitor only receives keystrokes for one of these screen groups. When you install a keyboard monitor you indicate the screen group in the last parameter of DosMonReg. Current documentation says that you can use 0 to specify the screen group in which the program is running, but this does not seem to work with the version of OS/2 I have.
When a foreground application program installs a keyboard monitor, it can obtain the screen group that the program is running in by first calling DosGetInfoSeg
DosGetInfoSeg (&GlobalSeg, &LocalSeg) ;
This function returns two selectors (segment addresses) that access some useful information. GlobalSeg is a selector in the global descriptor table and references a segment common to all processes running under OS/2. LocalSeg is a selector in the process's own local descriptor table.
The screen group in which a process is running is stored 8 bytes into the local information segment. It can be extracted with the following C code:
ScreenGroup = *((unsigned int far *) (((long) LocalSeg << 16) + 8)) ;
For an application program running in the foreground, this is the value to be passed as the last parameter to DosMonReg.
For a popup or keyboard macro program running in the background, this is not the case. In previous Environments columns I've discussed the OS/2 DETACH command,
DETACH progname
When you run a detached program off the OS/2 command line, OS/2 displays the process ID number of the program and the OS/2 command prompt returns. The program runs in the background. This is how you'll load a popup or keyboard macro program.
However, detached programs enter what Ray Duncan has called a "black box" screen group. This prevents the detached program from interfering with the screen or with keyboard or mouse input intended for the foreground program. Here's what happens when the detached program tries to use the screen, keyboard, or mouse:
■ All keyboard (Kbd) functions fail by returning an error code of 464, which is defined as ERROR_KBD_DETACHED.
■ All mouse (Mou) functions fail and return an error code of 466 (ERROR_MOU_DETACHED).
■ All video I/O (Vio) functions except VioPopUp fail and return an error code of 465 (ERROR_VIO_DETACHED).
Only if the detached program calls VioPopUp can it then get access to the screen, keyboard, and mouse.
A detached program running in the background can still install a keyboard monitor, but it should not be installed for the screen group in which the program is running (the "black box" screen group). Rather, the monitor should be installed for the foreground screen group at the time the program is loaded. This screen group is 24 bytes into the global information segment:
When a detached popup program detects its hotkey during keyboard monitor processing, it can call VioPopUp to get access to the screen, keyboard, and mouse. Although it's not implemented in the version of OS/2 I have, a flag associated with VioPopUp can be set to prevent clearing the screen. The popup program can thus get access to the screen contents as they were before VioPopUp was called.
A KEYBOARD MACRO DEMO Now let's put everything we've learned in this column into a simple keyboard macro demonstration program called MACDEMO. The listing is in Figure 4. MACDEMO translates an Alt-D to DIR, Alt-C to CHKDSK, and Alt-V to VER. (Obviously, in a real keyboard macro program, the key translations would be stored in a file.) The program can be compiled with the OS/2 Software Development Kit and run by executing:
DETACH MACDEMO
The table structure in MACDEMO.C holds the keys to be translated (which are the extended keyboard codes for Alt-D, Alt-C, and Alt-V) and the strings into which MACDEMO translates them. MACDEMO only searches for these extended key codes when the char_code field of the KeyData structure is 0 and the KbdDDFlagWord of the MonKeyData structure is 0 (indicating a normal key depression).
If the scan-code field of the KeyData structure matches one of the keys in the table structure, MACDEMO calls DosMonWrite several times to insert the appropriate string into the keyboard stream. Otherwise, the MonKeyData structure is passed unaltered to the DosMonWrite function.
A real keyboard macro program would also provide some simple means of disabling the program or terminating it. A detached program that installs a keyboard monitor can terminate simply by calling DosMonClose and DosExit. Because memory under protected mode is movable, the program doesn't leave a "memory hole," as it does in DOS.
A real keyboard macro program would also define a keystroke that would allow a user to alter macro sequences. A detached program can do this by calling VioPopUp to gain control of a fresh screen and allow the user to enter new character codes and sequences.
MONITORS AND MULTITASKING
One of the most annoying hassles with DOS RAM-resident programs is the difficulty in making DOS calls. DOS is not re-entrant, so the program has to use undocumented methods for calling DOS. This is not a problem under OS/2. The detached program monitoring the keystrokes can make OS/2 function calls for memory allocation or file I/O at any time.
However, to allow keystrokes to pass through the monitors wihout delay, programs should not spend much time within the DosMonRead and DosMonWrite loop. If the program has a lengthy job to do during keyboard processing, it should run the monitor in a separate thread of execution. A foreground application program that uses a keyboard monitor should queue its own keystrokes within the monitor thread and then retrieve them in the program's main thread.
I've mentioned OS/2 "threads" several times in these Environments columns. It's time to look at them in detail, and I'll do so in the next issue.
[Note: Art, please render this and show me - rh]
Keystroke
|
|
V
+--------------+
| |
| | +-----------------------------+
| |-------->| |
| | | First monitor to register |
| | | itself "first in chain" |
| +---|<--------| |
| | | +-----------------------------+
| | |
| | | +-----------------------------+
| +-->|-------->| |
| | | Second monitor to register |
| | | itself "first in chain" |
| +---|<--------| |
| | | +-----------------------------+
| Keyboard | |
| | | +-----------------------------+
| +-->|-------->| |
| Device | | Monitor that registers |
| | | itself with "no preference" |
| +---|<--------| |
| Driver | | +-----------------------------+
| | |
| | | +-----------------------------+
| +-->|-------->| |
| | | Second monitor to register |
| | | itself "last in chain" |
| +---|<--------| |
| | | +-----------------------------+
| | |
| | | +-----------------------------+
| +-->|-------->| |
| | | First monitor to register |
| | | itself "last in chain" |
| |<--------| |
| | +-----------------------------+
| |
+--------------+
|
|
V
Keyboard Queue
Caption:
Figure 1: A chain of keyboard monitors in the keyboard input stream.
struct MonKeyData {
unsigned int MonFlagWord ;
struct KeyData kd ;
unsigned int KbdDDFlagWord ;
}
mkd ;
Caption:
Figure 2: The C language structure used with keyboard monitors.
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0|
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| | | | | | | |
| | | | | | | |
+--+ | | | | +---------------+
| | | | | |
| | | | | |
| | | | | +---------- Key Identifier
| | | | |
| | | | +--------------------- 1 if release
| | | |
| | | +------------------------ 1 if previous key
| | | was prefix
| | |
| | +--------------------------- 1 if typematic repeat
| |
| +------------------------------ 1 if accented character
|
+---------------------------------------------- Private use by monitors
Caption:
Figure 3: The organization of the KbdDDFlagWord field.