Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Checking if a key is down in MS-DOS (C/C++)

Tags:

c++

c

dos

Yes, I mean real MS-DOS, not Windows' cmd.exe shell console.

Is there a way to check if a key is down in MS-DOS, analogically to the GetAsyncKeyState() function in WinAPI?

Currently I'm using kbhit() and getch(), but it's really slow, has a delay after the first character, doesn't allow multiple keys at the same time etc.

I'm using Turbo C++ 3.1. Can anyone help?

(by the way, don't ask why I'm coding my game on such an ancient system)

like image 580
CrizerPL Avatar asked Dec 04 '16 17:12

CrizerPL


People also ask

How do I check if a key is down or not?

The following example shows how to use the IsKeyDown method to determine the state of a specific key. The Return key is passed to the IsKeyDown method. If the method returns true, the background of a Button is changed. // Uses the Keyboard.IsKeyDown to determine if a key is down.

What is CHKDSK command in MS DOS?

MS-DOS 6.22 (CHKDSK Command). Below is a complete list of MS-DOS commands, commonly referred to as just DOS commands, available as of MS-DOS 6.22: The append command can be used by programs to open files in another directory as if they were located in the current directory.

How to check the key state while processing WM_Keyup/down?

Your code does not make much sense to me but if you want to check the state of other keys, such as Ctrl, Alt, or Shift while processing WM_KEYUP/DOWN, you can use GetKeyState API function. I have been reading information about keyboard messages recently, at one point, I was exposed to the topic you are investigating.

How do I check which keys are pressed in the BIOS?

The BIOS only keeps track of which modifier keys (Shift, Ctrl, or Alt) are held down, it doesn't track any of the other keys. If you want to do that you need to talk to the keyboard controller directly and monitor the make (key pressed) and break (key released) scan codes it receives from the keyboard.


2 Answers

Why are you coding your game on su…just kidding!

In MS-DOS, the "API" functions are implemented as interrupt servicers. In x86 assembly language, you use the INT instruction and specify the number of the interrupt that you want to execute. Most of the interrupts require that their "parameters" be set in certain registers before executing the INT. After the INT instruction returns control to your code, its result(s) will have been placed in certain registers and/or flags, as defined by the interrupt call's documentation.

I have no idea how Turbo C++ implements interrupts, since that pre-dates my involvement with programming, but I do know that it allows you to execute them. Google around for the syntax, or check your Turbo C++ documentation.

Knowing that these are interrupts will get you 90% of the way to a solution when you're searching. Ralf Brown compiled and published a famous list of DOS and BIOS interrupt codes. They should also be available in any book on DOS programming—if you're serious about retro-programming, you should definitely consider getting your hands on one. A used copy on Amazon should only set you back a few bucks. Most people consider these worthless nowadays.

Here is a site that lists the sub-functions available for DOS interrupt 21h. The ones that would be relevant to your use are 01, 06, 07, and 08. These are basically what the C standard library functions like getch are going to be doing under the hood. I find it difficult to imagine, but I have heard reports that programmers back in the day found it faster to call the DOS interrupts directly. The reason I question that is that I can't imagine the runtime library implementers would have been so stupid as to provide unnecessarily slow implementations. But maybe they were.

If the DOS interrupts are still too slow for you, your last recourse would be to use BIOS interrupts directly. This might make an appreciable difference in speed because you're bypassing every abstraction layer possible. But it does make your program significantly less portable, which is the reason operating systems like DOS provided these higher level function calls to begin with. Again, check Ralf Brown's list for the interrupt that is relevant to your use. For example, INT 16 with the 01h sub-function.

like image 102
Cody Gray Avatar answered Sep 24 '22 11:09

Cody Gray


There is no function provided by Turbo C++, MS-DOS or the BIOS that corresponds to Windows function GetAsyncKeyState. The BIOS only keeps track of which modifier keys (Shift, Ctrl, or Alt) are held down, it doesn't track any of the other keys. If you want to do that you need to talk to the keyboard controller directly and monitor the make (key pressed) and break (key released) scan codes it receives from the keyboard.

To do that you'll need to hook the keyboard interrupt (IRQ 1, INT 0x09), read the scancodes from the keyboard controller and then update your own keyboard state table.

Here's a simple program that demonstrates how do this:

#include <conio.h>
#include <dos.h>
#include <stdio.h>

unsigned char normal_keys[0x60];
unsigned char extended_keys[0x60];

static void interrupt 
keyb_int() {
    static unsigned char buffer;
    unsigned char rawcode;
    unsigned char make_break;
    int scancode;

    rawcode = inp(0x60); /* read scancode from keyboard controller */
    make_break = !(rawcode & 0x80); /* bit 7: 0 = make, 1 = break */
    scancode = rawcode & 0x7F;

    if (buffer == 0xE0) { /* second byte of an extended key */
        if (scancode < 0x60) {
            extended_keys[scancode] = make_break;
        }
        buffer = 0;
    } else if (buffer >= 0xE1 && buffer <= 0xE2) {
        buffer = 0; /* ingore these extended keys */
    } else if (rawcode >= 0xE0 && rawcode <= 0xE2) {
        buffer = rawcode; /* first byte of an extended key */
    } else if (scancode < 0x60) {
        normal_keys[scancode] = make_break;
    }

    outp(0x20, 0x20); /* must send EOI to finish interrupt */
}

static void interrupt (*old_keyb_int)();

void
hook_keyb_int(void) {
    old_keyb_int = getvect(0x09);
    setvect(0x09, keyb_int);
}

void
unhook_keyb_int(void) {
    if (old_keyb_int != NULL) {
        setvect(0x09, old_keyb_int);
        old_keyb_int = NULL;
    }
}

int
ctrlbrk_handler(void) {
    unhook_keyb_int();
    _setcursortype(_NORMALCURSOR);
    return 0;
}

static
putkeys(int y, unsigned char const *keys) {
    int i;
    gotoxy(1, y);
    for (i = 0; i < 0x30; i++) {
        putch(keys[i] + '0');
    }
}

void
game(void) {
    _setcursortype(_NOCURSOR);
    clrscr();
    while(!normal_keys[1]) {
        putkeys(1, normal_keys);
        putkeys(2, normal_keys + 0x30);
        putkeys(4, extended_keys);
        putkeys(5, extended_keys + 0x30);
    }
    gotoxy(1, 6);
    _setcursortype(_NORMALCURSOR);
}

int
main() {
    ctrlbrk(ctrlbrk_handler);
    hook_keyb_int();
    game();
    unhook_keyb_int();
    return 0;
}   

The code above has been compiled with Borland C++ 3.1 and tested under DOSBox and MS-DOS 6.11 running under VirtualBox. It shows the current state of keyboard a string of 0's and 1's, a 1 indicating that the key corresponding to that position's scan code is being pressed. Press the ESC key to exit the program.

Note that the program doesn't chain the original keyboard handler, so the normal MS-DOS and BIOS keyboard functions will not work while the keyboard interrupt is hooked. Also note that it restores original keyboard handler before exiting. This is critical because MS-DOS won't do this itself. It also properly handles extended keys that send two byte scan codes, which was the problem with the code in the question you linked to in your answer here.

like image 21
Ross Ridge Avatar answered Sep 23 '22 11:09

Ross Ridge