C Programming

Al presents JOYKEY, a joystick-driven keyboard simulator designed to provide handicapped computer users with a simple means of interacting with PC's.


June 01, 1993
URL:http://www.drdobbs.com/cpp/c-programming/184409023

Figure 1


JUN93: C PROGRAMMING

C PROGRAMMING

Flattening D-Flat++ and Typing with the Joystick

Al Stevens

I'm giving D-Flat++ a minor overhaul. As I rewrote the code in C++, I carried the original display logic along to the new version. One of the things that I do not like about D-Flat is its performance on older, slower machines. The display code is the culprit. Or, more to the point, the display philosophy is at fault. The system examines every screen write to see if the character position of the target window is in view. That's more complicated than it sounds. An application can legitimately write to a window that is partially or totally obscured. The target window might be overlapped by another window; it might be partially off screen; the window size might provide a smaller viewport than the display requires; or the window might be partially or completely outside the borders of its parent. Every one of these conditions is tested in most circumstances, resulting in a display rate that is, at best, marginally acceptable on older machines and even some current laptops.

What to do? D-Flat's application model has an application window with menus, a status bar, multiple document windows, and modal and modeless dialog boxes. The overhead for all of that is borne by every application, whether it uses all of the features or not. Looking back, I recall that one of my objectives for D-Flat was to let an application use the compiler to pare out the unused features. I did not squarely hit that target. Achieving that goal requires a complex system of compile-time conditionals that weave in and out and all over the code. As I added features, the conditionals became unmanageable. There were too many of them, and the number of possible configurations increased exponentially, which made comprehensive tests impossible.

There are two issues here: features and style of code. Not one of the applications I developed with D-Flat comes close to using all of its features, and so I decided that D-Flat++ does not need that much power. There are other C++ class libraries that provide it, and we do not need another one. What we need is a class library with fewer features and better performance.

The second issue has to do with how you go about building a program that does not have more code than it needs. I have the same goals as before, but I am trying to use the C++ inheritance mechanism to achieve them. If you don't need a radio button, for example, then your program does not instantiate a radio-button object, and the linker does not link the radio-button object module. This is one of those cases where using object-oriented design makes you wish you had always done so, even when the language was procedural. With more care I might have made the D-Flat library work that way. The C language doesn't promote or encourage it, however, and I am not sure I could have kept it up as the program grew with features, no matter how good my intentions.

Back to the first issue, the features themselves: D-Flat++ will support an application model similar to that of D-Flat, but without the multiple-document interface, which I never used except in the example program. An application window still has the menus and status bar. And although it can have its own control windows, they should be unmovable by the user and not overlapping. The application can spawn dialog boxes too, each with their own set of controls. This model supports a large number of applications and is faster and smaller than a full-featured CUA application. If it turns out that we need that extra stuff, it can be added by using class inheritance. Let's see how successful I am at reaching these lofty goals a second time.

I hope to have a working version by the time you read this. You can download D-Flat++ from the CompuServe DDJ forum or from M&T Online. You can also get it by sending a stamped, self-addressed diskette mailer and a formatted diskette to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402. I'll include the latest version of D-Flat as well. The software is free but if you wish, include a dollar for my Careware charity, the Brevard County Food Bank.

JOYKEY

Several years ago my friend Jerry was alone one evening, listening to his police-band scanner. Jerry, a single parent, has custody of Valerie, his then-teenaged daughter, who had gone out on her motorscooter to run an errand. Jerry works part-time as a volunteer dispatcher with the local police, so he keeps a scanner at home. The scanner landed on a police report describing an accident under investigation. A child had been hit by a car. He listened while the police officer spelled Jerry's last name with the phonetic alphabet that radio operators use. He says now that words cannot describe how he felt when he realized that the report was about Valerie. They were reading her name from her driver's license and broadcasting it ahead to the hospital to locate her parents.

What followed in the years since the tragedy is a story in itself. Valerie is now in a wheelchair, a paraplegic. Jerry's life is devoted to caring for her. He is always on the lookout for some way to help her. Valerie's mind is not totally intact, but a good bit of who she was survived the accident. However, her limited motor skills impair her ability to communicate. She cannot talk or write but she can move one hand.

Jerry is a computer nut like most of us, although not a programmer, and we were talking about computers one evening. He explained that Valerie had shown an interest in his computers before the accident. I wondered then if she could use one of those mouse-driven keyboard simulators that I've seen posted in the Handicap forum on CompuServe. Jerry doubted Valerie's ability to manage the mouse. Her hand movements tend to be spastic when what she is holding is not fastened down. She can maneuver her wheelchair somewhat with a joystick, though, so we kicked around an idea: Maybe she could handle the joystick on a computer. It's the kind of problem you want to solve.

I went home and downloaded one of the mouse-driven keyboard simulator programs to see how it worked. The program is NO-KEYS, written by David Leithauser. It is a TSR that displays a simulated keyboard on the screen on top of your application. You select keys with the mouse, and the NO-KEYS program tricks the application into thinking that you are using the keyboard. It's useful to anyone who can handle a mouse but has difficulty using a keyboard. NO-KEYS showed me how such a program should work. It did not come with source code, but its operation was simple enough, so I set out to build a similar program for the standard PC joystick port. The result is Listing One, joykey.c, on page 165.

JOYKEY is yet another TSR, albeit a benign one. It could just as well be a device driver, but I decided to load it from the command line so that it would be easier to unload. Here's what it does.

When you load JOYKEY, it attaches the keyboard BIOS interrupt, displays a simulated keyboard on the screen, and watches for programs to read the keys. Most well-behaved programs read the keyboard by calling BIOS, checking the keyboard status and reading any keystrokes that have been entered. Figure 1 shows how the simulated keyboard looks with the joystick cursor on the a key. The program works only in DOS text mode, although a Windows version would be an interesting project.

I wanted JOYKEY to be as unobtrusive as possible. For example, the real keyboard should work along with the simulated one so that Jerry and Valerie could use the same screen to converse. Furthermore, since the simulated keyboard writes itself as a 5x46 text screen rectangle on top of the application screen, they need to move the keyboard around to see what's under it.

Operating JOYKEY is simple enough, although tedious for the nonchallenged typist; you go at the rate of a word every now and then. The program uses a reverse box cursor just like the standard text-mode cursor. Bang the joystick to the left, and the cursor begins to move leftward. Same thing for up, down, right, and combinations. Release the joystick to its center position, and the cursor stops in its tracks. This movement is similar to the joystick behavior on some CAD/CAM systems that I have seen, although the PC joystick lacks the precision that those high-end devices have. You have to run the cursor pretty slowly to be able to stop it where you want. You need a delay before it starts tracking, too. Otherwise, it gets away from you.

If you click the joystick button while the cursor is outside of the keyboard, the keyboard repositions itself at the cursor location, being sure to stay completely on the screen, however.

To "type," you click the joystick button while the cursor is inside the simulated keyboard on the key you want to press. All the keys of a standard 94-key keyboard are there, so JOYKEY should work with most applications--if the program is well-behaved with respect to keyboard input, that is. For example, Microsoft's EDIT program, the text editor that comes with DOS, is not so well-behaved. Somehow, it captures the keyboard when you pop down a menu and freezes JOYKEY out. You'd expect Microsoft programmers to obey the rules, wouldn't you? D-Flat and D-Flat++ applications work fine. I've tested JOYKEY with several DOS text-mode word processors, including XyWrite, which is known to be finicky, and JOYKEY gets along with them just fine. It even works with the early, cantankerous versions of Sidekick.

The Shf, Alt, and Ctl keys on JOYKEY's simulated keyboard are toggles. When you press one, it highlights. When a toggle is highlighted, the program returns an appropriate keystroke value for subsequent keystrokes as if the highlighted key was pressed, too. Press the toggled key a second time, and the highlight turns off. When the Shf key is highlighted the keyboard's key display changes to show the uppercase letters and shifted special characters.

The Unl key to the right of the spacebar is not a standard keystroke. It's there to let you unload JOYKEY from memory. To avoid accidental hits--a likely mishap for a poorly coordinated user--the key is a toggle. The first time you hit the key, it highlights. The second time you hit it, the program removes itself from memory. If you hit anywhere else, the highlight turns off. Unloading becomes a simple double-click to those of us fortunate enough to be nonchallenged physically.

I used JOYKEY to write some of this column. It makes you empathize with the problems of the handicapped. The first paragraph took forever to write. My deadline loomed near, so I switched back to the real keyboard. Let someone use this program for a while and then tell me that they can still grab a handicapped parking space with a clear conscience. Here in Florida, we have senior citizens who patrol parking lots. They call the police when they catch a healthy person parking in one of those spaces. Even if you have the little wheelchair-icon bumper sticker, you'd better be physically impaired or picking up an obviously impaired passenger. Otherwise one of these gray-panther vigilantes is likely to crease your hood with an umbrella.

JOYKEY has command-line parameters that let you control which joystick button to use, the color of the simulated keyboard and the reaction of the joystick device to a user's hand movements. I have a FlightStick joystick, and the default values of these parameters work well with it. Joysticks are different, and user's abilities vary, so these parameters are to compensate. For example, the FlightStick has two buttons, one on top in the thumb position and the other in front in the trigger position. Which one you use depends on the characteristics of your impairment. A joystick movement moves the cursor one position, delays, and then starts a steady movement across the screen. The delay makes single-character position moves easier. The command-line options can modify the delay rate and the speed of the steady cursor movement, as well as the sensitivity of the joystick, which is a measure of how much movement is needed before the program decides that you have moved the joystick.

Table 1 lists the command-line options. You can put them in any sequence and use upper or lower case.

Table 1: JOYKEY command-line options.

  Command  Meaning
  -------------------------------------------------------------------------

  /1       Use the trigger joystick button.  The thumb position is the default.

  /Dn      Set the number of clock ticks that JOYKEY delays moving the
           cursor after the first move.  The n can be 0 to 20.  The default value is 5.

  /Sn      Set the joystick sensitivity, which is the amount of lateral
           movement before the joystick senses a move; n can be 0 to
           100.  The default value is 10.

  /Tn      Set the number of clock ticks between cursor movements.  This
           parameter makes the cursor move faster or slower in increments
           of about 1/18 second; n can be 0 to 10.  The default value is 1.

  /RED
  /GREEN
  /BLUE
  /WHITE   Set the color of the JOYKEY window.  You can use the first
           letter of the parameter.  The default is WHITE.

  /?       Display a small help screen to remind you about the
           command-line parameters.

JOYKEY's Code

JOYKEY is a small study in DOS systems programming. The code uses the Borland C++ low-level extensions to the C language to become a TSR and to access the hardware. I didn't jump through hoops to make the executable as small as possible. You can do that, but you have to rewrite the startup assembly language code and do tricks with the linker. (I wrote about such cybernastics in the August 1992 "Examining Room.") Valerie is not a power user; she can spare the 15K it takes and, if not, she can load it high.

The first part of the code to look at are the arrays that define the simulated keyboard. The first two arrays define how the keyboard displays on the screen, one for the unshifted keys and one for the shifted keys. The other array defines to the program the key positions within the display and the BIOS scan code and key value to return for each one. I did not type that table in when I built it. Instead, I wrote a program that noted each key press and built the corresponding values into a text file that I later modified with my editor.

The main function processes the command-line arguments and sets up the TSR. It reads the joystick port and adjusts its settings. Most joysticks have trimming pots, and the program assumes that whatever values it reads on startup are relative to the 0/0 center position of the stick. The program uses one of Andrew Schulman's Undocumented DOS features to get the address of what is called the inDOS flag. This flag usually indicates that the user is sitting at the command line when the keyboard BIOS is read. It has other purposes, but that's a side effect. It lets JOYKEY refuse to unload itself if another program is running above it. The inDOS flag would not be set in that situation. Of course, if the other program is using DOS to read the keyboard, which few reasonable programs do, then this test does not work reliably. Before becoming resident, the program hooks the timer and BIOS keyboard interrupt vectors. The timer interrupt supports the program's timed operations for the joystick cursor speed controls. The keyboard interrupt lets the program substitute joystick-selected keys for keyboard keys.

When a program calls BIOS to read the keyboard or its status, JOYKEY's hooked interrupt service routine gets control. If the simulated keyboard is not currently displayed, the program displays it. If the caller is waiting for a key rather than testing keyboard status, the program loops while calling the same interrupt but asking for status. The loop continues until the call returns an indicator that a key was typed, either at the keyboard or from the joystick. This call for status gets hooked, too, because it uses the same interrupt vector. This way, JOYKEY always operates from the hooked interrupt call that asks for status. Most interactive programs spend most of their time in a loop waiting for the next keystroke. That loop is where JOYKEY works.

The program displays the simulated keyboard and then remembers that it was displayed. But the user's actions can change the screen, so, once every loop cycle the program looks at video memory to see if the keyboard is still there. If not, it displays it again. Next, it tests to see if the user has pressed the joystick button. If so, the button-down function tests to see if the cursor is inside the simulated keyboard display. If not, the function moves the simulated keyboard to where the cursor is positioned. If the cursor is inside the keyboard, the function uses that big table to determine which key was hit. It gets the appropriate scan code and key value from the table and returns it.

If the user selected the Unl button, the program tests to see if it can unload itself. If the inDOS flag is set and no other program has hooked the interrupt vectors away from JOYKEY, it unloads by freeing the memory that DOS has allocated to it.

Other simulated keystrokes are returned to the original caller of the BIOS keyboard interrupt, and that's how JOYKEY substitutes the joystick for the keyboard.

Epilogue

In this column I discussed issues related to the handicapped, or the physically challenged, or whatever the current socially correct terminology is. If I slipped up and used a term that is out of favor in your part of the country, it is not due to my insensitivity to those issues but rather my inability to keep pace with vogue. If I seem to take a light view of all of this, it is because I am personally involved and have been for three decades with a so-called "challenged" son.

I wish that I had a happy ending for this story. I wish that I could tell you that Valerie took JOYKEY and wrote her doctoral dissertation or a best-selling novel or even that she was now happily communicating with her dad. But the small dramas in real life are not like a TV movie. We can't encapsulate the whole story in one installment. It takes years and whatever tools a dedicated person can muster and improvise to fit each unique situation. Success and progress are measured in small increments stretched out over a lifetime. They're working on it.




[LISTING ONE]


/* ------------------ joykey.c -------------- */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>
#include <ctype.h>
#include <conio.h>

/* ------- the interrupt function registers -------- */
typedef struct {
    int bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl;
} IREGS;

#define KEYBOARD 0x16
#define DOS      0x21
#define TIMER    0x1c
#define ZEROFLAG 0x40
#define CARRYBIT 1
#define READKEY 0
#define KEYSTATUS 1

#define TRUE 1
#define FALSE 0
#define HT 5
#define WD 46
#define HL WHITE
#define FG (BG == WHITE ? BLACK : LIGHTGRAY)
#define TRVL 8
#define BUTTONMASK (Use1 ? 0x10 : 0x20)

int LagTime = 5;
int Sensitivity = 10;
int SpeedCtr = 1;
int SpeedTimer = 0;

static void movecursor(void);
static void (interrupt *oldtimer)(void);
static void (interrupt *old16)(void);
static void interrupt newtimer(void);
static void interrupt int16(IREGS);
static int unload(void);
static unsigned highmemory;
static unsigned sizeprogram;
static int up;

static int Use1;
static int vsave[HT * WD];
static int BG = WHITE;

extern unsigned _heaplen = 1;
extern unsigned _stklen = 512;

static char *nk[2][5] = {
    {
        {"F1 F2` 1 2 3 4 5 6 7 8 9 0 - = \\ Esc      "},
        {"F3 F4  q w e r t y u i o p [ ]    Hom  PUp"},
        {"F5 F6Ctl a s d f g h j k l ; '        \x1a "},
        {"F7 F8Shf  z x c v b n m , . /      End  PDn"},
        {"F9 F0Alt        space          Unl Ins   Del"}
    },
    {
        {"F1 F2~ ! @ # $ % ^ & * ( ) _ + | Esc      "},
        {"F3 F4  Q W E R T Y U I O P { } Hom  PUp"},
        {"F5 F6Ctl A S D F G H J K L : \"       \x1a "},
        {"F7 F8Shf  Z X C V B N M < > ?      End  PDn"},
        {"F9 F0Alt        space          Unl Ins  Del"},
    }
};

struct kdef {
    int x, y;       /* position within window   */
    int wd;         /* width of screen token    */
    struct {
        char sc;    /* scan codes               */
        char ky;    /* key values               */
    } sk[4];        /* normal, shift, ctrl, alt */
};
static struct kdef kds[] = {
//x,y,wd  Normal      Shift       Ctrl        Alt
//- - --  ---------   ---------   ---------   ---------
{ 0,0,2,{{0x3b,0x00},{0x54,0x00},{0x5e,0x00},{0x68,0x00}}}, // F1
{ 3,0,2,{{0x3c,0x00},{0x55,0x00},{0x5f,0x00},{0x69,0x00}}}, // F2
{ 6,0,1,{{0x29,0x60},{0x29,0x7e},{0x29,0x60},{0x29,0x60}}}, // ~
{ 8,0,1,{{0x02,0x31},{0x02,0x21},{0x02,0x31},{0x78,0x00}}}, // 1
{10,0,1,{{0x03,0x32},{0x03,0x40},{0x03,0x00},{0x79,0x00}}}, // 2
{12,0,1,{{0x04,0x33},{0x04,0x23},{0x04,0x33},{0x7a,0x00}}}, // 3
{14,0,1,{{0x05,0x34},{0x05,0x24},{0x05,0x34},{0x7b,0x00}}}, // 4
{16,0,1,{{0x06,0x35},{0x06,0x25},{0x06,0x35},{0x7c,0x00}}}, // 5
{18,0,1,{{0x07,0x36},{0x07,0x5e},{0x07,0x1e},{0x7d,0x00}}}, // 6
{20,0,1,{{0x08,0x37},{0x08,0x26},{0x08,0x37},{0x7e,0x00}}}, // 7
{22,0,1,{{0x09,0x38},{0x09,0x2a},{0x09,0x38},{0x7f,0x00}}}, // 8
{24,0,1,{{0x0a,0x39},{0x0a,0x28},{0x0a,0x39},{0x80,0x00}}}, // 9
{26,0,1,{{0x0b,0x30},{0x0b,0x29},{0x0b,0x30},{0x81,0x00}}}, // 0
{28,0,1,{{0x0c,0x2d},{0x0c,0x5f},{0x0c,0x1f},{0x82,0x00}}}, // -
{30,0,1,{{0x0d,0x3d},{0x0d,0x2b},{0x0d,0x3d},{0x83,0x00}}}, // =
{32,0,1,{{0x2b,0x5c},{0x2b,0x7c},{0x2b,0x1c},{0x2b,0x5c}}}, // \
{34,0,2,{{0x0e,0x08},{0x0e,0x08},{0x0e,0x7f},{0x0e,0x08}}}, // BS
{37,0,3,{{0x01,0x1b},{0x01,0x1b},{0x01,0x1b},{0x01,0x1b}}}, // Esc
{ 0,1,2,{{0x3d,0x00},{0x56,0x00},{0x60,0x00},{0x6a,0x00}}}, // F3
{ 3,1,2,{{0x3e,0x00},{0x57,0x00},{0x61,0x00},{0x6b,0x00}}}, // F4
{ 6,1,1,{{0x0f,0x09},{0x0f,0x00},{0x0f,0x09},{0x0f,0x09}}}, // Tab
{ 9,1,1,{{0x10,0x71},{0x10,0x51},{0x10,0x11},{0x10,0x00}}}, // q
{11,1,1,{{0x11,0x77},{0x11,0x57},{0x11,0x17},{0x11,0x00}}}, // w
{13,1,1,{{0x12,0x65},{0x12,0x45},{0x12,0x05},{0x12,0x00}}}, // e
{15,1,1,{{0x13,0x72},{0x13,0x52},{0x13,0x12},{0x13,0x00}}}, // r
{17,1,1,{{0x14,0x74},{0x14,0x54},{0x14,0x14},{0x14,0x00}}}, // t
{19,1,1,{{0x15,0x79},{0x15,0x59},{0x15,0x19},{0x15,0x00}}}, // y
{21,1,1,{{0x16,0x75},{0x16,0x55},{0x16,0x15},{0x16,0x00}}}, // u
{23,1,1,{{0x17,0x69},{0x17,0x49},{0x17,0x09},{0x17,0x00}}}, // i
{25,1,1,{{0x18,0x6f},{0x18,0x4f},{0x18,0x0f},{0x18,0x00}}}, // o
{27,1,1,{{0x19,0x70},{0x19,0x50},{0x19,0x10},{0x19,0x00}}}, // p
{29,1,1,{{0x1a,0x5b},{0x1a,0x7b},{0x1a,0x1b},{0x1a,0x5b}}}, // [
{31,1,1,{{0x1b,0x5d},{0x1b,0x7d},{0x1b,0x1d},{0x1b,0x5d}}}, // ]
{37,1,3,{{0x47,0x00},{0x47,0x37},{0x77,0x00},{0x00,0x07}}}, // Home
{41,1,1,{{0x48,0x00},{0x48,0x38},{0x48,0x00},{0x00,0x08}}}, // UP
{43,1,3,{{0x49,0x00},{0x49,0x39},{0x84,0x00},{0x00,0x09}}}, // PgUp
{ 0,2,2,{{0x3f,0x00},{0x58,0x00},{0x62,0x00},{0x6c,0x00}}}, // F5
{ 3,2,2,{{0x40,0x00},{0x59,0x00},{0x63,0x00},{0x6d,0x00}}}, // F6
{ 6,2,3,{{0xff,0x00},{0xff,0x00},{0xff,0x00},{0xff,0x00}}}, // Ctrl
{10,2,1,{{0x1e,0x61},{0x1e,0x41},{0x1e,0x01},{0x1e,0x00}}}, // a
{12,2,1,{{0x1f,0x73},{0x1f,0x53},{0x1f,0x13},{0x1f,0x00}}}, // s
{14,2,1,{{0x20,0x64},{0x20,0x44},{0x20,0x04},{0x20,0x00}}}, // d
{16,2,1,{{0x21,0x66},{0x21,0x46},{0x21,0x06},{0x21,0x00}}}, // f
{18,2,1,{{0x22,0x67},{0x22,0x47},{0x22,0x07},{0x22,0x00}}}, // g
{20,2,1,{{0x23,0x68},{0x23,0x48},{0x23,0x08},{0x23,0x00}}}, // h
{22,2,1,{{0x24,0x6a},{0x24,0x4a},{0x24,0x0a},{0x24,0x00}}}, // j
{24,2,1,{{0x25,0x6b},{0x25,0x4b},{0x25,0x0b},{0x25,0x00}}}, // k
{26,2,1,{{0x26,0x6c},{0x26,0x4c},{0x26,0x0c},{0x26,0x00}}}, // l
{28,2,1,{{0x27,0x3b},{0x27,0x3a},{0x27,0x3b},{0x27,0x3b}}}, // ;
{30,2,1,{{0x28,0x27},{0x28,0x22},{0x28,0x27},{0x28,0x27}}}, // '
{32,2,3,{{0x1c,0x0d},{0x1c,0x0d},{0x1c,0x0a},{0x1c,0x0d}}}, // Enter
{38,2,1,{{0x4b,0x00},{0x4b,0x34},{0x73,0x00},{0x00,0x04}}}, // LF
{44,2,1,{{0x4d,0x00},{0x4d,0x36},{0x74,0x00},{0x00,0x06}}}, // RT
{ 0,3,2,{{0x41,0x00},{0x5a,0x00},{0x64,0x00},{0x6e,0x00}}}, // F7
{ 3,3,2,{{0x42,0x00},{0x5b,0x00},{0x65,0x00},{0x6f,0x00}}}, // F8
{ 6,3,3,{{0xfe,0x00},{0xfe,0x00},{0xfe,0x00},{0xfe,0x00}}}, // Shf
{11,3,1,{{0x2c,0x7a},{0x2c,0x5a},{0x2c,0x1a},{0x2c,0x00}}}, // z
{13,3,1,{{0x2d,0x78},{0x2d,0x58},{0x2d,0x18},{0x2d,0x00}}}, // x
{15,3,1,{{0x2e,0x63},{0x2e,0x43},{0x2e,0x03},{0x2e,0x00}}}, // c
{17,3,1,{{0x2f,0x76},{0x2f,0x56},{0x2f,0x16},{0x2f,0x00}}}, // v
{19,3,1,{{0x30,0x62},{0x30,0x42},{0x30,0x02},{0x30,0x00}}}, // b
{21,3,1,{{0x31,0x6e},{0x31,0x4e},{0x31,0x0e},{0x31,0x00}}}, // n
{23,3,1,{{0x32,0x6d},{0x32,0x4d},{0x32,0x0d},{0x32,0x00}}}, // m
{25,3,1,{{0x33,0x2c},{0x33,0x3c},{0x33,0x2c},{0x33,0x2c}}}, // ,
{27,3,1,{{0x34,0x2e},{0x34,0x3e},{0x34,0x2e},{0x34,0x2e}}}, // .
{29,3,1,{{0x35,0x2f},{0x35,0x3f},{0x35,0x2f},{0x35,0x2f}}}, // /
{37,3,3,{{0x4f,0x00},{0x4f,0x31},{0x75,0x00},{0x00,0x01}}}, // End
{41,3,1,{{0x50,0x00},{0x50,0x32},{0x50,0x00},{0x00,0x02}}}, // DN
{43,3,3,{{0x51,0x00},{0x51,0x33},{0x76,0x00},{0x00,0x03}}}, // PgDn
{ 0,4,2,{{0x43,0x00},{0x5c,0x00},{0x66,0x00},{0x70,0x00}}}, // F9
{ 3,4,2,{{0x44,0x00},{0x5d,0x00},{0x67,0x00},{0x71,0x00}}}, // F10
{ 6,4,3,{{0xfd,0x00},{0xfd,0x00},{0xfd,0x00},{0xfd,0x00}}}, // Alt
{17,4,5,{{0x39,0x20},{0x39,0x20},{0x39,0x20},{0x39,0x20}}}, // space
{32,4,3,{{0xfc,0x00},{0xfc,0x00},{0xfc,0x00},{0xfc,0x00}}}, // Unl
{37,4,3,{{0x52,0x00},{0x52,0x30},{0x52,0x00},{0x52,0x00}}}, // Ins
{43,4,3,{{0x53,0x00},{0x53,0x2e},{0x53,0x00},{0x53,0x00}}}, // Del
{0,0,0,0,0,0,0,0,0,0,0}
};
static int Shift;
static int Ctrl;
static int Alt;
static int Unl;
static int sx = 1;
static int sy = 1;
static int mx = 40;
static int my = 12;
static int nomx, nomy;
static int ff;
static struct kdef *kd;
static far char *inDOS;
/* ------- write a string to video ---------- */
static void wputs(char *str, int x, int y)
{
    int wx = wherex();
    int wy = wherey();
    gotoxy(x+1, y+1);
    cputs(str);
    gotoxy(wx, wy);
}
/* ------ position the joystick cursor ----- */
static void Cursor(int x, int y)
{
    static int mchr;
    gettext(x+1, y+1, x+1, y+1, &mchr);
    mchr ^= 0x7700;
    puttext(x+1, y+1, x+1, y+1, &mchr);
}
/* ---- display the simulated keyboard ----- */
static void DisplayKB(void)
{
    int y;
    textbackground(BG);
    textcolor(FG);
    gettext(sx+1,sy+1,sx+WD,sy+HT,vsave);
    for (y = 0; y < HT; y++)
        wputs(nk[Shift][y], sx, sy+y);
    textcolor(HL);
    if (Ctrl)
        wputs("Ctl", sx+6, sy+2);
    if (Shift)
        wputs("Shf", sx+6, sy+3);
    if (Alt)
        wputs("Alt", sx+6, sy+4);
    if (Unl)
        wputs("Unl", sx+32, sy+4);
}
#define DoParm(p, mx) min(atoi(p),mx)
void main(int argc, char *argv[])
{
    int ch;
    /* --- process the command line parameters --- */
    while (argc > 1)    {
        --argc;
        argv++;
        if (**argv == '/')   {
            (*argv)++;
            ch = toupper(**argv);
            (*argv)++;
            switch (ch) {
                    case 'D':
                    LagTime = DoParm(*argv, 20);
                    break;
                case 'S':
                    Sensitivity = DoParm(*argv, 100);
                    break;
                case 'T':
                    SpeedCtr = DoParm(*argv, 10);
                    break;
                case '1':
                    Use1 = TRUE;
                    break;
                case 'R':
                    BG = RED;
                    break;
                case 'G':
                    BG = GREEN;

                    break;
                case 'B':
                    BG = BLUE;
                    break;
                case 'W':
                    BG = WHITE;
                    break;
                default:
                    puts("Usage: JoyKey /<switch>");
                    puts(" /1 = Use other button");
                    puts(" /RED, /GREEN, /BLUE, /WHITE");
                    puts(" /Dn = delay (n = 0 to 20)");
                    puts(" /Sn = sensitivity (n = 0 to 100)");
                    puts(" /Tn = ticks (n = 0 to 10)");
                    return;
            }
        }
    }
    /* -------- test and center the joystick ------ */
    _AH = 0x84;
    _DX = 1;
    geninterrupt(0x15);
    nomx = _AX;
    nomy = _BX;
    if (nomx && nomy)   {
        /* --- get address of InDOS flag --- */
        unsigned seg, off;
        _AH = 0x34;
        geninterrupt(DOS);
        seg = _ES;
        off = _BX;
        inDOS = MK_FP(seg, off);
        /* ----- attach interrupt vectors ------ */
        old16 = getvect(KEYBOARD);
        oldtimer = getvect(TIMER);
        setvect(KEYBOARD, int16);
        setvect(TIMER, newtimer);
        /* ------ compute program size ------- */
        highmemory = _SS + ((_SP+8) / 16);
        sizeprogram = highmemory - _psp + 1;
        /* ----- terminate and stay resident ------- */
        keep(0, sizeprogram);
    }
}
/* ----- timer interrupt service routine ------- */
static void interrupt newtimer(void)
{
    if (SpeedTimer > 0)
        --SpeedTimer;
    (*oldtimer)();
}
/* -- adjust js vectors by nominal values and sensitivity -- */
static int adjust(int v, int nomv, int mv, int maxtr)
{
    if (mv < maxtr)
        if (v > nomv + Sensitivity)
            return mv+1;
    if (mv > 0)
        if (v < nomv - Sensitivity)
            return mv-1;
    return mv;
}
/* --- move the joystick cursor based on joystick motion --- */
static void movecursor(void)
{
    int x, y;
    if (SpeedTimer == 0 && up)  {
        _AH = 0x84;
        _DX = 1;
        geninterrupt(0x15);
        x = _AX;
        y = _BX;
        x = adjust(x, nomx, mx, 79);
        y = adjust(y, nomy, my, 24);
        if (x != mx || y != my) {
            if (ff == LagTime || ff == 0)   {
                Cursor(mx, my);
                Cursor(x, y);
                mx = x;
                my = y;
            }
            if (ff > 0)
                --ff;
        }
        else
            ff = LagTime;
        SpeedTimer = SpeedCtr;
    }
}
/* ---- the joystick button was pressed ---- */
static int buttondown(void)
{
    int newkey = 0;
    Cursor(mx, my);
    puttext(sx+1,sy+1,sx+WD,sy+HT,vsave);
    if (mx < sx || mx > sx+WD-1 || my < sy || my > sy+HT-1) {
        /* ---- hit outside of the window ----- */
        sx = mx;
        sy = my;
        if (sx > 79-WD)  /* keep within the screen area */
            sx = 79-WD;
        if (sy > 25-HT)
            sy = 25-HT;
        Unl = 0;
    }
    else    {
        /* ---------- hit inside the window ----------- */
        kd = kds;
        /* --- see if a key was hit ---- */
        while (kd->wd != 0) {
            if (my == kd->y+sy)
                if (mx >= kd->x+sx && mx <= kd->x+sx+kd->wd-1)
                    break;
            kd++;
        }
        if (kd->wd != 0)    {
            /* ---- hit a key in buttondown ---- */
            int i = 0, scan;
            if (Shift)
                i = 1;
            else if (Ctrl)
                i = 2;
            else if (Alt)
                i = 3;
            scan = kd->sk[i].sc & 255;  /* get scan code */
            switch (scan)   {
                case 0xfc:          /* Unl */
                    Unl++;
                    break;
                case 0xfd:          /* Alt */
                    Alt ^= TRUE;
                    break;
                case 0xfe:          /* Shift */
                    Shift ^= TRUE;
                    break;
                case 0xff:
                    Ctrl^=TRUE;     /* Ctrl */
                    break;
                default:
                    /* --- get key mask --- */
                    newkey = (scan << 8) | (kd->sk[i].ky & 255);
                    Unl = 0;
                    break;
            }
        }
        else
            Unl = 0;
    }
    DisplayKB();
    Cursor(mx, my);
    return newkey;
}
/* ----- Keyboard BIOS ISR ------- */
static void interrupt int16(IREGS ir)
{
    int sw, i, j;
    int func = (ir.ax >> 8) & 0xff;
    static unsigned int newkey = 0;
    static int buttonup = FALSE;
    static int sample[WD];
    char *cp;
    int *ip;

    /* --- display the keyboard if it is not up --- */
    if (!up)    {
        DisplayKB();
        Cursor(mx, my);
        up = TRUE;
    }
    /* --- for read key bios call, loop until key pressed --- */
    if (func == READKEY)    {
        int flg = ZEROFLAG;
        while (flg & ZEROFLAG)    {
            _AH = KEYSTATUS;
            geninterrupt(KEYBOARD); /* this will call myself */
            flg = _FLAGS;
        }
    }
    /* ---- test to see if keyboard is really displayed ----- */
    gettext(sx+1,sy+1,sx+WD,sy+1,sample);
    cp = nk[Shift][0];
    ip = sample;
    for (i = 0; i < WD; i++)
        if (((*cp++) & 255) != ((*ip++) & 255))
            break;
    if (i < WD) {
        /* ---- keyboard is not on the screen ---- */
        for (j = 0; j < sy; j++)    {
            /* ----- test to see if screen has scrolled ---- */
            gettext(sx+1,j+1,sx+WD,j+1,sample);
            cp = nk[Shift][0];
            ip = sample;
            for (i = 0; i < WD; i++)
                if (((*cp++) & 255) != ((*ip++) & 255))
                    break;
            if (i == WD)    {
                /* --- restore the video buffer --- */
                Cursor(mx, my-(sy-j));
                puttext(sx+1,j+1,sx+WD,j+HT,vsave);
                break;
            }
        }
        if (i < WD)
            Cursor(mx, my);
        DisplayKB();
        Cursor(mx, my);
    }
    /* ----- Test joystick Button ---- */
    _AH = 0x84;
    _DX = 0;
    geninterrupt(0x15);
    sw = _AL;
    if ((sw & BUTTONMASK) == 0) {
        /* ---- button is down ---- */
        if (buttonup)   {
            newkey = buttondown();
            buttonup = FALSE;
        }
    }
    else
        buttonup = TRUE;
    if (Unl > 1)   {
        /* --- the Unl simulated function key --- */
        if (up) {
            puttext(sx+1,sy+1,sx+WD,sy+HT,vsave);
            up = FALSE;
        }
        if (unload())   {
            _AX = ir.ax;  /* pass into keyboard BIOS */
            geninterrupt(KEYBOARD);
            ir.ax = _AX;
            ir.fl = _FLAGS;
        }
        Unl = 0;
        return;
    }
    if (func != READKEY)
        movecursor();
    if (newkey) {
        /* --- user selected a keystroke with joystick --- */
        ir.ax = newkey;
        if (func == READKEY)
            newkey = 0;
        else
            ir.fl &= ~ZEROFLAG;
    }
    else    {
        _AX = ir.ax;
        (*old16)();
        ir.ax = _AX;
        ir.fl = _FLAGS;
    }
    if (func == READKEY && up)  {
        int ch = ir.ax & 0xff;
        int sc = ir.ax & 0xff00;
        if (sc == 0 || ch < ' ' || ch > 127)    {
            Cursor(mx, my);
            puttext(sx+1,sy+1,sx+WD,sy+HT,vsave);
            up = FALSE;
        }
    }
}
/* --------- unload the resident program --------- */
static int unload(void)
{
    if (getvect(KEYBOARD) == int16) {
        if (getvect(TIMER) == newtimer)  {
            if (*inDOS) {
                unsigned far *env = MK_FP(_psp, 0x2c);
                /* --- restore interrupt vectors --- */
                setvect(KEYBOARD, old16);
                setvect(TIMER, oldtimer);
                /* ---- free memory ---- */
                freemem(*env);
                freemem(_psp);
                return TRUE;
            }
        }
    }
    return FALSE;
}











Copyright © 1993, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.