Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


MAY93: C PROGRAMMING

C PROGRAMMING

Screen Snapper

This article contains the following executables: DFPP01.ARC

Al Stevens

I'm taking a side trip from the development of D-Flat++ to share with you a utility that I've found useful in the development and documentation of D-Flat applications. Building a computer system always includes the painful task of writing a user's guide. Most programmers don't like to write any kind of documentation, and the user's guide is often the most difficult document to write, because its audience is from a variety of other disciplines, each with its own jargon. A vertical application involves a whole new lexicon and perhaps a user base that doesn't speak computerese. The more horizontal the application, the more diverse the audience, so the language must be specialized enough to describe the application yet general enough that anyone can understand it. Large software houses use professional technical writers to clear these hurdles. Smaller ones have to rely on the programmers to do it. If you're all by yourself, maybe doing the bound documentation for the registered users of a shareware application, you're stuck with the job--you wrote the code, you write it up.

Often the hardest part in creating a user's manual is getting the screen illustrations into print. If you are documenting a Windows application, the job is easier, because Windows and its desktop-publishing applications have everything you need to capture screens and import them into a document. But if you are doing a DOS, text-mode application, there are problems.

Let me describe the environment. You're documenting a DOS application. It has multiple-color screens that use the graphics character set and colors to define windows of one sort or another. In my case, it might be a D-Flat application. You're using a typical black-and-white laser printer to print the master copy of the document, and you will use a typical copier or local-office print service to manufacture the documents. You need to run the application, capture screen shots to a disk file, and import those pictures into your document. The screen shots are sometimes of the full screen, and at other times they are captured from a defined rectangle within the screen. What tools are available?

In several years of writing books, I've used a number of tools designed for capturing DOS screens. Most came from publishers who chose them because they prefer to use a particular capture format to lay out book pages. While the tools work fairly well for book publishing, none have been exactly right for the environment I just described. Some are incredibly difficult to use. Others have limited features. None produce an acceptable, laser-printed copy of a multiple-color DOS screen. Usually I'll run the application in monochrome mode just to get something readable, but an application that uses color to define boundaries is hard to read in its raw black-and-white format. To see for yourself, run the DOS 5.0 DOSSHELL program in one of its monochrome configurations. It works, but the screens are less than pleasing and, when printed, are less readable than you know they could be. The best displays are in color, and they do not print well. Look at the documentation from the vendors. Even Microsoft's manuals look shabby when it comes to the text-mode screen prints.

There are a lot of reasons for that. Screens use white characters on a blue background in one place and black characters on green somewhere else, for example. When you capture these screens, you'll get a .PCX or .TIF file which looks pretty good when displayed with a paint program, but loses a lot of information and visual appeal when printed. Green, red, yellow, and blue don't translate well into black-and-white. Mind you, those formats work better when a publisher prints them with a 1200 dots per inch (dpi) or better Lino-type machine, but laser printers are typically 300 dpi, and if you have to do any reduction whatsoever to fit your page, the added loss in resolution degrades the picture's quality.

HP to the Rescue

One day I sat gazing at my LaserJet III after it had dumped out a bunch of these marginal screen prints. I knew that it had a nice little font that included the graphics character set and the ability to print filled rectangles of dot patterns with white or seven different shades of gray, expressed as 2-, 10-, 15-, 30-, 45-, 70-, and 90-percent fill patterns. These features are common to most laser printers, and the LJIII uses a command language that other printers can emulate. I reasoned that a screen shot that uses shades of gray to represent background colors and that always uses black characters in the foreground might do the job. I hammered out some samples and was pleased with the result.

The pictures that the LJIII makes are just about right. The font I use is about 17 characters per inch and 8.5 lines per inch (lpi), which prints a 25x80 screen dump in a 4.75x3.25-inch picture, a size that fits nicely onto most standard manual-page sizes. Smaller screen segments make smaller pictures. Figure 1 is an example of a D-Flat screen printed this way. Until I see the figure in the magazine, I won't know if it will impress you, because DDJ has to take camera-ready copy and get it into the magazine somehow. I printed it on the LaserJet, and trust me when I say that it looks pretty good in the flesh.

Grabbing the Screen

Printing screen shots is only the beginning. You need a way to capture the screen shots themselves, and I'm ready for that one. In the 1991 March and April issues of DDJ I published a screen-grabber TSR program that captured the text part but not the color attributes of selected text-screen rectangles. The point of the project was to teach C-language, event-driven programming, both in anticipation of D-Flat and for fledgling Windows programmers. I spent very little time on the screen-grabbing code itself other than to explain what it did.

I modified that program to embed LaserJet commands into the screen captures so that the files, when sent to the LaserJet, would print the screens using the 17-pitch font and different shades of gray for different-colored backgrounds. That is the program you will find with this column. I've pulled out the event-driven stuff, not needing to teach that again, and accessed the screen, mouse, and keyboard by using more traditional function calls. The TSR code is virtually unchanged. I won't explain it again, either. If you have a burning need to know about TSRs, read the earlier columns, which explain them some, refer you to other sources for more detail, and tell you why I think you shouldn't care.

Recycled Listings

Listings One and Two, page 140, are console.h and console.c. They implement the low-level code for reading the keyboard and mouse and managing the screen cursor. They, too, are similar to the code used in the earlier project and in D-Flat as well. I am including them here to make the project complete in one installation. Listing Three, page 141, is tsr.c, the TSR engine that turns the program into a reasonably well-behaved TSR, also virtually the same as before. This engine is minimal. It does not include code to unload the TSR from memory. You can use the MARK/ RELEASE utilities or you can reboot to unload the program. The engine does not allow you to change the hot key except by recompiling the program. Change the KEYMASK and SCANCODE global variables to change the hot key. Change the startup message in copyscrn.c as well. By the way, the TSR works okay if you load it high. It occupies about 25K.

The Screen Grabber

Listing Four, page 142, is copysrcn.c, the part of the program that captures screens. When you load it, the main function displays the shades of gray associated with each background color and lets you change them. I found that not all the fill patterns work well, and some screens display better if I change the default. After you finish with that, the program declares itself resident.

The Capture

The program captures the screen image into a file named SNAP.000. Subsequent captures during the same load of the program are named SNAP.001, SNAP.002, and so on. To start the program at a specific numbered extension, enter the number as a command-line parameter when you load it.

The file will have screen text and LaserJet-command statements to print the screen image. You can copy the file directly to the printer to see what it looks like. You can import the file into the document's text file, too. The number of text lines is the same as the number of lines at 8.5 lpi. I find the gobbledygook printer commands to be distracting in the text. Therefore, I insert merge commands into the word processor so that it will retrieve the files at print time. By changing margins and lines per inch in the word processor, I can center the figures and maintain the page integrity. I use XyWrite for DOS word processing, and it supports these operations well.

Using Different Laser Printers

Not everyone will use a LaserJet-compatible laser printer, although most of them emulate the command language. If you want to modify the program to work with another printer, it must at least behave like a LaserJet in that it must have commands to move the print cursor and to print rectangles in shades of gray. It must also have a compatible font.

The LaserJet III macros in copyscrn.c implement the interface. The push and pop macros write the commands telling the printer to remember the current cursor location and restore it later. This allows the program to temporarily change the cursor position without having to know how to restore it. The CH and CW macros define the character height and width in cursor-address increments. The movehorizontal and movevertical macros accept an integer parameter that specifies a relative cursor position. The macros move the print cursor accordingly. The shading macro accepts an integer value that programs the percentage of shading for a rectangular block's fill pattern. The rectangle macro defines the rectangle to be filled, and the rectanglefill macro draws the defined rectangle with the latest programmed fill pattern. The selectfont macro selects the font for the screen dump, and the resetfont macro resets it to the printer's default.

Building and Running Screen Snapper

I used the Turbo C extensions to build screen snapper, so you'll need a Borland compiler to build it. Compile the three modules with the small memory model and link them together using the command line bcc -esnap.exe copyscrn.c console.c tsr.c. This will build snap.exe, the TSR program. When you load snap, the program will display the menu in Figure 2(a). Press the digit associated with a color, and you see Figure 2(b).

Figure 2: The screen-snapper main menu.

  (a)

  Screen Snapper Version 2 - snap.000
    Gray scale defaults:
     0. Black    45%
     1. Blue     45%
     2. Green    30%
     3. Cyan     15%
     4. Red      45%
     5. Magenta  15%
     6. Brown    30%
     7. White     0%

  Enter number of a color to change,
  Esc to return to defaults,
  Or Enter to accept settings
  ...

  (b)

  Black:

  Sp = change, Esc = quit, Enter = accept
  45%

Press the spacebar to step through the fill patterns, Esc to return to the earlier setting, and Enter to accept whatever change you made. The program goes back to the first menu. Continue to change colors until you have the setting you prefer.

When you pop the program up, it saves the current mouse and cursor configurations, sets them both to the upper-left corner of the screen, and turns the mouse cursor on. Define a screen rectangle with either the keyboard or the mouse. With the keyboard, you move the cursor to a corner of the rectangle and press the F2 key to tell the program that you are now defining a rectangle. Move the cursor to the opposite corner and press Enter to save the screen shot, or Esc to reject it and return to the interrupted application. Press F2 again if you want to change the anchored corner. As you move the cursor, the screen inverts the background color so you can see the rectangle being defined. With the mouse, move the mouse cursor to a corner and press and hold down the left button. Drag the mouse cursor to the opposite corner. Release the button. The rectangle is defined. You can ignore it by moving the cursor and pressing the button again. When the rectangle describes the one you want to print, press the right mouse button. The program saves the screen in a disk file, restores the interrupted mouse and cursor configuration, and returns to the interrupted program.

Jazz: Wild Bill and Wild Philippe

Time for some musical plugs. Anyone who has attended a Borland conference knows that Philippe Kahn, the Boss at Borland, likes to play the saxophone and flute. He keeps his Turbo Jazz band of Borland employees busy during the social hours. These days Turbo Jazz is minus one alto sax player who now blows his Selmer at Symantec. Philippe is rumored to believe that the change raised the musical quality of both organizations.

Besides leading Turbo Jazz, Philippe also produces his own compact disks, having three of them out now. The latest is called Paradiso, and Philippe plays the flute exclusively on it. His recording band consists of some of the best players in California, including Alan Broadbent, Terrance Blanchard, and John Patitucci, some world-class heavyweights. Philippe's own playing has improved so much in the few years since I first heard him that I now find myself routinely listening to this CD while I work and liking it very much. I don't know if the CD is available commercially, but you can try Pacific High Productions, P.O. Box 536, Los Gatos, CA 95031.

As a jazz pianist I had the good fortune over the years to work and record with a cornet player named Wild Bill Davison. I first met him when I was still a teenager, and worked with him many times while he lived in Washington, D.C. in the 1970s. He died in 1989 at the age of 83, having inspired and influenced several generations of musicians. He wasn't as well known as Satch, Dizzy, or Bix, but they all knew and respected him, and because of his contributions to our culture, he was inducted into the Jazz Hall of Fame and declared a National Treasure. In the last years of Bill's life, Tommy Saunders, also a fine jazz cornet player, undertook the production of a video tribute to Wild Bill with interviews with Bill and his wife, video and still clips of Bill throughout the years, including a delightful conversation with Johnny Carson on "The Tonight Show," and many samples of Bill's music. If you are interested in jazz and its history and would enjoy hearing the crystal-clear recollections of an irascible yet beguiling and engaging elder statesman, I recommend this work. It runs 100 minutes and is called "Wild Bill Davison, His Life, His Times, His Music," T.T.&T Network, 1158 Bedford, Suite 1506, Grosse Pointe, MI 48230 ($40.00 plus $2.00 P/H).



_C PROGRAMMING COLUMN_
by Al Stevens


[LISTING ONE]
<a name="0139_000e">

/* ----------- console.h ------------ */
#ifndef CONSOLE_H
#define CONSOLE_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
#define KEYBOARD   0x16
#define ZEROFLAG   0x40
#define SETCURSORTYPE 1
#define SETCURSOR     2
#define READCURSOR    3
#define HIDECURSOR 0x20

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 MOUSE 0x33
void resetmouse(void);
unsigned mouse_buffer(void);
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 *);
#define leftbutton() (mousebuttons()&1)
#define rightbutton() (mousebuttons()&2)

/* ------- defines a screen rectangle ------ */
typedef struct {
    int x, y, x1, y1;
} RECT;

#endif





<a name="0139_000f">
<a name="0139_0010">
[LISTING TWO]
<a name="0139_0010">

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

#include <bios.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "console.h"

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();
}
/* --------- generic mouse utility ---------- */
static void mouse(int m1,int m2,int m3,int m4)
{
    _DX = m4;
    _CX = m3;
    _BX = m2;
    _AX = m1;
    geninterrupt(MOUSE);
}
/* ----- 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);
}
/* ---------- reset the mouse ---------- */
void resetmouse(void)
{
    if (mouse_installed())
        mouse(0,0,0,0);
}
/* ------ 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;
}
/* --------- get mouse state buffer size --------- */
unsigned mouse_buffer(void)
{
    if (mouse_installed())    {
        mouse(21,0,0,0);
        return _BX;
    }
    return 0;
}
/* ----- intercept mouse in case an interrupted program is using it ------ */
void intercept_mouse(void *bf)
{
    if (mouse_installed())  {
        _ES = _DS;
        mouse(22, 0, 0, (unsigned) bf);
    }
}
/* ----- restore the mouse to the interrupted program ----- */
void restore_mouse(void *bf)
{
    if (mouse_installed())  {
        _ES = _DS;
        mouse(23, 0, 0, (unsigned) bf);
    }
}





<a name="0139_0011">
<a name="0139_0012">
[LISTING THREE]
<a name="0139_0012">

/* --------- tsr.c --------- */
#include <dos.h>
#include <stdlib.h>
#include <stdio.h>
#include "console.h"

void tsr_program(void);
/* ------- the interrupt function registers -------- */
typedef struct {
    int bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,fl;
} IREGS;
#define DISK    0x13
#define CTRLBRK 0x1b
#define INT28   0x28
#define CRIT    0x24
#define CTRLC   0x23
#define TIMER   8
#define KYBRD   9
#define DOS     0x21
#define KEYMASK  8
#define SCANCODE 52
unsigned highmemory;
/* ------ interrupt vector chains ------ */
static void (interrupt *oldtimer)(void);
static void (interrupt *old28)(void);
static void (interrupt *oldkb)(void);
static void (interrupt *olddisk)(void);
/* ------ ISRs for the TSR ------- */
static void interrupt newtimer(void);
static void interrupt new28(void);
static void interrupt newdisk(IREGS);
static void interrupt newkb(void);
static void interrupt newcrit(IREGS);
static void interrupt newbreak(void);
static unsigned sizeprogram; /* TSR's program size     */
unsigned dossegmnt;          /* DOS segment address    */
unsigned dosbusy;            /* offset to InDOS flag   */
static int diskflag;         /* Disk BIOS busy flag    */
unsigned mcbseg;             /* address of 1st DOS mcb */
static char far *mydta;      /* TSR's DTA              */
int hotkeyhit = 0;
int tsrss;          /* TSR's stack segment */
int tsrsp;          /* TSR's stack pointer */
/* -------------- context for the popup ---------------- */
unsigned intpsp;            /* Interrupted PSP address   */
int running;                /* TSR running indicator     */
char far *intdta;           /* interrupted DTA           */
unsigned intsp;             /*     "       stack pointer */
unsigned intss;             /*     "       stack segment */
unsigned ctrl_break;        /* Ctrl-Break setting        */
void (interrupt *oldcrit)(void);
void (interrupt *oldbreak)(void);
void (interrupt *oldctrlc)(void);
/* ------- local prototypes -------- */
static void resident_psp(void);
static void interrupted_psp(void);
static void popup(void);
void initialize(void);

void main(void)
{
    unsigned es, bx;
    initialize();
    /* ---------- compute memory parameters ------------ */
    highmemory = _SS + ((_SP + 256) / 16);
    /* ------ get address of DOS busy flag ---- */
    _AH = 0x34;
    geninterrupt(DOS);
    dossegmnt = _ES;
    dosbusy = _BX;
    /* ---- get the seg addr of 1st DOS MCB ---- */
    _AH = 0x52;
    geninterrupt(DOS);
    es = _ES;
    bx = _BX;
    mcbseg = peek(es, bx-2);
    /* ----- get address of resident program's dta ----- */
    mydta = getdta();
    /* ------------ prepare for residence ------------ */
    tsrss = _SS;
    tsrsp = _SP;
    oldtimer = getvect(TIMER);
    old28 = getvect(INT28);
    oldkb = getvect(KYBRD);
    olddisk = getvect(DISK);
    /* ----- attach vectors to resident program ----- */
    setvect(KYBRD, newkb);
    setvect(INT28, new28);
    setvect(DISK, newdisk);
    setvect(TIMER, newtimer);
    /* ------ compute program size ------- */
    sizeprogram = highmemory - _psp + 1;
    /* ----- terminate and stay resident ------- */
    _DX = sizeprogram;
    _AX = 0x3100;
    geninterrupt(DOS);
}
/* ---------- break handler ------------ */
static void interrupt newbreak(void)
{
    return;
}
/* -------- critical error ISR ---------- */
static void interrupt newcrit(IREGS ir)
{
    ir.ax = 0;            /* ignore critical errors */
}
/* ------ BIOS disk functions ISR ------- */
static void interrupt newdisk(IREGS ir)
{
    diskflag++;
    (*olddisk)();
    ir.ax = _AX;        /* for the register returns */
    ir.cx = _CX;
    ir.dx = _DX;
    ir.es = _ES;
    ir.di = _DI;
    ir.fl = _FLAGS;
    --diskflag;
}
/* ----- keyboard ISR ------ */
static void interrupt newkb(void)
{
    static unsigned char kbval;

    kbval = inportb(0x60);
    if (!hotkeyhit && !running)
        if ((peekb(0, 0x417) & 0xf) == KEYMASK)
            if (SCANCODE == kbval)    {
                hotkeyhit = 1;
                /* --- reset the keyboard ---- */
                kbval = inportb(0x61);
                outportb(0x61, kbval | 0x80);
                outportb(0x61, kbval);
                outportb(0x20, 0x20);
                return;
            }
    (*oldkb)();
}
/* ----- timer ISR ------- */
static void interrupt newtimer(void)
{
    (*oldtimer)();
    if (hotkeyhit && (peekb(dossegmnt, dosbusy) == 0) &&
            !diskflag)
        popup();
}
/* ----- 0x28 ISR -------- */
static void interrupt new28(void)
{
    (*old28)();
    if (hotkeyhit)
        popup();
}
/* ------ switch psp context from interrupted to TSR ----- */
static void resident_psp(void)
{
    intpsp = getpsp();
    _AH = 0x50;
    _BX = _psp;
    geninterrupt(DOS);
}
/* ---- switch psp context from TSR to interrupted ---- */
static void interrupted_psp(void)
{
     _BX = intpsp;
     _AH = 0x50;
    geninterrupt(DOS);
}
/* ------ execute the resident program ------- */
static void popup(void)
{
    running = 1;
    hotkeyhit = 0;
    intsp = _SP;
    intss = _SS;
    _SP = tsrsp;
    _SS = tsrss;
    oldcrit = getvect(CRIT);    /* redirect critical err  */
    oldbreak = getvect(CTRLBRK);
    oldctrlc = getvect(CTRLC);
    setvect(CRIT, newcrit);
    setvect(CTRLBRK, newbreak);
    setvect(CTRLC, newbreak);
    ctrl_break = getcbrk();     /* get ctrl break setting */
    setcbrk(0);                 /* turn off ctrl break    */
    intdta = getdta();          /* get interrupted dta    */
    setdta(mydta);              /* set resident dta       */
    resident_psp();             /* swap psps              */
    enable();
    tsr_program();              /* call the TSR C program */
    disable();
    interrupted_psp();          /* reset interrupted psp  */
    setdta(intdta);             /* reset interrupted dta  */
    setvect(CRIT, oldcrit);     /* reset critical error   */
    setvect(CTRLBRK, oldbreak);
    setvect(CTRLC, oldctrlc);
    setcbrk(ctrl_break);        /* reset ctrl break       */
    disable();
    _SP = intsp;                /* reset interrupted stack*/
    _SS = intss;
    running = 0;                /* reset semaphore        */
}




<a name="0139_0013">
<a name="0139_0014">
[LISTING FOUR]
<a name="0139_0014">

/* ---------------- copyscrn.c -------------- */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <dos.h>
#include "console.h"

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);
extern unsigned _stklen = 1024;
extern unsigned _heaplen = 8192;
static int cursorx, cursory;
static RECT blk;
static int done = 0;
static int kx = 0, ky = 0;
static int mx, my;
static int px = -1, py = -1;
static int mouse_marking = FALSE;
static int keyboard_marking = FALSE;
static int marked_block = FALSE;
static int vpcts[] = {0,2,10,15,30,45,70,90,100};
static int extension = 0;
static int pcts[]   = {45, 45, 30, 15, 45, 15, 30, 0};
static int dfpcts[] = {45, 45, 30, 15, 45, 15, 30, 0};
char *clrs[] = {"Black", "Blue", "Green", "Cyan",
                "Red", "Magenta", "Brown", "White"};
/* ----- LaserJet III Macros ----- */
#define push(fp) fputs("&f0S",fp)
#define pop(fp)  fputs("&f1S",fp)
#define CW 18
#define CH 40
#define movehorizontal(fp,n) fprintf(fp,"&a%dC",n)
#define movevertical(fp,n)   fprintf(fp,"*p%dY",n)
#define shading(fp, c)       fprintf(fp,"*c%dg",c)
#define rectangle(fp,h,w)    fprintf(fp,"%db%dA",h*CH,w*CW)
#define rectanglefill(fp)    fputs("*c2P", fp)
#define selectfont(fp)       \
            fputs("&l6C&l8D(10U(s0p16.67h8.5v0s0b0T",fp);
#define resetfont(fp)        \
            fputs("&l0O(8U(s1p10v0s0b4101T",fp);
/* ----------- 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;
}
static char *FileName(void)
{
    static char fn[15];
    sprintf(fn, "snap.%03d", extension);
    return fn;
}
void initialize(void)
{
    int i, c, done = 0;

    if (_argc > 1)
        extension = atoi(_argv[1]);
    printf("\nScreen Snapper Version 2 - %s", FileName());
    while (!done)   {
        printf("\n  Gray scale defaults:");
        for (i = 0; i < 8; i++)
            printf("\n     %d. %-7.7s %d%%",
                i, clrs[i], pcts[i]);
        printf("\n\nEnter number of a color to change,\n"
                "Esc to return to defaults,\n"
                "Or Enter to accept settings\n...");
        c = getch();
        putch(c);
        switch (c)  {
            case 27:
                for (i = 0; i < 8; i++)
                    pcts[i] = dfpcts[i];
                break;
            case '\r':
                done = 1;
                break;
            default:
                if (c > '0'-1 && c < '8')   {
                    int ch = 0;
                    int cl = c - '0';
                    int p;
                    for (p = 0; p < 8; p++)
                        if (pcts[cl] == vpcts[p])
                            break;
                    printf("\n  %s:", clrs[cl]);
                    printf(
            "\n  Sp = change, Esc = quit, Enter = accept\n");
                    while (ch != 27 && ch != '\r')  {
                        printf("\r%3d%%", vpcts[p]);
                        ch = getch();
                        if (ch == ' ')  {
                            if (++p == 9)
                                p = 0;
                        }
                        else if (ch == '\r')
                            pcts[cl] = vpcts[p];
                        else
                            putch('/a');
                    }
                }
                else
                    putch('/a');
                break;
        }
    }
    printf("\n\nHot key is Alt+Period\nSnapper is resident");
}
/* ----- process keystrokes ------ */
static void keystroke(void)
{
    int key = getkey();
    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':
            done = 1;
            break;
        case ESC:
            done = -1;
            break;
    }
    cursor(kx, ky);
}
/* ---------- enter here to run screen grabber --------- */
void tsr_program(void)
{
    static char *bf = NULL;
    int bfsize = mouse_buffer();
    /* ------ save the video cursor configuration ------- */
    savecursor();
    set_cursor_type(0x0607);
    unhidecursor();
    if (bfsize)
        if ((bf = malloc(bfsize)) != NULL)
            intercept_mouse(bf);
    resetmouse();
    init_variables();
    curr_cursor(&cursorx, &cursory);
    unhidecursor();
    cursor(0, 0);
    set_mouseposition(0, 0);
    show_mousecursor();
    /* ----- event message dispatching loop ---- */
    done = 0;
    while (done == 0)   {
        if (keyhit())
            keystroke();
        if (leftbutton())   {
            if (!mouse_marking)    {
                px = mx;
                py = my;
                keyboard_marking = FALSE;
                setstart(&mouse_marking, mx, my);
            }
        }
        get_mouseposition(&mx, &my);
        if (mx != px || my != py)  {
            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;
        }
        if (button_releases())
            mouse_marking = FALSE;
        if (rightbutton())
            done = 1;
    }

    /* ----- done ------ */
    if (marked_block)    {
        highlight(blk);
        if (done == 1)
            writescreen(rect(min(blk.x, blk.x1),
                                min(blk.y, blk.y1),
                                max(blk.x, blk.x1),
                                max(blk.y, blk.y1)));
    }
    resetmouse();
    if (bf != NULL) {
        restore_mouse(bf);
        free(bf);
        bf = NULL;
    }
    cursor(cursorx, cursory);
    restorecursor();
    init_variables();
}
/* ------- 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++;
    }
}
static void fill(FILE *fp, int color, int len)
{
    if (color != LIGHTGRAY && pcts[color] != 0) {
        push(fp);
        // ---- go back len character positions
        movehorizontal(fp, -len);
        movevertical(fp, -30);
        // ----- set the shading
        shading(fp, pcts[color]);
        // ----- define the rectangle
        rectangle(fp, 1, len);
        // ----- fill the rectangle
        rectanglefill(fp);
        pop(fp);
    }
}
/* ------ write the rectangle to the file ------- */
static void writescreen(RECT rc)
{
    FILE *fp = fopen(FileName(), "wt");
    hide_mousecursor();
    extension++;
    if (fp != NULL)    {
        int vx, vy, x;
        int fg, bg, color, colorstart = 0, prevcolor = -1;
        int wd = rc.x1-rc.x+1;
        int margin = 6 + (70-wd)/2;
        int i;

        selectfont(fp);
        fputc('\n', fp);
        // ---------- write the text
        for (vy = rc.y; vy < rc.y1+1; vy++) {
            for (i = 0; i < margin; i++)
                fputc(' ', fp);
            for (vx = rc.x; vx < rc.x1+1; vx++) {
                int vid;
                gettext(vx+1, vy+1, vx+1, vy+1, &vid);
                /* ---- get the video attribute ---- */
                color = (vid >> 8) & 255;
                bg = (color >> 4) & 7;
                fg = color & 15;
                if (bg != prevcolor)    {
                    if (prevcolor != -1)
                         fill(fp, prevcolor, x-colorstart);
                    prevcolor = bg;
                    colorstart = x;
                }
                fputc((bg == BLACK && fg == DARKGRAY) ?
                    0xdb : (vid & 255), fp);
                x++;
            }
            fill(fp, bg, x-colorstart);
            fputc('\n', fp);
            x = 0;
            prevcolor = -1;
            colorstart = 0;
        }
        resetfont(fp);
        fclose(fp);
    }
}
#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)    {
        hide_mousecursor();
        gettext(rc.x+1, rc.y+1, rc.x1+1, rc.y1+1, bf);
        bf1 = bf;
        bflen /= 2;
        while (bflen--)
            *bf1++ ^= 0x7700;
        puttext(rc.x+1, rc.y+1, rc.x1+1, rc.y1+1, bf);
        show_mousecursor();
        free(bf);
    }
}
/* ---- initialize global variables for later popup ---- */
static void init_variables(void)
{
    kx = ky = blk.x = blk.y = blk.x1 = blk.y1 = 0;
    px = py = -1;
    mouse_marking = FALSE;
    keyboard_marking = FALSE;
    marked_block = FALSE;
}












Copyright © 1993, 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.