Note: This is the June 17, 2006 capture of http://bentong.topcities.com/comp/progs/c/trans.htm from the Wayback Machine. This was uploaded to the reloaded ISLESV.NET on June 22, 2023.
Introduction
In DOS you can access the keyboard in at least three different ways: Use INT 16H, use the more robust INT 09H, or read from the keyboard port yourself. What you use depends on the level of control you want and the level of complexity or detail you can take. With INT 16H, you can just issue an interrupt call when you feel you need to — this is the way most of us are comfortable with. Although this is not perfect for real-time control, it works for most (>98%) programming jobs. INT 09H is lower-detailed; it is called every time a key is pressed on the keyboard. If you need to know when and if your user presses a key, you just hook INT 09H. This is the way most software-based debuggers work. The vector for INT 09H could be changed to point to somewhere else and not to the debugger routine. Soft-ICE reads the keyboard port directly (and that is why even if DOS hangs, Soft-ICE still work).
Since you can hook the keyboard interrupt, you can fool the calling program as to what really was pressed. Pop-up TSRs works just like that: they hook the keyboard interrupt, and if their hotkey is pressed, it is taken out of the keyboard buffer and they pop up.
One Key for Another
The character ‘�’ and ‘�’ (IBM extended codes 165 and 164 respectively) are common in countries with a Spanish history, including the Philippines. Because it is such a common thing to type any of these two characters, we design a program to replace one of the keys in our keyboard such that when we type that key, ‘�’ or ‘�’ appears instead. The program hooks interrupt 16H.
What is our program to do? If the function is not extended keyboard read, then then we just call the old interrupt routine.
Upon entry at the interrupt function, the parameters defined for the function are not on the stack but are on the registers. So we can call the old keyboard interrupt right away.
First we should consider that not all functions in the keyboard interrupt is for reading the keyboard. Our interest is only with the EXT_KBREAD function. So what do we do if it is not a keyboard read? We just call the old keyboard interrupt and don’t bother to change anything.
Upon return of the old keyboard interrupt, the registers are set. Then we need to stuff them back to the parameters we have received so that the registers are set right when we return.
Consider: on entry, we will know what function is needed by accessing _AH. But in any case, we are going to call the old keyboard interrupt. Somehow we need to mark something such that upon return from old keyboard interrupt, we know whether we need to change something or not (we change something if the function was a keyboard read). I thought of three techniques:
1. Have a global variable to set.
2. Have a local variable to set.
3. Just have a very big if(){}else{} statement…
I don’t know the relative safety of each approaches, but I am intuitively thinking that the third is the most safe (although would be the biggest and cumbersome).
The Design
The key that we want replaced with is the forward quote key (tell me, when was the last time you used the forward quote key?) Pressing this key should produce a small enye, while pressing Alt plus this key will produce an uppercase enye. On the other hand, pressing Shift plus this key should remain as is: put a tilde (‘~’) in the keyboard buffer. Essentially, what we do is this: if the ` key is pressed, place enye in the keyboard buffer (ie, calls to int 16h will yield scan code 0x29, ascii code 0xa4); if the Alt + ` keys are pressed, place ENYE in the keyboard buffer, scan code is 0x29, ascii code 0xa5.
The Source
/* ; This file Copyright (C) 2004 by Vincent "Bentong" S. Isles ; http://bentong.topcities.com ; bentong_isles@yahoo.com */ #include <dos.h> #define KBD_INT 0x16 /* keyboard interrupt */ #define KBD_EXT_KBREAD 0x10 /* extended keyboard read code */ unsigned int _heaplen = 2; unsigned int _stklen = 1024; #define u_int unsigned int #define INTERRUPT_REGS u_int bp, u_int di, u_int si,\ u_int ds, u_int es,\ u_int dx, u_int cx, u_int bx, u_int ax,\ u_int ip, u_int cs, u_int flags #define INTERRUPT_PARAM bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flags /* old keyboard interrupt routine */ static void interrupt (*old_keyboard)(INTERRUPT_REGS); /* remember: when this interrupt is called with ah=0x10, the caller expects ah to contain the ascii code and al the scan code upon return. If we do not see ah to be 0x10, we just call the interrupt routine and use what it has returned to return to the caller. */ /* Advanced C Programming p. 127: "Changing the value of these special para- meters will cause the corresponding register to be when the function returns." Therefore, if you don't change the parameter, the register's value will not be changed when the function returns, no matter how much you will use the registers (the C compiler puts the code around the function. */ #pragma warn -par /* we know we don't use all params */ void interrupt new_keyboard(INTERRUPT_REGS) { if(_AH == KBD_EXT_KBREAD) { /* our handling routine */ (*old_keyboard)(INTERRUPT_PARAM); ax = _AX; if(ax == 0x2960) /* scan code / ascii */ ax = 0x29a4; else if(ax == 0x2900) /* Alt + ` */ { ax = 0x29a5; /* ENYE */ (*old_keyboard)(INTERRUPT_PARAM); /* to force throw of one char */ } } else { (*old_keyboard)(INTERRUPT_PARAM); /* change the corresponding registers */ ax = _AX; flags = _FLAGS; bx = _BX; cx = _CX; dx = _DX; si = _SI; di = _DI; es = _ES; } } #pragma warn .par /* turn parameter checking off */ int main(void) { static unsigned keep_size; disable(); old_keyboard = getvect(KBD_INT); setvect(KBD_INT, new_keyboard); enable(); keep_size = _SS - _psp + (_SP / 16 ) + 50; keep(0, keep_size); /* never reached */ return 0; }
Notes
Because this program hooks INT 16H and not INT 09H, it cannot “fool” a program that reads the keyboard using INT 09H, for example, the Turbo C compiler used to compile this program. It works well for EDIT.COM, though. Also, it obviously works only in a DOS environment or inside a DOS box under Windows.