Channels ▼


C Programming

Source Code Accompanies This Article. Download It Now.


This article contains the following executables: DFLAT.ARC

This month we begin a new C Programming column project, the D-Flat C library that implements a subset of the IBM SAA Common User Access (CUA) interface library into C programs. The library is for use in DOS text-mode programs. This project will take several months. The accumulated source code now exceeds 7500 lines, although I haven't prepared it for publication yet. I'll be narrowing the margins to fit the printed page and inserting lots of comments, so the code will surely grow. Each month, we will add to the source code and you can collect it that way, learning about it from the column as you go along. If you prefer, you can download the entire source code library from the DDJ Forum on CompuServe or TelePath and get an early start on using it. The monthly code will be posted there as well, just as it always has been, and the documentation will be the text of the columns as the project proceeds. The full source code package will be preliminary and generally unsupported although I will answer any questions about it on CompuServe. The package will include a terse description of the functions and messages in the D-Flat API as well as some example programs to show how the library works. See the later section, "How to Get D-Flat."

To begin with, I will explain the subset of CUA that D-Flat implements. D-Flat supports a program model that begins with an application window. The application window can be any reasonable size. It has a title bar and a menu bar. The menu bar hosts popdown menus. A D-Flat application can open other windows that are children of the application or children of one another. The windows can be text boxes, list boxes, and edit boxes. Your user can move and resize these windows and the application window by using the mouse or the keyboard. These operations follow the conventions of the CUA standard, so your application's user interface will resemble that of Windows, Presentation Manager, TurboVision, and others. D-Flat supports the standard set of CUA pull-down menus: File, Edit, Options, Window, Help, and the System menu for moving, sizing, minimizing, and maximizing windows from the keyboard. Your application can add its own menus as well. D-Flat has a clipboard and dialog boxes, too. A dialog box is a window that contains data entry fields. These fields can be edit boxes, push buttons, radio buttons, and list boxes. The D-Flat library includes dialog boxes for the CUA standard File Open and Save As dialogs. D-Flat does not use resource files and a resource compiler to describe menus and dialog boxes the way the Windows SDK does. Instead, you code your menus and dialog boxes in a format similar to the ones used by the SDK resource files, and a set of C preprocessor macros compiles the menus and dialog boxes into the structures that the D-Flat software expects.

For a complete definition of CUA, you can get volume CG26-4582 from the IBM Systems Application Architecture Library. The volume is titled, Common User Access: Advanced Interface Design Guide. I got my copy along with the Microsoft Windows 3.0 Software Development Kit. It appears to be a definitive description of CUA, although probably derived from the OS/2 Presentation Manager. You will find some subtle departures from the CUA standard in Windows 3.0 and other Microsoft products. The Borland Turbo Debugger 2.0 interface follows CUA somewhat, and the TurboVision functions of Turbo Pascal implement some of CUA for Pascal programmers.

I developed this library to use in an application project. I looked around for a CUA-compliant library that was text-based and that offered adequate performance to run on the bottom-end laptops, the ones with not much disk space and slow drives and processors. I found libraries for faster machines (MEWEL, for example) and C++ class libraries (Zinc, for example), but nothing that suited my needs for C language development of programs targeted for low-end platforms. As a result, I launched this project and decided to publish it as a series for the column. So far, the code compiles with Turbo C 2.0 and Microsoft C 6.0. I'll stick with those two for a while and point out the few places where the code is compiler-dependent so you can consider ports to other compilers if you want.

I am writing this "C Programming" column with a simple D-Flat application, a multiple-document notepad-like text editor. This program will be one of the examples we use later on. It employs most of the CUA features of D-Flat -- editor windows, dialog boxes, pull-down menus, the Clipboard, and so on. It's also helping me debug the D-Flat library.

Low-Level D-Flat Code

Like its musical namesake, D-Flat is not the easiest key to play in. It uses the feared message-based, event-driven architecture similar to the one that sends programmers scurrying to avoid Windows. But with practice you can play "Body and Soul" in "five," and with diligence you can master and take advantage of the benefits of this alternative way to write programs. As you will see in the months to come, event-driven programming is not all that intimidating.

We need to get some platform-dependent code out of the way first. This month is dedicated to the C files that bind the rest of the library to the PC architecture. If you wanted to move D-Flat to a different system, you would change this code, which provides interfaces to the keyboard, mouse, and screen.

Listing One, page 140, is dflat.h, the header file that all D-Flat applications and most D-Flat library source files include. It begins by including several other header files, four of which -- system.h, keys.h, rect.h, and video.h -- appear in this month's column, and the rest of which you will see next month. If you want to do some compiling and testing of the low-level drivers with this month's code, you can stub out the includes of the files that you do not have yet.

A browse through dflat.h will give you a preview of what D-Flat supports. The first item of interest is the definition of the standard window classes in the CLASS enumerated data type. From their titles -- TEXTBOX, RADIOBUTTON, and so on, you get advance warning of what is coming. The WINDOW structure in dflat.h is the controlling structure that accompanies the windows that your application opens. A number of macros and prototypes define the D-Flat API. There are some external data definitions and then the macros that define the border characters of the windows. These characters use the text graphics character set of the PC's text video system. The characters with the FOCUS_ prefix define the borders for a window that is "in focus," the one into which the user is presently entering data, for example. The others define the border characters for other windows.

D-Flat supports scroll bars. The scroll bar characters in dflat.h define the characters that D-Flat uses to paint the scroll bar and the scroll boxes and buttons. The CHECKMARK character is for menu items that are toggles. It displays next to a toggle menu as a check mark when the item is turned on.

A D-Flat title bar can include a Control Box, Min/Max Box, and a window restore character. The title bar characters in dflat.h define these values.

The text control characters in dflat.h define control bytes that D-Flat uses to manage colors when it displays the contents of a text window. You will learn about these in later columns.

The macros and prototypes at the end of dflat.h define the external functions that either your application or the D-Flat API itself uses. I will explain each of these when I address the subject to which the item relates.

Listing Two, page 142, is keys.h, a header file that defines the key values for D-Flat. Your application would use these values to identify that a particular key has been pressed. D-Flat is an event-driven, message-based architecture. Later you will learn how to build window-processing functions that react to events and messages. A keystroke would be an event that sends a message to the window that is in focus. Your window-processing function would receive the message along with the pressed key as a parameter. The values in keys.h will be of interest when we get to that part of the system discussion.

Listing Three, page 142, is system.h, the header file that defines a number of global values and prototypes that associate with the hardware and its drivers. Your application programs will not usually concern themselves with these values, but the hardware-dependent functions of D-Flat will. There are values for interrupt vectors, screen dimensions, BIOS functions, and prototypes for the mouse, cursor, and timer functions. There are macros that make the code compatible with Microsoft C. The Turbo C and Microsoft C compilers implement the DOS interface functions differently. Because I used Turbo C to develop D-Flat, I had to develop the compatibility macros that translate the Turbo C implementations into the Microsoft C conventions. These macros and a few other places in the code where you will find the #ifdef MSC statement are where compiler-dependent code exists. If you port D-Flat to another compiler, these are the places that need your attention.

Listings Four and Five, page 145, are rect.h and video.h. The first listing defines some macros and prototypes that D-Flat uses to deal with screen rectangles. The second listing defines the interface to the video drivers.

Listing Six, page 145, is video.c, which contains the D-Flat video drivers. The getvideo and storevideo functions read and write rectangles of video memory from and to RAM buffers. These functions support saving and restoring the video space that a temporary window will occupy. Not all windows use this technique, but some do. The GetVideoChar and PutVideoChar functions read and write a video character and its attribute byte from and to video memory. The wputch function writes a single character to a position within a specified window and uses the currently-established color values. The wputs function similarly writes a string to a window. The get_videomode function determines the current video mode and the address of video memory. Observe that these functions do not worry about the video snow characteristics of the old IBM Color Graphics Adaptor. If that is a problem, you must drop into assembly language to solve it. The solution has been published often in the past. A later column will discuss the technique if enough of you tell me it is warranted.

Listing Seven, page 146, is console.c, which contains the code that manages the keyboard, cursor, and the generation of a warning buzz. The keybit function is of interest. It determines if a key has been pressed by using a BIOS call. I could have used the Turbo C bioskey and MSC_dos_bioskey functions, but they both have a bug that has persisted since they were first introduced. If you go into a loop waiting for one of these functions to tell you a key has been pressed, and the user presses Ctrl-Break, you will never exit from the loop. I reported this bug to both compiler vendors years ago, but neither has found it necessary to make a repair. Both compilers support the kbhit function, but that function uses a DOS call, something I try to avoid. Functions that use DOS for console I/O will crash if they execute from within a TSR. As an alternative, I use the code you see in console.c when I compile with Turbo C. MSC does not provide a way to test the zero flag after an interrupt call except by using assembly language, and I do not want to do that. Therefore, the MSC D-Flat implementation substitutes kbhit for keyhit and is not suitable for use in a TSR.

The getkey function in console.c reads a key from the keyboard. If the key is a function key or Alt-key value, the getkey function builds a unique 8-bit value for it so that calling functions do not need to translate the 16-bit scancode value that BIOS returns.

The beep function sounds a buzz for a short time. Observe the implementation of the wait macro. It looks something like a C++ inline function.

The cursor functions in console.c use BIOS to position the keyboard cursor, read its current position, change its shape, save and restore its configuration, and hide and unhide it.

Listing Eight, page 147, is mouse.c, which contains the code that manages the mouse. These functions are simple calls to the mouse interrupt vector with the appropriate values in registers. The generic mouse function makes the call. The other functions call it to use the mouse driver API. Before your program can use a mouse, you must have a mouse driver installed. The API to mouse drivers follows a standard established by the Microsoft mouse, so this code works for most mice. The mouse_installed function tests to see if the mouse driver is installed as a DOS device driver or as a TSR by looking at the contents of the mouse interrupt vector. If it contains zero or if it points to an IRET instruction, the driver is not there. The comments that precede each of the other mouse functions explain what they do.

The code this month is the foundation to D-Flat. You can use it to write other programs if you want, but its purpose is to support the D-Flat operating environment. Next month we'll publish the other header files and get into the functions that create windows and manage messages.

How to Get D-Flat

The complete source code package for D-Flat is on CompuServe in Library 0 of the DDJ Forum and on TelePath. Its name is DFLAT.ZIP. I will replace this file over the months as the code changes. As posted, everything compiles and works with Turbo C 2.0 and Microsoft C 6.0. There is a makefile that the make utilities of both compilers accept, and there is one example program, the MEMOPAD program, with which I am writing this column. If you want to discuss it with me, my CompuServe ID is 71101,1262.


I often receive unsolicited copies of small commercial utility programs that their purveyors hope I will like and tell you about. One such program is called VCOMP (Visual Compare). It is a text file comparison program. Until now I always used the DIFF program that came with the old Aztec C compiler. I never really liked DIFF because it does not give a clear picture of the differences between files. It displays two differing blocks of text with << and >> tokens to say which file the text comes from. The idea is that one token is an extract token and the other is an insert token. Extract the extractable text and insert the insert text and you have the original file or something like that. Not exactly intuitive. VCOMP, on the other hand, displays a composite file in a fullscreen, scrolling, paging display with the common text in one color and the differing blocks of text from the two files in two other colors. There are a number of operations you can perform prior to writing a composite file, but I haven't used any of them. I am mainly interested in seeing what changed between two versions of a C source code file. Being more human than being a good programmer, I often jam a bunch of new code into a program without a wall-to-wall test. Sometime later I find that an unrelated bug has been born. Not remembering the last time I tested the part of the program that has the bug, and therefore not remembering what I changed since the time the bug did not exist, I need to compare the current source files with old backups to see what is new. VCOMP is perfect for that. The price is $30 for DOS or OS/2 versions and $45 for both. Order from Whitney Software Inc., P.O. Box 4999, Walnut Creek, CA 94596.

No End to Huffman

Several of you wrote to point out a bug in the Huffman compression code I published in the February 1991 issue. It seems that the decompressed file had a bad last byte. Some of you even sent corrections to the code. The function that shifted bits into the compressed file did not shift down the last compressed byte before writing it out. Apparently the two files I used to test the program were, coincidentally, just the right size to avoid the bug. Eight-to-one odds are hard to beat, especially twice. Replace the outbit function in huffc.c with the code in Example 1.

Example 1: Replacement code for the huffc.c outbit function in my February 1991 column.

  /* -- collect and write bits to the
          compressed output file -- */
  static void outbit (FILE *fo, int bit)
      if (ct8 == 8 || bit == -1)  {
          while (ct8 < 8)    {
              out8 <<= 1;
          fputc(out8, fo);
          ct8 = 0;
      out8 = (out8 << 1) | bit;

by Al Stevens

<a name="0127_000a">

/* ------------- dflat.h ----------- */

#ifndef WINDOW_H
#define WINDOW_H

#define TRUE 1
#define FALSE 0

#include "system.h"
#include "config.h"
#include "rect.h"
#include "menu.h"
#include "keys.h"
#include "commands.h"
#include "config.h"
#include "dialbox.h"

/* ------ integer type for message parameters ----- */
typedef long PARAM;
typedef enum window_class    {
typedef struct window {
    CLASS class;           /* window class                  */
    char *title;           /* window title                  */
    struct window *parent; /* parent window                 */
    int (*wndproc)
        (struct window *, enum messages, PARAM, PARAM);
    /* ---------------- window dimensions ----------------- */
    RECT rc;               /* window coordinates
                                            (0/0 to 79/24)  */
    int ht, wd;            /* window height and width       */
    RECT RestoredRC;       /* restored condition rect       */
    /* -------------- linked list pointers ---------------- */
    struct window *next;        /* next window on screen    */
    struct window *prev;        /* previous window on screen*/
    struct window *nextbuilt;   /* next window built        */
    struct window *prevbuilt;   /* previous window built    */

    int attrib;                 /* Window attributes        */
    char *videosave;            /* video save buffer        */
    int condition;              /* Restored, Maximized,
                                           Minimized        */
    void *extension;            /* -> menus, dialog box, etc*/
    struct window *PrevMouse;
    struct window *PrevKeyboard;
    /* ----------------- text box fields ------------------ */
    int wlines;     /* number of lines of text              */
    int wtop;       /* text line that is on the top display */
    char *text;     /* window text                          */
    int textlen;    /* text length                          */
    int wleft;      /* left position in window viewport     */
    int textwidth;  /* width of longest line in textbox     */
    int BlkBegLine; /* beginning line of marked block       */
    int BlkBegCol;  /* beginning column of marked block     */
    int BlkEndLine; /* ending line of marked block          */
    int BlkEndCol;  /* ending column of marked block        */
    int HScrollBox; /* position of horizontal scroll box    */
    int VScrollBox; /* position of vertical scroll box      */
    /* ------------------ list box field ------------------ */
    int selection;  /* current selection                    */
    /* ----------------- edit box fields ------------------ */
    int CurrCol;    /* Current column                       */
    char *CurrLine; /* Current line                         */
    int WndRow;     /* Current window row                   */
    int TextChanged; /* TRUE if text has changed            */
    char *DeletedText; /* for undo                          */
    int DeletedLength; /*  "   "                            */
    /* ---------------- dialog box fields ----------------- */
    struct window *dFocus; /* control that has the focus    */
    int ReturnCode;        /* return code from a dialog box */

#include "message.h"
#include "classdef.h"
#include "video.h"

enum Condition     {
/* ------- window methods ----------- */
#define WindowHeight(w)      ((w)->ht)
#define WindowWidth(w)       ((w)->wd)
#define BorderAdj(w,n)       (TestAttribute(w,HASBORDER)?n:0)
#define ClientWidth(w)       (WindowWidth(w)-BorderAdj(w,2))
#define ClientHeight(w)      (WindowHeight(w)-BorderAdj(w,2))
#define WindowRect(w)        ((w)->rc)
#define GetTop(w)            (RectTop(WindowRect(w)))
#define GetBottom(w)         (RectBottom(WindowRect(w)))
#define GetLeft(w)           (RectLeft(WindowRect(w)))
#define GetRight(w)          (RectRight(WindowRect(w)))
#define GetClientTop(w)      (GetTop(w)+BorderAdj(w,1))
#define GetClientBottom(w)   (GetBottom(w)-BorderAdj(w,1))
#define GetClientLeft(w)     (GetLeft(w)+BorderAdj(w,1))
#define GetClientRight(w)    (GetRight(w)-BorderAdj(w,1))
#define GetParent(w)         ((w)->parent)
#define GetTitle(w)          ((w)->title)
#define NextWindow(w)        ((w)->next)
#define PrevWindow(w)        ((w)->prev)
#define NextWindowBuilt(w)   ((w)->nextbuilt)
#define PrevWindowBuilt(w)   ((w)->prevbuilt)
#define GetClass(w)          ((w)->class)
#define GetAttribute(w)      ((w)->attrib)
#define AddAttribute(w,a)    (GetAttribute(w) |= a)
#define ClearAttribute(w,a)  (GetAttribute(w) &= ~(a))
#define TestAttribute(w,a)   (GetAttribute(w) & (a))
#define isVisible(w)         (GetAttribute(w) & VISIBLE)
#define SetVisible(w)        (GetAttribute(w) |= VISIBLE)
#define ClearVisible(w)      (GetAttribute(w) &= ~VISIBLE)
#define gotoxy(w,x,y) cursor(w->rc.lf+(x)+1,w->
WINDOW CreateWindow(CLASS,char *,int,int,int,int,void*,WINDOW,
        int (*)(struct window *,enum messages,PARAM,PARAM),int);
void AddTitle(WINDOW, char *);
void RepaintBorder(WINDOW, RECT *);
void ClearWindow(WINDOW, RECT *, int);
void clipline(WINDOW, int, char *);
void writeline(WINDOW, char *, int, int, int);
void writefull(WINDOW, char *, int);
void SetNextFocus(WINDOW,int);
void PutWindowChar(WINDOW, int, int, int);
void GetVideoBuffer(WINDOW);
void RestoreVideoBuffer(WINDOW);
int LineLength(char *);
#define DisplayBorder(wnd) RepaintBorder(wnd, NULL)
#define DefaultWndProc(wnd,msg,p1,p2)    \
#define BaseWndProc(class,wnd,msg,p1,p2)    \
#define NULLWND ((WINDOW) 0)
struct LinkedList    {
    WINDOW FirstWindow;
    WINDOW LastWindow;
extern struct LinkedList Focus;
extern struct LinkedList Built;
extern WINDOW inFocus;
extern WINDOW CaptureMouse;
extern WINDOW CaptureKeyboard;
extern int foreground, background;
extern int WindowMoving;
extern int WindowSizing;
extern int TextMarking;
extern char *Clipboard;
extern WINDOW SystemMenuWnd;
/* --------------- border characters ------------- */
#define FOCUS_NW       '\xc9'
#define FOCUS_NE       '\xbb'
#define FOCUS_SE       '\xbc'
#define FOCUS_SW       '\xc8'
#define FOCUS_SIDE     '\xba'
#define FOCUS_LINE     '\xcd'
#define NW             '\xda'
#define NE             '\xbf'
#define SE             '\xd9'
#define SW             '\xc0'
#define SIDE           '\xb3'
#define LINE           '\xc4'
#define LEDGE          '\xc3'
#define REDGE          '\xb4'
/* ------------- scroll bar characters ------------ */
#define UPSCROLLBOX    '\x1e'
#define DOWNSCROLLBOX  '\x1f'
#define LEFTSCROLLBOX  '\x11'
#define RIGHTSCROLLBOX '\x10'
#define SCROLLBARCHAR  176
#define SCROLLBOXCHAR  178
#define CHECKMARK      251      /* menu item toggle         */
/* ----------------- title bar characters ----------------- */
#define CONTROLBOXCHAR '\xf0'
#define MAXPOINTER     24      /* maximize token            */
#define MINPOINTER     25      /* minimize token            */
#define RESTOREPOINTER 18      /* restore token             */
/* --------------- text control characters ---------------- */
#define APPLCHAR     176    /* fills application window     */
#define SHORTCUTCHAR '~'    /* prefix: shortcut key display */
#define CHANGECOLOR  174    /* prefix to change colors      */
#define RESETCOLOR   175    /* reset colors to default      */
/* ---- standard window message processing prototypes ----- */
int ApplicationProc(WINDOW, MESSAGE, PARAM, PARAM);
/* ------------- normal box prototypes ------------- */
int isWindow(WINDOW);
WINDOW inWindow(int, int);
int WndForeground(WINDOW);
int WndBackground(WINDOW);
int FrameForeground(WINDOW);
int FrameBackground(WINDOW);
int SelectForeground(WINDOW);
int SelectBackground(WINDOW);
void SetStandardColor(WINDOW);
void SetReverseColor(WINDOW);
void SetClassColors(CLASS);
#define HitControlBox(wnd, p1, p2)     \
    (TestAttribute(wnd, TITLEBAR)   && \
     TestAttribute(wnd, CONTROLBOX) && \
     p1 == 2 && p2 == 0)
/* -------- text box prototypes ---------- */
char *TextLine(WINDOW, int);
void WriteTextLine(WINDOW, RECT *, int, int);
void SetTextBlock(WINDOW, int, int, int, int);
#define BlockMarked(wnd) (  wnd->BlkBegLine ||    \
                            wnd->BlkEndLine ||    \
                            wnd->BlkBegCol  ||    \
#define ClearBlock(wnd) wnd->BlkBegLine = wnd->BlkEndLine =  \
                        wnd->BlkBegCol  = wnd->BlkEndCol = 0;
#define GetText(w)        ((w)->text)
/* --------- menu prototypes ---------- */
int CopyCommand(char *, char *, int, int);
void PrepOptionsMenu(void *, struct Menu *);
void PrepEditMenu(void *, struct Menu *);
void PrepWindowMenu(void *, struct Menu *);
void BuildSystemMenu(WINDOW);
/* ------------- edit box prototypes ----------- */
#define isMultiLine(wnd)     TestAttribute(wnd, MULTILINE)
/* --------- message box prototypes -------- */
void MessageBox(char *, char *);
void ErrorMessage(char *);
int TestErrorMessage(char *);
int YesNoBox(char *);
int MsgHeight(char *);
int MsgWidth(char *);
/* ------------- dialog box prototypes -------------- */
int DialogBox(DBOX *, int (*)(struct window *,
                        enum messages, PARAM, PARAM));
int DlgOpenFile(char *, char *);
int DlgSaveAs(char *);
void GetDlgListText(WINDOW, char *, enum commands);
int DlgDirList(WINDOW, char *, enum commands,
                            enum commands, unsigned);
int RadioButtonSetting(DBOX *, enum commands);
void PushRadioButton(DBOX *, enum commands);
void PutItemText(WINDOW, enum commands, char *);
void GetItemText(WINDOW, enum commands, char *, int);
/* ------------- help box prototypes ------------- */
void HelpFunction(void);
void LoadHelpFile(void);
#define swap(a,b){int x=a;a=b;b=x;}


<a name="0127_000b">
<a name="0127_000c">
<a name="0127_000c">

/* ----------- keys.h ------------ */
#ifndef KEYS_H
#define KEYS_H
#define RUBOUT        8
#define BELL          7
#define ESC          27
#define ALT_BS      197
#define SHIFT_DEL   198
#define CTRL_INS    186
#define SHIFT_INS   185
#define F1          187
#define F2          188
#define F3          189
#define F4          190
#define F5          191
#define F6          192
#define F7          193
#define F8          194
#define F9          195
#define F10         196
#define CTRL_F1     222
#define CTRL_F2     223
#define CTRL_F3     224
#define CTRL_F4     225
#define CTRL_F5     226
#define CTRL_F6     227
#define CTRL_F7     228
#define CTRL_F8     229
#define CTRL_F9     230
#define CTRL_F10    231
#define ALT_F1      232
#define ALT_F2      233
#define ALT_F3      234
#define ALT_F4      235
#define ALT_F5      236
#define ALT_F6      237
#define ALT_F7      238
#define ALT_F8      239
#define ALT_F9      240
#define ALT_F10     241
#define HOME        199
#define UP          200
#define PGUP        201
#define BS          203
#define FWD         205
#define END         207
#define DN          208
#define PGDN        209
#define INS         210
#define DEL         211
#define CTRL_HOME   247
#define CTRL_PGUP   132
#define CTRL_BS     243
#define CTRL_FIVE   143
#define CTRL_FWD    244
#define CTRL_END    245
#define CTRL_PGDN   246
#define SHIFT_HT    143
#define ALT_A       158
#define ALT_B       176
#define ALT_C       174
#define ALT_D       160
#define ALT_E       146
#define ALT_F       161
#define ALT_G       162
#define ALT_H       163
#define ALT_I       151
#define ALT_J       164
#define ALT_K       165
#define ALT_L       166
#define ALT_M       178
#define ALT_N       177
#define ALT_O       152
#define ALT_P       153
#define ALT_Q       144
#define ALT_R       147
#define ALT_S       159
#define ALT_T       148
#define ALT_U       150
#define ALT_V       175
#define ALT_W       145
#define ALT_X       173
#define ALT_Y       149
#define ALT_Z       172
#define ALT_1      0xf8
#define ALT_2      0xf9
#define ALT_3      0xfa
#define ALT_4      0xfb
#define ALT_5      0xfc
#define ALT_6      0xfd
#define ALT_7      0xfe
#define ALT_8      0xff
#define ALT_9      0x80
#define ALT_0      0x81
#define ALT_HYPHEN  130

#define RIGHTSHIFT 0x01
#define LEFTSHIFT  0x02
#define CTRLKEY    0x04
#define ALTKEY     0x08
#define SCROLLLOCK 0x10
#define NUMLOCK    0x20
#define CAPSLOCK   0x40
#define INSERTKEY  0x80

struct keys {
    int keycode;
    char *keylabel;
int getkey(void);
int getshift(void);
int keyhit(void);
void beep(void);
extern struct keys keys[];
extern char altconvert[];


<a name="0127_000d">
<a name="0127_000e">
<a name="0127_000e">

/* --------------- system.h -------------- */
#ifndef SYSTEM_H
#define SYSTEM_H
/* ----- interrupt vectors ----- */
#define TIMER  8
#define VIDEO  0x10
#define KEYBRD 0x16
#define DOS    0x21
#define CRIT   0x24
#define MOUSE  0x33
/* ------- platform-dependent values ------ */
#define FREQUENCY 100
#define COUNT (1193280L / FREQUENCY)
#define ZEROFLAG 0x40
#define MAXSAVES 50
#define SCREENWIDTH  80
/* ----- keyboard BIOS (0x16) functions -------- */
#define READKB 0
#define KBSTAT 1
/* ------- video BIOS (0x10) functions --------- */
#define SETCURSOR     2
#define READCURSOR    3
#define HIDECURSOR 0x20
/* ------- the interrupt function registers -------- */
typedef struct {
    int bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl;
/* ---------- cursor prototypes -------- */
void curr_cursor(int *x, int *y);
void cursor(int x, int y);
void hidecursor(void);
void unhidecursor(void);
void savecursor(void);
void restorecursor(void);
void normalcursor(void);
void set_cursor_type(unsigned t);
void videomode(void);
/* ---------- mouse prototypes ---------- */
int mouse_installed(void);
int mousebuttons(void);
void get_mouseposition(int *x, int *y);
void set_mouseposition(int x, int y);
void show_mousecursor(void);
void hide_mousecursor(void);
int button_releases(void);
void resetmouse(void);
#define leftbutton()     (mousebuttons()&1)
#define rightbutton()     (mousebuttons()&2)
#define waitformouse()     while(mousebuttons());
/* ------------ timer macros -------------- */
#define timed_out(timer)         (timer==0)
#define set_timer(timer, secs)     timer=(secs)*182/10+1
#define disable_timer(timer)     timer = -1
#define timer_running(timer)     (timer > 0)
#define countdown(timer)         --timer
#define timer_disabled(timer)     (timer == -1)

#ifdef MSC
/* ============= MSC Compatibility Macros ============ */
#define BLACK         0
#define BLUE          1
#define GREEN         2
#define CYAN          3
#define RED           4
#define MAGENTA       5
#define BROWN         6
#define LIGHTGRAY     7
#define DARKGRAY      8
#define LIGHTBLUE     9
#define LIGHTGREEN   10
#define LIGHTCYAN    11
#define LIGHTRED     12
#define YELLOW       14
#define WHITE        15

#define getvect(v)   _dos_getvect(v)
#define setvect(v,f) _dos_setvect(v,f)
#define MK_FP(s,o)   ((void far *) \
               (((unsigned long)(s) << 16) | (unsigned)(o)))
#undef FP_OFF
#undef FP_SEG
#define FP_OFF(p)    ((unsigned)(p))
#define FP_SEG(p)    ((unsigned)((unsigned long)(p) >> 16))
#define poke(a,b,c)  (*((int  far*)MK_FP((a),(b))) = (int)(c))
#define pokeb(a,b,c) (*((char far*)MK_FP((a),(b))) = (char)(c))
#define peek(a,b)    (*((int  far*)MK_FP((a),(b))))
#define peekb(a,b)   (*((char far*)MK_FP((a),(b))))
#define findfirst(p,f,a) _dos_findfirst(p,a,f)
#define findnext(f)      _dos_findnext(f)
#define ffblk            find_t
#define ff_name          name
#define ff_fsize         size
#define ff_attrib        attrib
#define fnsplit          _splitpath
#define fnmerge          _makepath
#define EXTENSION         2
#define FILENAME          4
#define DIRECTORY         8
#define DRIVE            16
#define MAXPATH          80
#define MAXDRIVE          3
#define MAXDIR           66
#define MAXFILE           9
#define MAXEXT            5
#define setdisk(d)       _dos_setdrive((d)+1, NULL)
#define bioskey          _bios_keybrd
#define keyhit           kbhit

<a name="0127_000f">
<a name="0127_0010">
<a name="0127_0010">

/* ----------- rect.h ------------ */
#ifndef RECT_H
#define RECT_H

typedef struct    {
    int lf,tp,rt,bt;
#define within(p,v1,v2)   ((p)>=(v1)&&(p)<=(v2))
#define RectTop(r)        (
#define RectBottom(r)     (
#define RectLeft(r)       (r.lf)
#define RectRight(r)      (r.rt)
#define InsideRect(x,y,r) (within(x,RectLeft(r),RectRight(r)) \
                               &&                             \
#define ValidRect(r)      (RectRight(r) || RectLeft(r))
#define RectWidth(r)      (RectRight(r)-RectLeft(r)+1)
#define RectHeight(r)     (RectBottom(r)-RectTop(r)+1)
RECT subRectangle(RECT, RECT);
RECT RelativeRectangle(RECT, RECT);
RECT ClientRect(void *);
RECT SetRect(int,int,int,int);

<a name="0127_0011">
<a name="0127_0012">
<a name="0127_0012">

/* ---------------- video.h ----------------- */

#ifndef VIDEO_H
#define VIDEO_H

#include "rect.h"

void getvideo(RECT, void far *);
void storevideo(RECT, void far *);
extern unsigned video_mode;
extern unsigned video_page;
void wputch(WINDOW, int, int, int);
int GetVideoChar(int, int);
void PutVideoChar(int, int, int);
void get_videomode(void);
void wputs(WINDOW, void *, int, int);

#define clr(fg,bg) ((fg)|((bg)<<4))
#define vad(x,y) ((y)*160+(x)*2)
#define ismono() (video_mode == 7)
#define istext() (video_mode < 4)
#define videochar(x,y) (GetVideoChar(x,y) & 255)


<a name="0127_0013">
<a name="0127_0014">
<a name="0127_0014">

/* --------------------- video.c -------------------- */
#include <stdio.h>
#include <dos.h>
#include <string.h>
#include <conio.h>
#include "dflat.h"

static unsigned video_address;
/* -- read a rectangle of video memory into a save buffer -- */
void getvideo(RECT rc, void far *bf)
    int ht = RectBottom(rc)-RectTop(rc)+1;
    int bytes_row = (RectRight(rc)-RectLeft(rc)+1) * 2;
    unsigned vadr = vad(RectLeft(rc), RectTop(rc));
    while (ht--)    {
        movedata(video_address, vadr, FP_SEG(bf),
                FP_OFF(bf), bytes_row);
        vadr += 160;
        (char far *)bf += bytes_row;

/* -- write a rectangle of video memory from a save buffer -- */
void storevideo(RECT rc, void far *bf)
    int ht = RectBottom(rc)-RectTop(rc)+1;
    int bytes_row = (RectRight(rc)-RectLeft(rc)+1) * 2;
    unsigned vadr = vad(RectLeft(rc), RectTop(rc));
    while (ht--)    {
        movedata(FP_SEG(bf), FP_OFF(bf), video_address,
                vadr, bytes_row);
        vadr += 160;
        (char far *)bf += bytes_row;

/* -------- read a character of video memory ------- */
int GetVideoChar(int x, int y)
    int c;
    c = peek(video_address, vad(x,y));
    return c;

/* -------- write a character of video memory ------- */
void PutVideoChar(int x, int y, int c)
    if (x < SCREENWIDTH && y < SCREENHEIGHT)    {
        poke(video_address, vad(x,y), c);

/* -------- write a character to a window ------- */
void wputch(WINDOW wnd, int c, int x, int y)
    int x1 = GetClientLeft(wnd)+x;
    int y1 = GetClientTop(wnd)+y;
    if (x1 < SCREENWIDTH && y1 < SCREENHEIGHT)    {
            vad(x1,y1),(c & 255) |
                (clr(foreground, background) << 8));

/* ------- write a string to a window ---------- */
void wputs(WINDOW wnd, void *s, int x, int y)
    int x1 = GetLeft(wnd)+x;
    int y1 = GetTop(wnd)+y;
    if (x1 < SCREENWIDTH && y1 < SCREENHEIGHT)    {
        int fg = foreground;
        int bg = background;
        unsigned char *str = s;
        char ss[200];
        int ln[SCREENWIDTH];
        int *cp1 = ln;
        int len;
        strncpy(ss, s, 199);
        ss[199] = '\0';
        clipline(wnd, x, ss);
        str = (unsigned char *) ss;
        while (*str)    {
            if (*str == CHANGECOLOR)    {
                foreground = (*str++) & 0x7f;
                background = (*str++) & 0x7f;
            if (*str == RESETCOLOR)    {
                foreground = fg;
                background = bg;
            *cp1++ = (*str & 255) |
                (clr(foreground, background) << 8);
        foreground = fg;
        background = bg;
        len = (int)(cp1-ln);
        if (x1+len > SCREENWIDTH)
            len = SCREENWIDTH-x1;
        movedata(FP_SEG(ln), FP_OFF(ln), video_address,
            vad(x1,y1), len*2);

/* --------- get the current video mode -------- */
void get_videomode(void)
    /* ---- Monochrome Display Adaptor or text mode ---- */
    if (ismono())
        video_address = 0xb000;
        /* ------ Text mode -------- */
        video_address = 0xb800 + video_page;

<a name="0127_0015">
<a name="0127_0016">
<a name="0127_0016">

/* ----------- console.c ---------- */

#include <conio.h>
#include <bios.h>
#include <dos.h>
#include "system.h"
#include "keys.h"

/* ----- table of alt keys for finding shortcut keys ----- */
char altconvert[] = {

unsigned video_mode;
unsigned video_page;

static int near cursorpos[MAXSAVES];
static int near cursorshape[MAXSAVES];
static int cs = 0;

static union REGS regs;

#ifndef MSC
#define ZEROFLAG 0x40
/* ---- Test for keystroke ---- */
int keyhit(void)
    _AH = 1;
    return (_FLAGS & ZEROFLAG) == 0;

/* ---- Read a keystroke ---- */
int getkey(void)
    int c;
    while (keyhit() == 0)
    if (((c = bioskey(0)) & 0xff) == 0)
        c = (c >> 8) | 0x80;
    return c & 0xff;

/* ---------- read the keyboard shift status --------- */
int getshift(void)
    regs.h.ah = 2;
    int86(KEYBRD, &regs, &regs);

/* ------- macro to wait one clock tick -------- */
#define wait()                     \
{                                  \
    int now = peek(0x40,0x6c);     \
    while (now == peek(0x40,0x6c)) \
        ;                          \

/* -------- sound a buzz tone ---------- */
void beep(void)
    outp(0x43, 0xb6);               /* program the frequency */
    outp(0x42, (int) (COUNT % 256));
    outp(0x42, (int) (COUNT / 256));
    outp(0x61, inp(0x61) | 3);      /* start the sound */
    outp(0x61, inp(0x61) & ~3);     /* stop the sound  */

/* -------- get the video mode and page from BIOS -------- */
void videomode(void)
    regs.h.ah = 15;
    int86(VIDEO, &regs, &regs);
    video_mode =;
    video_page = regs.x.bx;
    video_page &= 0xff00;
    video_mode &= 0x7f;

/* ------ position the cursor ------ */
void cursor(int x, int y)
    regs.x.dx = ((y << 8) & 0xff00) + x; = 0x0200;
    regs.x.bx = video_page;
    int86(VIDEO, &regs, &regs);

/* ------ get cursor shape and position ------ */
static void near getcursor(void)
    regs.h.ah = READCURSOR;
    regs.x.bx = video_page;
    int86(VIDEO, &regs, &regs);

/* ------- get the current cursor position ------- */
void curr_cursor(int *x, int *y)
    *x = regs.h.dl;
    *y = regs.h.dh;

/* ------ save the current cursor configuration ------ */
void savecursor(void)
    if (cs < MAXSAVES)    {
        cursorshape[cs] =;
        cursorpos[cs] = regs.x.dx;

/* ---- restore the saved cursor configuration ---- */
void restorecursor(void)
    if (cs)    {
        regs.x.dx = cursorpos[cs];
        regs.h.ah = SETCURSOR;
         regs.x.bx = video_page;
        int86(VIDEO, &regs, &regs);

/* ------ make a normal cursor ------ */
void normalcursor(void)

/* ------ hide the cursor ------ */
void hidecursor(void)
    getcursor(); |= HIDECURSOR;
    regs.h.ah = SETCURSORTYPE;
    int86(VIDEO, &regs, &regs);

/* ------ unhide the cursor ------ */
void unhidecursor(void)
    getcursor(); &= ~HIDECURSOR;
    regs.h.ah = SETCURSORTYPE;
    int86(VIDEO, &regs, &regs);

/* ---- use BIOS to set the cursor type ---- */
void set_cursor_type(unsigned t)
    regs.h.ah = SETCURSORTYPE;
     regs.x.bx = video_page; = t;
    int86(VIDEO, &regs, &regs);

<a name="0127_0017">
<a name="0127_0018">
<a name="0127_0018">

/* ------------- mouse.c ------------- */

#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include "system.h"

static union REGS regs;

static void near mouse(int m1,int m2,int m3,int m4)
    regs.x.dx = m4; = m3;
    regs.x.bx = m2; = m1;
    int86(MOUSE, &regs, &regs);

/* ---------- reset the mouse ---------- */
void resetmouse(void)

/* ----- test to see if the mouse driver is installed ----- */
int mouse_installed(void)
    unsigned char far *ms;
    ms = MK_FP(peek(0, MOUSE*4+2), peek(0, MOUSE*4));
    return (ms != NULL && *ms != 0xcf);

/* ------ return true if mouse buttons are pressed ------- */
int mousebuttons(void)
    if (mouse_installed())
    return regs.x.bx & 3;

/* ---------- return mouse coordinates ---------- */
void get_mouseposition(int *x, int *y)
    if (mouse_installed())    {
        *x =;
        *y = regs.x.dx/8;

/* -------- position the mouse cursor -------- */
void set_mouseposition(int x, int y)

/* --------- display the mouse cursor -------- */
void show_mousecursor(void)

/* --------- hide the mouse cursor ------- */
void hide_mousecursor(void)

/* --- return true if a mouse button has been released --- */
int button_releases(void)
    return regs.x.bx;

Copyright © 1991, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.