This month's column delves into several different areas. The theme is event-driven programming, and to show you how it works, I had to come up with a software model that uses the architecture of events. To do that, I had to settle on something that could tell the story in one encapsulated column. So, besides the event-driven software, you will find drivers for the mouse, the keyboard, and the screen.
You will learn the two sides of an event-driven architecture. You will see how the event and message managers work, and you will see how an application program uses the message system to manage its execution.
In a typical event-driven model, the events happen when you hit a key, slap the mouse around, or take some other external action that makes the program do something. The application program that accompanies this column is a simple screen grabber program for a textmode PC under DOS. It runs from the DOS command line, but, with the proper driver, it can be a TSR, which means that it can be resident in memory. The program uses the keyboard or mouse to describe a rectangle on the screen, which it can write to a text file. I use a variation of this program to capture screen snapshots for software documentation.
Although most of the code deals with the mouse hardware and -- next month -- all the ornery stuff that you have to do to get a TSR to work, the purpose of the program is to demonstrate event-driven programming in the C language. If you do not want to mess with the TSR part of the project, you can skip next month, use the stubbed-in main function that substitutes as the popup function, and run the program each time from the command line. Of course, to be useful for something other than as an example of event-driven programming, the screen grabber needs to be memory resident.
Another requisite: I wrote the hardware drivers in Turbo C 2.0 and used the pseudoregister and other extensions that the Turbo dialect of the language provide. If you want to use a different compiler, you must port the program to the other compiler's conventions for managing interrupts, hardware registers, and the like. No two of them do it quite the same way. The application part of the code is standard C, but the device drivers use the Turbo extensions.
How does an event-driven program work? First, consider how you might write such a program by using the traditional procedural approach. After you initialized the program, you would poll the keyboard and the mouse. When one of them did something, you would read the device, determine if what it did had a bearing on what your program needed, take appropriate action, and return to poll the hardware again. When one of the user's actions indicated that the program was done, you would not return to the polling loop, but would shut things down and exit from the program. Polling the devices, reading them, and interpreting their different inputs are integral parts of your program.
An event-driven program does all of that, too, but in a somewhat different manner. Instead of polling devices and reacting, an event-driven program uses an event-dispatching function to call your applications functions when an event has occurred. The dispatcher sends a message that your function interprets. So, for example, instead of reading the keyboard, your program waits until the dispatcher sends you a message telling you that the user pressed a key.
The event software watches the hardware for you and queues the hardware events as messages in a queue. The dispatcher retrieves messages from the queue and sends them to your functions. As you might expect, your functions can put messages into the queue as well -- messages that your application will receive in turn from the dispatcher.
What is the point of all this? Why is it any better than the old way? First, an event-driven program tends to be more orderly. I know, you've heard it all before. Every new trendy paradigm that someone is puffing up makes the same claim. This is one you'll just have to see for yourself. The advantages of the event-driven architecture were not apparent to me until I ported a conventional program into it. Things got a lot easier to maintain. It isn't the answer to every programming problem, but in some cases it will make program design and maintenance easier because it imposes its form of structure on your code. The more structure, the better.
Second, if you get into Windows programming, you'll find yourself deeply ensconced in event-driven programming. In the Windows world, you create a window with an associated window-processing function that you provide. The windows dispatcher sends messages to the window-processing functions whenever an event occurs that might be important to the windows. For example, whenever you move the mouse across a window, the dispatcher sends the window a message about it. When you type a key, the dispatcher sends a message to whichever window is active.
The event-driven paradigm is especially applicable to Windows programming. For example, the Windows user can choose a menu command by one of several manual actions. Click the command with the mouse. Press the command's accelerator key. Open the menu and press the shortcut key. Or move the menu cursor to the command and press the Enter key. But your application program does not care about all that. The message manager takes care of it. When the user chooses the command, your program gets a message to that effect, one message only, regardless of how the user made the choice. The message tells your program that the user chose the command. What is more, another part of your program can simulate the same command choice simply by sending the same message.
To build our event-driven program, first we need some software to recognize events so they can become messages. The screen grabber program will work with the keyboard and the mouse, and it deals with the video screen. You want to move the details of the hardware away from the application code and into the event manager software. Although most event-driven environments come complete with hardware drivers, we'll need to build our own. Remember, though, that the point of all this is in the event and message code that comes later.
Listings One and Two are keys.h and getkey.c, the functions that manage the keyboard and the screen cursor. You might have seen similar modules in other programs. The keys.h file defines some values for keys that the program will use. It also provides the prototypes for the keyboard and cursor functions. The getkey.c file has two keyboard and several cursor functions. The keyhit function returns a true value if you have pressed a key. The getkey function reads a key from the keyboard, translating function keys into values above 128. The key definitions in keys.h reflect this behavior. The cursor functions deal with the keyboard cursor, allowing a program to position the cursor, get its current position, hide it, unhide it, and save and restore the cursor context and configuration of whatever other program the TSR interrupts.
Listings Three and Four, are mouse.h and mouse.c, the hardware drivers for the PC's mouse. The mouse.h file defines the prototypes and some macros. The mouse.c file contains the functions that allow a program to determine if a mouse is installed, read or set the screen coordinates of its cursor, read its buttons, turn the cursor on and off, and save and restore the mouse context of the program that the TSR interrupts. There are more mouse operations than these functions support. I have included only the ones that the program needs. Microsoft Press publishes the Microsoft Mouse Programmer's Reference that specifies how all the mouse operations work.
Listings Five and Six are message.h and message.c, the message driver software. The message.h file defines the message codes. For this example, there are only 16 messages. More about them later. An application might add custom messages to this list.
There are three functions that a program calls to use the event-driven architecture of the message drivers. The dispatch_message function is the message dispatching module. You pass it the address of your application's message processing function, which is a void function that expects to receive three integer parameters. The first parameter will be the message code, and the other two are generic parameters to be used by the messages any way they need.
You call the post_message function to insert messages into the message queue. The dispatcher will send these messages in the order in which they appear in the queue. Messages that the post_message function posts are not sent right away. To send a message immediately, call the send_message function. You would use send_message when the message returns a value or when its effects must take place on the spot.
The dispatcher sends the START message to your function when the process first begins, and your program posts the STOP message to tell the program at large that the message-dispatching loop should end.
In this simple example of an event-driven architecture, a message cycle begins and ends and has one destination. In more complex systems, such as Windows, there are many more messages, different categories of messages, and they can be sent to many different processing functions. For example, you can have several windows on the screen and each of them can deal with its own copy of the messages that tell it to start and stop.
The dispatcher sends the KEYBOARD message when the user presses a key. The first of the two parameters holds the value of the keystroke.
You send the CURRENT_KEBOARD_CURSOR and KEYBOARD_CURSOR messages yourself. The CURRENT_KEYBOARD_CURSOR message returns the current cursor coordinates in the two parameters. The first parameter is the address of where the message will copy the X coordinate, and the second parameter is the address of the Y coordinate. The KEYBOARD_CURSOR message changes the cursor location. The first parameter is the new X coordinate and the second parameter is the new Y coordinate.
The dispatcher sends the RIGHT_BUTTON and LEFT_BUTTON messages when you press the right or left button on the mouse. As long as you hold the button down, the dispatcher will continue to send the message. The dispatcher sends the MOUSE_MOVED message when you move the mouse, whether a button is pressed or not. When you release a button, the dispatcher sends the BUTTON_RELEASED message. In these four mouse messages, the first parameter contains the column (X) screen coordinate, and the second parameter contains the row (Y) screen coordinate where the mouse cursor was when the event occurred.
Your program sends the CURRENT_MOUSE_CURSOR, MOUSE_CURSOR, SHOW_MOUSE, and HIDE_MOUSE messages. The CURRENT_MOUSE_CURSOR message returns the current mouse screen coordinates in the two parameters. The first parameter is the address of where the message will copy the X coordinate, and the second parameter is the address of the Y coordinate. The MOUSE_CURSOR message changes the mouse cursor location. The first parameter is the new X coordinate and the second parameter is the new Y coordinate.
The HIDE_MOUSE and SHOW_MOUSE messages hide and display the mouse cursor. The two parameters are zero for both messages.
There are other messages that a mouse driver might send. You might want to know when you have double-clicked a location, for example. A keyboard driver might send the status of the Shift, Alt, and Ctrl keys in the second parameter. The small set of messages and events in this example serve to illustrate the principle, but you would use many more in an actual event-driven architecture.
Your program sends the VIDEO_CHAR message to retrieve a character that is on the screen. The parameters are the X and Y screen coordinates, and the message returns the character.
The message.h file defines the RECT structure, which contains the upper left and bottom right X/Y screen coordinates. The GET_VIDEORECT and PUT_VIDEORECT messages read and write video data between the screen and your buffer. The first parameter is the address of a RECT structure that has the coordinates of the rectangle, and the second parameter is the address of the buffer.
Observe that the PARAM typedef in message.h for the message parameters is an integer. When a message expects addresses, you must cast them to the PARAM type. If you use a large data model, you should change the PARAM typedef to a long integer.
The functions in message.c maintain a circular queue of messages. The post_message function adds an entry to that queue. The queue entries contain the message code and the two parameters. The collect_message function polls the hardware for events and posts messages to the queue. It also recognizes when it is executing for the first time and posts the START message. It returns a true value if messages exist in the queue when it exits. It returns false if the queue is empty.
The dispatch_message function is the one that an application calls to have queued messages dispatched. It calls collect_message to have any uncollected events queued and to see if there are any messages in the queue to send to the application. If a message is on the queue, dispatch_message dequeues the message and calls send_message to send it to the application's message processing function. The send_message function also acts on messages such as MOUSE_CURSOR that the application sends to drive the hardware. Observe that when send_message calls the application's message processing function, it takes action on the messages only if the message processing function returns a true value. This allows the application to override any default message processing. In the case of this example, that option never gets used, but the architecture is typical of event-driven systems. The send_message function can return a value to the caller. For example, the VIDEO_CHAR message returns the video character.
The dispatch_message function returns a true value to its caller whether or not it found a message to send. When it sees that it has just dispatched the STOP message, it returns a FALSE value instead. Therefore, an application program should continue to call dispatch_message as long as the function returns a true value. The program should quit when dispatch_message returns a false value.
The mouse_event function calls the mouse functions to build mouse event messages. The collect_messages function calls it, and mouse_event returns any mouse events that occur. The function reads the mouse position and posts the X and Y coordinates into global variables. The collect_message function will use these variables to build the parameters for the message to be queued. If the mouse position has changed since the last time collect_message called mouse_event, the function will return the MOUSE_MOVED message. If you have released a mouse button since the last time the function checked, the function returns the BUTTON_RELEASED message. If the right or left button is down, the function returns the RIGHT_BUTTON or LEFT_BUTTON messages.
You can see that the message and event functions are small. It doesn't take much code to implement a simple event-driven environment. The strength of the approach is in how you use it. Listing Seven is copyscrn.c, the event-driven application that illustrates the use of the event and message manager software. Observe the #ifdef TSR statement at the beginning of copyscrn.c. We'll use that compile-time conditional expression next month to install this program as a memory resident utility.
The program begins by creating a text disk file named "grab.dat." Then it makes repeated calls to dispatch_message, passing the address of the message_proc function. These calls continue until the dispatch_message function returns a false value, whereupon the copyscrn function closes the text file and returns.
From this point on, think of this program as an event-driven system. The only way anything will happen is when the user does something with the keyboard or mouse. These events will become messages that the program's message_proc function receives and processes.
If you look back into message.c, you will see that the first time copyscrn calls dispatch_message, the collect_message function will post the START message to be sent to the application's message_proc function. It uses the START message to tell it to initiate processing. In this case, it sends messages that save the keyboard and mouse cursor positions and set both cursors to the upper left corner of the screen.
Now the program is running with the keyboard and screen cursors both in the upper left corner of the screen and with a text file open. Nothing is going on out here in application land because the user isn't doing anything. But the dispatcher is busily watching the keyboard and mouse for some action. Suppose now that the user presses a key on the keyboard. Look back into message.c. The collect_messages function is calling the keyboard driver's keyhit function to see if a key was pressed. When keyhit returns a true value, collect_messages posts the KEYBOARD message into the queue with the value returned from getkey as the first parameter. Because the message-dispatching loop is running, send_message will eventually send that message to the message_proc function in the copyscrn.c source file. You can see that the message_proc function processes the KEYBOARD message, doing different things depending on the value of the keystroke. It is watching for the up, down, right, and left arrow keys; the Esc key; the Enter key; and the F2 key. It will ignore any other keys. You can use the arrow keys to move the cursor all over the screen. When you press F2, you tell the program that you want to begin describing a screen rectangle to write to the file. The cursor location when you press F2 will become one of the corners of the rectangle. When you subsequently move the cursor, the program defines the rectangle by reversing the video colors of the characters within the rectangle. If you press F2 again, you are telling the program that you do not like that rectangle. The reverse video rectangle goes away, and you can move to a new starting point again and press F2. When the rectangle is defined, you press the Enter key. To forget the whole thing, you press the Esc key.
The forward, upward, backward, and downward functions manage the cursor position. The highlight function draws and redraws the rectangle as you move the cursor around. The setstart function turns screen marking on and off and positions the block markers.
Observe the treatment of the Enter ('\r') key and the Esc key in the message_proc function. Remember that these keys terminate the program by writing the rectangle to disk or by ignoring it. When their messages arrive, the message_proc function calls post_message to post the STOP message. The first parameter has a true value for the Enter key and a false value for the Esc key. That message, like all messages, will eventually be sent to message_proc as well. Now observe the function's treatment of the STOP message. If a block is marked, the function calls highlight to turn it off. If the first parameter is true, the function calls the writescreen function to write the block to the disk file. In either case, the function sends messages to restore the mouse and keyboard cursor to the positions they had when the program began running. Remember that the dispatch_message function uses the STOP message to tell it to return a false value to its caller to stop the program. But it does that after your message-processing function gets a crack at the STOP message.
The mouse works in a manner similar to the keyboard. You can move the mouse around until its cursor is on the corner -- any corner -- of the rectangle you want to define. Press the left mouse button and hold it down while you move the cursor. The program will follow your movements and define the rectangle. Release the button to stop defining. If you do not like the rectangle being defined, move to a new corner and press the left button again. To write the rectangle to disk press the Enter key or the right mouse button. The Esc key rejects the rectangle and terminates the program.
When you press the left button, the message_proc function receives the LEFT_BUTTON message. As long as you hold that button down, that message will continue to come in, so you only want to do something with it the first time. If the program is not presently marking a block with the mouse, this message gets it started by saving the coordinates and calling setstart.
The message_proc function gets the MOUSE_MOVED message whenever you move the mouse. If you are not currently marking the rectangle, the function ignores the message. If you marking, the function calls one of the same forward, upward, backward, and downward functions that the KEYBOARD message uses to define the rectangle.
When the BUTTON_RELEASED message comes in, the function notes that it is no longer marking a block with the mouse. When the RIGHT_BUTTON comes in the function does the same thing that the Enter key value of the KEYBOARD message does -- it posts a STOP message with a true first parameter to tell the STOP message to write the rectangle to the screen.
The program allows you to define a rectangle with the mouse and then press F2 to define a different one with the keyboard. It allows you to click the mouse during a keyboard definition to override the keyboard's rectangle and start a new one with the mouse.
The event-driven architecture of this small program is typical of that of most event-driven systems. Most of the components are here, albeit on a smaller scale. You do not need hardware events to use the event-driven architecture. You could use traditional input/output functions and implement soft events to manage the processing flow of a program. This is yet another technique, one that offers a different way to view programming and one that seems to have some promise to bring order to complex programming problems.
As I write this column, I am listening to "Pacific High," a compact disk that Borland sent me along with a huge calendar with works of art superimposed on pictures of diskettes. The CD features Philippe Kahn, his flutes, compositions, and a collection of different musical styles and musicians. It's mostly jazz, and mostly listenable, but every now and then it reaches into that post-Coltrane cacophonous sound that makes my cat bark.
The drum machine programmer got a mention in the CD's liner notes. I hope that's not a new paradigm.
We'll discuss a C language TSR driver that will make this month's screen grabber and most other C programs memory resident TSR programs. I'll try to unravel some of the arcane underpinnings of the TSR.
Copyright © 1991, Dr. Dobb's JournalEvents and Messages
Hardware Drivers
The Keyboard and the Cursor
The Mouse
The Message Driver
The Messages
The Message Queue
The Screen Grabber
The START Message
Keyboard Messages
The STOP Message
Mouse Messages
The Message is the Medium
Pacific High
Next Month...
_C PROGRAMMING COLUMN_
by Al Stevens
[LISTING ONE]
/* ----------- keys.h ------------ */
#define TRUE 1
#define FALSE 0
#define ESC 27
#define F2 188
#define UP 200
#define FWD 205
#define DN 208
#define BS 203
int getkey(void);
int keyhit(void);
void curr_cursor(int *, int *);
void cursor(int, int);
void hidecursor(void);
void unhidecursor(void);
void savecursor(void);
void restorecursor(void);
void set_cursor_type(unsigned);
#define normalcursor() set_cursor_type(0x0607)
[LISTING TWO]
/* ----------- getkey.c ---------- */
#include <bios.h>
#include <dos.h>
#include "keys.h"
#define KEYBOARD 0x16
#define ZEROFLAG 0x40
#define SETCURSORTYPE 1
#define SETCURSOR 2
#define READCURSOR 3
#define HIDECURSOR 0x20
static unsigned video_mode;
static unsigned video_page;
static int cursorpos;
static int cursorshape;
/* ---- Test for keystroke ---- */
int keyhit(void)
{
_AH = 1;
geninterrupt(KEYBOARD);
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;
else
c &= 0xff;
return c;
}
static void videoint(void)
{
static unsigned oldbp;
_DI = _DI;
oldbp = _BP;
geninterrupt(0x10);
_BP = oldbp;
}
void videomode(void)
{
_AH = 15;
videoint();
video_mode = _AL;
video_page = _BX;
video_page &= 0xff00;
video_mode &= 0x7f;
}
/* ---- Position the cursor ---- */
void cursor(int x, int y)
{
videomode();
_DX = ((y << 8) & 0xff00) + x;
_AX = 0x0200;
_BX = video_page;
videoint();
}
/* ---- get cursor shape and position ---- */
static void near getcursor(void)
{
videomode();
_AH = READCURSOR;
_BX = video_page;
videoint();
}
/* ---- Get current cursor position ---- */
void curr_cursor(int *x, int *y)
{
getcursor();
*x = _DL;
*y = _DH;
}
/* ---- Hide the cursor ---- */
void hidecursor(void)
{
getcursor();
_CH |= HIDECURSOR;
_AH = SETCURSORTYPE;
videoint();
}
/* ---- Unhide the cursor ---- */
void unhidecursor(void)
{
getcursor();
_CH &= ~HIDECURSOR;
_AH = SETCURSORTYPE;
videoint();
}
/* ---- Save the current cursor configuration ---- */
void savecursor(void)
{
getcursor();
cursorshape = _CX;
cursorpos = _DX;
}
/* ---- Restore the saved cursor configuration ---- */
void restorecursor(void)
{
videomode();
_DX = cursorpos;
_AH = SETCURSOR;
_BX = video_page;
videoint();
set_cursor_type(cursorshape);
}
/* ----------- set the cursor type -------------- */
void set_cursor_type(unsigned t)
{
videomode();
_AH = SETCURSORTYPE;
_BX = video_page;
_CX = t;
videoint();
}
[LISTING THREE]
/* ------------- mouse.h ------------- */
#ifndef MOUSE_H
#define MOUSE_H
#define MOUSE 0x33
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 intercept_mouse(void);
void restore_mouse(void);
void resetmouse(void);
#define leftbutton() (mousebuttons()&1)
#define rightbutton() (mousebuttons()&2)
#define waitformouse() while(mousebuttons());
#endif
[LISTING FOUR]
/* ------------- mouse.c ------------- */
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include "mouse.h"
static void mouse(int m1,int m2,int m3,int m4)
{
_DX = m4;
_CX = m3;
_BX = m2;
_AX = m1;
geninterrupt(MOUSE);
}
/* ---------- reset the mouse ---------- */
void reset_mouse(void)
{
mouse(0,0,0,0);
}
/* ----- 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)
{
int bx = 0;
if (mouse_installed()) {
mouse(3,0,0,0);
bx = _BX;
}
return bx & 3;
}
/* ---------- return mouse coordinates ---------- */
void get_mouseposition(int *x, int *y)
{
if (mouse_installed()) {
int mx, my;
mouse(3,0,0,0);
mx = _CX;
my = _DX;
*x = mx/8;
*y = my/8;
}
}
/* -------- position the mouse cursor -------- */
void set_mouseposition(int x, int y)
{
if(mouse_installed())
mouse(4,0,x*8,y*8);
}
/* --------- display the mouse cursor -------- */
void show_mousecursor(void)
{
if(mouse_installed())
mouse(1,0,0,0);
}
/* --------- hide the mouse cursor ------- */
void hide_mousecursor(void)
{
if(mouse_installed())
mouse(2,0,0,0);
}
/* --- return true if a mouse button has been released --- */
int button_releases(void)
{
int ct = 0;
if(mouse_installed()) {
mouse(6,0,0,0);
ct = _BX;
}
return ct;
}
static int mx, my;
/* ----- intercept the mouse in case an interrupted program is using it ---- */
void intercept_mouse(void)
{
if (mouse_installed()) {
_AX = 3;
geninterrupt(MOUSE);
mx = _CX;
my = _DX;
_AX = 31;
geninterrupt(MOUSE);
}
}
/* ----- restore the mouse to the interrupted program ----- */
void restore_mouse(void)
{
if (mouse_installed()) {
_AX = 32;
geninterrupt(MOUSE);
_CX = mx;
_DX = my;
_AX = 4;
geninterrupt(MOUSE);
}
}
[LISTING FIVE]
/* ----------- message.h ------------ */
#ifndef MESSAGES_H
#define MESSGAES_H
#define MAXMESSAGES 50
/* --------- event message codes ----------- */
enum messages {
START,
STOP,
KEYBOARD,
RIGHT_BUTTON,
LEFT_BUTTON,
MOUSE_MOVED,
BUTTON_RELEASED,
CURRENT_MOUSE_CURSOR,
MOUSE_CURSOR,
SHOW_MOUSE,
HIDE_MOUSE,
KEYBOARD_CURSOR,
CURRENT_KEYBOARD_CURSOR,
VIDEO_CHAR,
PUT_VIDEORECT,
GET_VIDEORECT
};
/* ------- defines a screen rectangle ------ */
typedef struct {
int x, y, x1, y1;
} RECT;
/* ------ integer type for message parameters ----- */
typedef int PARAM;
void post_message(enum messages, PARAM, PARAM);
int send_message(enum messages, PARAM, PARAM);
int dispatch_message(int (*)(enum messages, PARAM, PARAM));
RECT rect(int, int, int, int);
#endif
[LISTING SIX]
/* --------- message.c ---------- */
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include "mouse.h"
#include "keys.h"
#include "message.h"
static int mouse_event(void);
static int px = -1, py = -1;
static int mx, my;
static int first_dispatch = TRUE;
static struct msgs {
enum messages msg;
PARAM p1;
PARAM p2;
} msg_queue[MAXMESSAGES];
static int qonctr;
static int qoffctr;
static int (*mproc)(enum messages,int,int);
/* ----- post a message and parameters to msg queue ---- */
void post_message(enum messages msg, PARAM p1, PARAM p2)
{
msg_queue[qonctr].msg = msg;
msg_queue[qonctr].p1 = p1;
msg_queue[qonctr].p2 = p2;
if (++qonctr == MAXMESSAGES)
qonctr = 0;
}
/* --------- clear the message queue --------- */
static void clear_queue(void)
{
px = py = -1;
mx = my = 0;
first_dispatch = TRUE;
qonctr = qoffctr = 0;
}
/* ------ collect events into the message queue ------ */
static int collect_messages(void)
{
/* ---- collect any unqueued messages ---- */
enum messages event;
if (first_dispatch) {
first_dispatch = FALSE;
reset_mouse();
show_mousecursor();
send_message(START,0,0);
}
if ((event = mouse_event()) != 0)
post_message(event, mx, my);
if (keyhit())
post_message(KEYBOARD, getkey(), 0);
return qoffctr != qonctr;
}
int send_message(enum messages msg, PARAM p1, PARAM p2)
{
int rtn = 0;
RECT rc;
if (mproc == NULL)
return -1;
if (mproc(msg, p1, p2)) {
switch (msg) {
case STOP:
hide_mousecursor();
clear_queue();
mproc = NULL;
break;
/* -------- keyboard messages ------- */
case KEYBOARD_CURSOR:
unhidecursor();
cursor(p1, p2);
break;
case CURRENT_KEYBOARD_CURSOR:
curr_cursor((int*)p1,(int*)p2);
break;
/* -------- mouse messages -------- */
case SHOW_MOUSE:
show_mousecursor();
break;
case HIDE_MOUSE:
hide_mousecursor();
break;
case MOUSE_CURSOR:
set_mouseposition(p1, p2);
break;
case CURRENT_MOUSE_CURSOR:
get_mouseposition((int*)p1,(int*)p2);
break;
/* ----------- video messages ----------- */
case VIDEO_CHAR:
gettext(p1+1, p2+1, p1+1, p2+1, &rtn);
rtn &= 255;
break;
case PUT_VIDEORECT:
rc = *(RECT *) p1;
puttext(rc.x+1, rc.y+1, rc.x1+1, rc.y1+1,(char *) p2);
break;
case GET_VIDEORECT:
rc = *(RECT *) p1;
gettext(rc.x+1, rc.y+1, rc.x1+1, rc.y1+1,(char *) p2);
break;
default:
break;
}
}
return rtn;
}
/* ---- dispatch messages to the message proc function ---- */
int dispatch_message(
int (*msgproc)(enum messages msg,PARAM p1,PARAM p2))
{
mproc = msgproc;
/* ------ dequeue the next message ----- */
if (collect_messages()) {
struct msgs mq = msg_queue[qoffctr];
send_message(mq.msg, mq.p1, mq.p2);
if (++qoffctr == MAXMESSAGES)
qoffctr = 0;
if (mq.msg == STOP)
return FALSE;
}
return TRUE;
}
/* ---------- gather and interpret mouse events -------- */
static int mouse_event(void)
{
get_mouseposition(&mx, &my);
if (mx != px || my != py) {
px = mx;
py = my;
return MOUSE_MOVED;
}
if (button_releases())
return BUTTON_RELEASED;
if (rightbutton())
return RIGHT_BUTTON;
if (leftbutton())
return LEFT_BUTTON;
return 0;
}
/* ----------- make a RECT from coordinates --------- */
RECT rect(int x, int y, int x1, int y1)
{
RECT rc;
rc.x = x;
rc.y = y;
rc.x1 = x1;
rc.y1 = y1;
return rc;
}
[LISTING SEVEN]
/* ---------------- copyscrn.c -------------- */
#include <stdio.h>
#include <stdlib.h>
#include "keys.h"
#include "message.h"
static int message_proc(enum messages, int, int);
static void near highlight(RECT);
static void writescreen(RECT);
static void near setstart(int *, int, int);
static void near forward(int);
static void near backward(int);
static void near upward(int);
static void near downward(int);
static void init_variables(void);
static int cursorx, cursory; /* Cursor position */
static int mousex, mousey; /* Mouse cursor position */
static RECT blk;
static int kx = 0, ky = 0;
static int px = -1, py = -1;
static int mouse_marking = FALSE;
static int keyboard_marking = FALSE;
static int marked_block = FALSE;
static FILE *fp;
#ifdef TSR
#define main tsr_program
#endif
/* ---------- enter here to run screen grabber --------- */
void main(void)
{
fp = fopen("grab.dat", "wt");
if (fp != NULL) {
/* ----- event message dispatching loop ---- */
while(dispatch_message(message_proc))
;
fclose(fp);
}
}
/* --------- event-driven message processing function ------- */
static int message_proc(
enum message message, /* message */
int param1, /* 1st parameter */
int param2) /* 2nd parameter */
{
int mx = param1;
int my = param2;
int key = param1;
switch (message) {
case START:
init_variables();
post_message(CURRENT_KEYBOARD_CURSOR,(PARAM) &cursorx, (PARAM) &cursory);
post_message(KEYBOARD_CURSOR, 0, 0);
post_message(CURRENT_MOUSE_CURSOR,(PARAM) &mousex, (PARAM) &mousey);
post_message(MOUSE_CURSOR, 0, 0);
break;
case KEYBOARD:
switch (key) {
case FWD:
if (kx < 79) {
if (keyboard_marking)
forward(1);
kx++;
}
break;
case BS:
if (kx) {
if (keyboard_marking)
backward(1);
--kx;
}
break;
case UP:
if (ky) {
if (keyboard_marking)
upward(1);
--ky;
}
break;
case DN:
if (ky < 24) {
if (keyboard_marking)
downward(1);
ky++;
}
break;
case F2:
mouse_marking = FALSE;
setstart(&keyboard_marking, kx, ky);
break;
case '\r':
post_message(STOP, TRUE, 0);
break;
case ESC:
post_message(STOP, FALSE, 0);
break;
}
send_message(KEYBOARD_CURSOR, kx, ky);
break;
case LEFT_BUTTON:
if (!mouse_marking) {
px = mx;
py = my;
keyboard_marking = FALSE;
setstart(&mouse_marking, mx, my);
}
break;
case MOUSE_MOVED:
if (mouse_marking) {
if (px < mx)
forward(mx-px);
if (mx < px)
backward(px-mx);
if (py < my)
downward(my-py);
if (my < py)
upward(py-my);
px = mx;
py = my;
}
break;
case BUTTON_RELEASED:
mouse_marking = FALSE;
break;
case RIGHT_BUTTON:
post_message(STOP, TRUE, 0);
break;
case STOP:
if (marked_block) {
highlight(blk);
if (param1)
writescreen(rect(min(blk.x, blk.x1),min(blk.y, blk.y1),
max(blk.x, blk.x1),max(blk.y, blk.y1)));
}
send_message(MOUSE_CURSOR, mousex, mousey);
send_message(KEYBOARD_CURSOR, cursorx, cursory);
init_variables();
break;
default:
break;
}
return TRUE;
}
/* ------- set the start of block marking ------- */
static void near setstart(int *marking, int x, int y)
{
if (marked_block)
highlight(blk); /* turn off old block */
marked_block = FALSE;
*marking ^= TRUE;
blk.x1 = blk.x = x; /* set the corners of the new block */
blk.y1 = blk.y = y;
if (*marking)
highlight(blk); /* turn on the new block */
}
/* ----- move the block rectangle forward one position ----- */
static void near forward(int n)
{
marked_block = TRUE;
while (n-- > 0) {
if (blk.x < blk.x1)
highlight(rect(blk.x,blk.y,blk.x,blk.y1));
else
highlight(rect(blk.x+1,blk.y,blk.x+1,blk.y1));
blk.x++;
}
}
/* ---- move the block rectangle backward one position ----- */
static void near backward(int n)
{
marked_block = TRUE;
while (n-- > 0) {
if (blk.x > blk.x1)
highlight(rect(blk.x,blk.y,blk.x,blk.y1));
else
highlight(rect(blk.x-1,blk.y,blk.x-1,blk.y1));
--blk.x;
}
}
/* ----- move the block rectangle up one position ----- */
static void near upward(int n)
{
marked_block = TRUE;
while (n-- > 0) {
if (blk.y > blk.y1)
highlight(rect(blk.x,blk.y,blk.x1,blk.y));
else
highlight(rect(blk.x,blk.y-1,blk.x1,blk.y-1));
--blk.y;
}
}
/* ----- move the block rectangle down one position ----- */
static void near downward(int n)
{
marked_block = TRUE;
while (n-- > 0) {
if (blk.y < blk.y1)
highlight(rect(blk.x,blk.y,blk.x1,blk.y));
else
highlight(rect(blk.x,blk.y+1,blk.x1,blk.y+1));
blk.y++;
}
}
/* ------ write the rectangle to the file ------- */
static void writescreen(RECT rc)
{
int vx = rc.x;
int vy = rc.y;
while (vy != rc.y1+1) {
if (vx == rc.x1+1) {
fputc('\n', fp);
vx = rc.x;
vy++;
}
else {
fputc(send_message(VIDEO_CHAR, vx, vy), fp);
vx++;
}
}
}
/* ------- simple swap macro ------ */
#define swap(a,b) {int s=a;a=b;b=s;}
/* -------- invert the video of a defined rectangle ------- */
static void near highlight(RECT rc)
{
int *bf, *bf1, bflen;
if (rc.x > rc.x1)
swap(rc.x,rc.x1);
if (rc.y > rc.y1)
swap(rc.y,rc.y1);
bflen = (rc.y1-rc.y+1) * (rc.x1-rc.x+1) * 2;
if ((bf = malloc(bflen)) != NULL) {
send_message(HIDE_MOUSE, 0, 0);
send_message(GET_VIDEORECT, (PARAM) &rc, (PARAM) bf);
bf1 = bf;
bflen /= 2;
while (bflen--)
*bf1++ ^= 0x7700;
send_message(PUT_VIDEORECT, (PARAM) &rc, (PARAM) bf);
send_message(SHOW_MOUSE, 0, 0);
free(bf);
}
}
/* ---- initialize global variables for later popup ---- */
static void init_variables(void)
{
mouse_marking = keyboard_marking = FALSE;
kx = ky = blk.x = blk.y = blk.x1 = blk.y1 = 0;
px = py = -1;
mouse_marking = FALSE;
keyboard_marking = FALSE;
marked_block = FALSE;
}