Insoddisfatti della vostra tastiera? 

Possiamo almeno tentare di evitare la sostituzione fisica, dal momento che qui si tratta di eliminare un problema spesso fastidioso: nessuna tastiera è configurata per gestire tutti i caratteri di cui si può avere necessità e pertanto, più o meno spesso, si è costretti a ricorrere alle scomode sequenze ALT+nnn sul tastierino numerico. 

Ridefinire la tastiera 

Il programma presentato in queste pagine è in grado di assegnare ai tasti nuovi scan code e ascii code, in sostituzione o in aggiunta a quelli standard. Nella maggior parte dei casi sperimentati, KBDPLUS si è rivelato di grande utilità per aggiungere le parentesi graffe e la tilde, care ai programmatori C, alle tastiere italiane e le lettere accentate alle tastiere americane; in un caso ha egregiamente trasformato in tedesca una normale tastiera italiana. 

La tabella di ridefinizione dei tasti è contenuta in un normale file ASCII, ed è pertanto riconfigurabile a piacere. La sintassi della tabella è semplice: ogni riga del file ASCII rappresenta una ridefinizione di tasto[1] e deve essere strutturata come descritto di seguito. 
spazi shifts spazi scan spazi nuovo_scan spazi ascii spazi commento
spazi uno o più spazi o tabulazioni. Possono anche non essere presenti. 
shifts uno dei seguenti caratteri o una loro combinazione (senza spazi tra un carattere e l'altro):

 A ALT 

C CTRL 

L LEFT SHIFT 

R RIGHT SHIFT 

N Nessuno shift 

Lo shift N è ignorato se non e' il solo presente. 
spazi uno o più spazi o tabulazioni. 
scan scan code del tasto da ridefinire (2 cifre esadecimali). 
spazi uno o più spazi o tabulazioni. 
nuovo_scan nuovo scan code per il tasto (2 cifre esadecimali). Può essere uguale a SCAN. 
spazi uno o più spazi o tabulazioni. 
ascii ASCII code per il tasto (2 cifre esadecimali). 
spazi uno o più spazi o tabulazioni. 
commento è opzionale e non ha formato particolare. 
Si noti che utilizzando un comune editor per scrivere il file, una coppia CR LF è aggiunta al termine di ogni riga quando è premuto il tasto RETURN. La lunghezza della riga, CR_LF compreso, non può superare gli 80 caratteri. Ecco un esempio di riga valida, che ridefinisce la combinazione CTRL ALT ESC come ENTER
CA 01 1C 0D - Il tasto ESC, premuto con CTRL e ALT, equivale a ENTER.
Gli shift sono indicati all'inizio della riga: CA significa CTRL ALT. Uno spazio separa gli shift dallo scan code del tasto: 01 è il tasto ESC. Il nuovo scan code (cioè lo scan code che viene inserito nel buffer di tastiera alla pressione di CTRL ALT ESC è 1C (lo scan code del tasto RETURN). Il nuovo codice ASCII da inserire nel buffer è 0D, corrispondente anch'esso al tasto RETURN. Tutto ciò che segue 0D, sino alla fine della riga, rappresenta un commento ed è ignorato dal programma. 

KBDPLUS è un programma TSR, che viene installato fornendogli come parametro (sulla command line) il nome del file contenente le ridefinizioni dei tasti; la command line è scandita a partire dall'ultimo parametro ed ognuno di essi (se ve n'è più di uno) è considerato un nome di file. Se il tentativo di apertura fallisce il processo è ripetuto con il parametro che lo precede; se nessun parametro è un nome di file esistente o non vi sono parametri, KBDPLUS cerca, nella directory in cui esso stesso si trova, il file KBDPLUS.DEF [2]. Se anche questo tentativo fallisce, il programma non si installa e visualizza un testo di aiuto. 

KBDPLUS accetta inoltre, dalla command line, due parametri particolari: il carattere '?', che provoca la visualizzazione del testo di help senza tentativo di installazione[3], e il carattere '*', che provoca la disinstallazione del programma (se già installato) e il ripristino delle funzionalità originali della tastiera. 

KBDPLUS è in grado di individuare se stesso in RAM ed evita quindi installazioni multiple. 

Segue il listato. 
/**************************************************************************

    KBDPLUS.C - KBDPLUS - Barninga_Z! 1991

        Ridefinitore di tastiera.

    Modalita' di funzionamento.
        Legge un file in formato ASCII e in base al suo contenuto ridefinisce
        la tastiera. Ogni riga del file deve essere lunga al massimo 80 caratteri
        ed e' strutturata come segue:

              shf spc scc spc nsc spc asc spc commento crlf

        dove:

        spc.......uno o piu' blanks o tabs. Possono anche essere presenti
                  prima di shifts; in tal caso saranno ignorati.
        shf.......uno dei seguenti caratteri o una loro combinazione (senza
                  spazi tra un carattere e l'altro):
                      A  ALT
                      C  CTRL
                      L  LEFT SHIFT
                      R  RIGHT SHIFT
                      N  Nessuno shift (ignorato se non e' il solo presente).
        scc.......scan code in esadecimale (2 cifre) del tasto da
                  ridefinire.
        nsc.......nuovo scan code in esadecimale (2 cifre) per il tasto.
                  Puo' essere uguale a scancode.
        asc.......codice ascii in esadecimale (2 cifre) per il tasto.
        commento..e' opzionale e non ha formato particolare.
        crlf......sequenza CR LF che chiude ogni riga di testo.

        Esempio di riga che ridefinisce CTRL ALT ESC:

     CA 01 1C 0D - Il tasto ESC, premuto con CTRL e ALT, equivale a ENTER.

        KBDPLUS accetta, alternativamente al nome di file, due parametri:

        KBDPLUS ?   produce la visualizzazione di uno schermo di aiuto;
        KBDPLUS *   provoca la disinstallazione del programma (purche'
                    esso sia gia' presente in RAM).

    Compilato sotto BORLAND C++ 2.0:

        tcc -O -d -rd -k- -Tm2 kbdplus.C

**************************************************************************/
#pragma  inline
#pragma  warn -pia
#pragma  option -k-      /* attenzione: non serve la std stack frame */

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

#define  PRG            "KBDPLUS"   /* nome del programma */
#define  YEAR           "1991"     /* anno */
#define  PSPENVOFF      0x2C  /* offset, nel PSP, del ptr all'environment */
#define  ALT_V          ((char)8)
#define  CTRL_V         ((char)4)
#define  LSHF_V         ((char)2)
#define  RSHF_V         ((char)1)
#define  NONE_V         ((char)0)
#define  ALT_C          'A'
#define  CTRL_C         'C'
#define  LSHF_C         'L'
#define  RSHF_C         'R'
#define  NONE_C         'N'
#define  SHFMASK        0x0F    /* shift status mask (elimina NumLock, etc.) */
#define  INKEYPORT      0x60  /* porta di input della tastiera */
#define  CTRKEYPORT     0x61 /* porta di controllo della tastiera */
#define  ENABLEKBBIT    0x80        /* bit di abilitazione della tastiera */
#define  E_O_I          0x20      /* segnale di fine interrupt */
#define  I_CTRPORT      0x20  /* porta di controllo degli interrupt */
#define  KBDOFF         0x3FE    /* off di seg implicito nei puntatori - 2 */
#define  MAXLEN         83       /* n. max di carat. in ogni riga del file dati */
#define  BLANK          ' '       /* spazio bianco (ascii 32) */
#define  ARRDIM         360      /* dimensione della func-array Dummy() (90*4)*/
#define  TSR_TEST       0xAD   /* HANDSHAKE per TSR */
#define  TSR_YES        0xEDAF  /* se installato risponde cosi' al test */
#define  TSR_INST       0x00   /* serv. 0 int 2Fh. Testa se installato */
#define  TSR_FREE       0x01   /* serv. 1 int 2Fh. Disinstalla il TSR */
#define  DEFEXT         ".DEF"   /* il file di default e' KBDPLUS.DEF */
#define  HELP_REQUEST   '?'        /* opzione cmd line per richiedere help */
#define  FREE_REQUEST   '*'        /* opzione cmd line per disinstall. TSR */

#define  int09h    ((void(interrupt *)())*(((long *)Dummy)+0))      /* off 0 */
#define  int2Fh    ((void(interrupt *)())*(((long *)Dummy)+1))      /* off 4 */
#define  ShiftFlag (*((char far *)*(((int *)Dummy)+4)))  /* offset 8 */
#define  HeadPtr   (*((int far *)*(((int *)Dummy)+5)))     /* offset 10 */
#define  TailPtr   (*((int far *)*(((int *)Dummy)+6)))     /* offset 12 */
#define  StartPtr  (*((int far *)*(((int *)Dummy)+7)))    /* offset 14 */
#define  EndPtr    (*((int far *)*(((int *)Dummy)+8)))      /* offset 16 */
#define  nk        (*(((int *)Dummy)+9))        /* offset 18 */
#define  ResPSP    (*(((unsigned *)Dummy)+10))      /* offset 20 */
#define  keys      (((struct KbDef far *)Dummy)+6)    /* offset 24 */

#define int09h_asm   Dummy        /* usata nell'inline assembly */
#define int2Fh_asm   Dummy+4      /* usata nell'inline assembly */
#define ResPSP_asm   Dummy+20     /* usata nell'inline assembly */

extern unsigned cdecl _heaplen = 8000;  /* riduce ingombro al caricamento */

struct KbDef { /* struttura per la gestione della tastiera */
    char shf;      /* shifts usati per ridefinire il tasto */
    char scan;     /* scan code originale del tasto */
    char nwscn;    /* nuovo scan code del tasto */
    char nwasc;    /* nuovo codice ASCII del tasto */
};

struct ShfFlag {       /* struttura di definizione dei possibili shifts */
    char kc;       /* carattere usato per rappresentare lo shift */
    char kv;       /* valore dello shift status byte */
} shf[] = {
    {ALT_C,ALT_V},
    {CTRL_C,CTRL_V},
    {LSHF_C,LSHF_V},
    {RSHF_C,RSHF_V},
    {NONE_C,NONE_V}      /* NONE deve essere l'ultimo item (ultimo .kv = 0) */
};

void Dummy(void);       /* necessaria per JMP in newint09h() e newint16h() */

void far newint09h(void)        /* handler int 09h */
{
    int i;

    asm {
        push ax;
        push bx;
        push cx;
        push dx;
        push es;
    }
    for(i = 0; i < nk; i++) {     /* scandisce tabella ridefinizioni */
        if((ShiftFlag & SHFMASK) == keys[i].shf) {
            asm in al,INKEYPORT;
            asm mov cl,al; /* salva al; ax e' usato per i puntatori */
            if(_CL == keys[i].scan) {
                TailPtr += 2;
                if(TailPtr == HeadPtr ||
                              (TailPtr == EndPtr && HeadPtr == StartPtr)) {
                    TailPtr -= 2;
                    break; /* se il buffer e' pieno lascia al BIOS */
                }
                _CH = keys[i].nwscn;
                _CL = keys[i].nwasc;
                *((int far *)((TailPtr)+KBDOFF)) = _CX;        /* tasto --> buf */
                if(TailPtr == EndPtr)  /* se è in fondo al buffer */
                    TailPtr = StartPtr;    /* aggiorna i puntatori */
                asm {
                    in al,CTRKEYPORT;      /* legge lo stato della tastiera */
                    mov ah,al;     /* lo salva */
                    or al,ENABLEKBBIT;     /* setta il bit "enable kbd" */
                    out CTRKEYPORT,al;     /* lo scrive sulla porta di ctrl */
                    xchg ah,al;    /* riprende lo stato originale della tast. */
                    out CTRKEYPORT,al;     /* e lo riscrive */
                    mov al,E_O_I;  /* manda il segnale di fine Interrupt */
                    out I_CTRPORT,al;      /* al controllore dell'8259 */
                    jmp _EXIT;
                }
            }
        }
    }
    asm {
        pop es;
        pop dx;
        pop cx;
        pop bx;
        pop ax;
        mov sp,bp;
        pop bp;
        jmp dword ptr int09h_asm;      /* concatena handler originale */
    }
_EXIT:
    asm {
        pop es;
        pop dx;
        pop cx;
        pop bx;
        pop ax;
        mov sp,bp;
        pop bp;
        iret;  /* ritorna al processo interrotto */
    }
}

void far newint2Fh(void)        /* gestore dell'int 2Fh */
{
    asm {
        cmp ah,TSR_TEST;       /* la chiamata viene da KBDPLUS ? */
        jne _CHAIN;    /* no: salta */
        cmp al,TSR_INST;       /* si: e' richiesto il test di gia' installato ? */
        jne _FREETSR;  /* no: salta al servizio successivo */
        mov ax,TSR_YES;        /* si: carica AX con la "passwd" per restituirla */
        iret;
    }
_FREETSR:
    asm {
        cmp al,TSR_FREE;       /* e' richiesta la disinstallazione ? */
        jne _CHAIN;    /* no: salta */
        mov ax,offset Dummy;   /* carica AX con l'offset di Dummy */
        mov dx,seg Dummy;      /* carica DX con il segmento di Dummy */
        iret;
    }
_CHAIN:
    asm jmp dword ptr int2Fh_asm;  /* concatena gestore originale */
}

void Dummy(void)        /* spazio dati globali e strutture KbDef */
{
    asm {
        dd 0;  /* punta all'int 09h */
        dd 0;  /* punta all'int 2Fh originale */
        dw 0417h;      /* punta allo shift status byte */
        dw 041Ah;      /* punta alla testa del kbd buf */
        dw 041Ch;      /* punta alla coda del kbd buf */
        dw 0480h;      /* punta all'inizio del kbd buf */
        dw 0482h;      /* punta alla fine del kbd buf */
        dw 0;  /* n. di ridefinizioni (nk) */
        dw 0;  /* PSP del TSR */
        dw 0;  /*  tappo per sincronizzare gli offset */
        db ARRDIM dup (0);     /* spazio per strutture di template KbDef */
    }
}

char *hlpmsg = "\n\
Type  "PRG" ?  for help; "PRG" *  to uninstall.\n\
DATA FILE DESCRIPTION (if no name given, "PRG" tries for "PRG""DEFEXT"):\n\
Each line in the file redefines a key and has the following format:\n\
\n\
      shifts spaces scan spaces newscan spaces ascii spaces comment crlf\n\
\n\
spaces...one or more blanks (or tabs).\n\
shifts...the combination of shift keys: one or more of the following:\n\
            A  (the ALT key)\n\
            C  (the CTRL key)\n\
            L  (the LEFT SHIFT key)\n\
            R  (the RIGHT SHIFT key)\n\
            N  (no shift has to be pressed with the redefined key)\n\
scan.....the hex scan code of the redefined key.\n\
newscan..the new hex scan code for the redefined key. Can be = <scan>.\n\
ascii....the hex ASCII code of the char for the redefined key.\n\
comment..optional entry; useful to scribble some remarks.\n\
crlf.....a CR LF seq. (End-of-line; max 80 chrs).\n\
\n\
CA 01 1C 0D   Example that makes CTRL ALT ESC same as the ENTER key.\
";      /* stringa di help */

void release_env(void)  /* libera environment del TSR */
{
    extern unsigned _envseg;

    asm {
        mov es,_envseg;        /* ...carica in ES l'ind. di segmento dell'env. */
        mov ah,0x49;   /* chiede al DOS di liberare il MCB dell'environment */
        int 0x21;
    }
}

int nibble(char c)      /* 2 hex digit string ===> int */
{
    if(c > (char)0x29 && c < (char)0x40)
        return(c-'0');
    return(((c <= 'Z') ? c : c-BLANK)-('A'-10));
}

char *setcodes(char *bufptr,char far *code)     /* legge i codici dal file */
{
    for(; *bufptr <= BLANK;)
        ++bufptr;
    *code = (char)((nibble(*bufptr) << 4) + nibble(*(++bufptr)));
    return(++bufptr);
}

void readdata(FILE *in) /* legge i codici e prepara tabella in memoria */
{
    char shift[MAXLEN], *bufptr;
    register int i, lim;

    lim = ARRDIM / sizeof(struct KbDef);
    for(; fgets(shift,MAXLEN,in) && nk < lim; nk++) {
        for(bufptr = shift; *bufptr <= BLANK;)
            ++bufptr;
        for(; *bufptr > BLANK;)
            ++bufptr;
        *bufptr = (char)NULL;
        for(i = 0; shf[i].kv; i++)
            if(strchr(shift,shf[i].kc))
                keys[nk].shf |= shf[i].kv;
        bufptr = setcodes(bufptr,&(keys[nk].scan));
        bufptr = setcodes(bufptr,&(keys[nk].nwscn));
        setcodes(bufptr,&(keys[nk].nwasc));
        }
}

int readfile(int argc,char **argv)      /* apre e chiude il file */
{
    FILE *in;

    strcpy(strrchr(argv[0],'.'),DEFEXT);
    for(; argc;)   /* ipotesi: ogni parm in cmdline = nome di file */
        if(in = fopen(argv[--argc],"rb")) {
            printf(PRG": found %s. Reading...\n",argv[argc]);
            readdata(in);
            fclose(in);
            break;
        }
        else
            printf(PRG": %s not found.\n",argv[argc]);
    return(nk);
}

int resparas(void)      /* calcola paragrafi residenti indispensabili */
{
    return(FP_SEG(keys)+((FP_OFF(keys)+nk*sizeof(struct KbDef))
                                                             >> 4)+1-_psp);
}

int tsrtest(void)       /* controlla se gia' residente */
{
    asm {
        mov ah,TSR_TEST;
        mov al,TSR_INST;
        int 0x2F;
        cmp ax,TSR_YES;
        je _RESIDENT;
        xor ax,ax;
    }
_RESIDENT:
    return(_AX);
}

void uninstall(void)    /* disinstalla KBDPLUS residente */
{
    void far * ResDataPtr;

    asm {
        mov ah,TSR_TEST;
        mov al,TSR_FREE;
        int 0x2F;
        mov word ptr ResDataPtr,ax;    /* DX:AX e' l'ind. della Dummy resid. */
        mov word ptr ResDataPtr+2,dx;  /* e va gestito in back-words */
    }
    setvect(0x09,(void(interrupt *)())(*((long far *)ResDataPtr)));
    setvect(0x2F,(void(interrupt *)())(*(((long far *)ResDataPtr)+1)));
    _ES = *(((unsigned far *)ResDataPtr)+10);
    asm {
        mov ah,0x49;   /* chiede al DOS di liberare il MCB del PSP del TSR */
        int 0x21;
    }
}

void install(void)      /* installa TSR KBDPLUS */
{
    asm cli;
    int09h = getvect(0x09);
    int2Fh = getvect(0x2F);
    setvect(0x09,(void(interrupt *)())newint09h);
    setvect(0x2F,(void(interrupt *)())newint2Fh);
    asm sti;
    ResPSP = _psp;
    release_env();
    printf(PRG": Installed... %d key redefinition(s).\n",nk);
    keep(0,resparas());    /* TSR ! */
}

void main(int argc,char **argv)
{
    printf(PRG": Keyboard ReDef Utility - Barninga_Z! "YEAR"\n");
    if(argv[1][0] == HELP_REQUEST)
        printf(PRG": help screen.\n%s",hlpmsg);
    else
        if(tsrtest())
            if(argv[1][0] == FREE_REQUEST) {
                uninstall();
                printf(PRG": Uninstalled: original keys restored.\n");
            }
            else
                printf(PRG": Already installed.\n%s",hlpmsg);
        else
            if(readfile(argc,argv))
                install();
            else
                printf(PRG": Invalid data file name or lines.\n%s",hlpmsg);
}
Il programma installa i gestori dell' int09h e dell' int2Fh. L'int 2Fh ha il compito di determinare se KBDPLUS è già presente in memoria e collabora alla disintallazione: esso controlla se in ingresso AH contiene il valore della costante manifesta TSR_TEST, nel qual caso valuta AL. Se questo contiene TSR_INST (il valore è 0 come suggerito da Microsoft) la routine restituisce in AX il valore TSR_YES, riconosciuto da tsrtest()

Se invece AL contiene TSR_FREE, il gestore dell' int2Fh restituisce a uninstall() l'indirizzo far della Dummy() residente: uninstall() può così ripristinare i vettori e liberare la RAM allocata al PSP del codice residente. 

Se AL contiene un altro valore, il gestore concatena l' int2Fh originale. 

Se KBDPLUS non è residente, main() invoca readfile(), che costruisce il nome di default per il file dati sostituendo all'estensione del nome del programma (ricavato da argv[0]) la stringa DEFEXT (".DEF") e tenta, comunque, di aprire tutti i file i cui nomi sono specificati sulla command line, a partire dall'ultimo (ogni parametro è interpretato come nome di file). Se nessuno di essi esiste o non sono stati passati parametri viene aperto il file di default. Se neppure questo esiste KBDPLUS visualizza il testo di help e termina. 

Se invece un file è stato trovato ed aperto, vengono lette in memoria, una ad una, le righe che lo compongono (o, al massimo, tante righe quante sono le ridefinizioni consentite[4]). La decodifica di ogni riga avviene non appena questa è letta, per evitare di allocare un buffer in grado di contenere tutto il file. Sono scartati gli spazi eventualmente presenti ad inizio riga; tutti i caratteri rappresentanti gli shift sono considerati un'unica sequenza interrotta dal primo blank; gli scan code e il codice ascii sono interpretati come numeri di due cifre esadecimali e convertiti in int da nibble(), in luogo della quale sarebbe possibile utilizzare sscanf(), analoga alla fscanf()

I dati decodificati sono scritti nella tabella alla quale Dummy() riserva spazio. La Dummy(), funzione fittizia, contiene anche tutti i dati globali; il suo nome (che è in pratica il puntatore alla funzione) viene all'occorrenza forzato a puntatore ai vari tipi di dato in essa contenuti: alcune macro consentono di "nascondere" dietro a pseudo­nomi di variabili le indirezioni e i cast necessari, a volte complessi. 

Lo stratagemma attuato con la Dummy() consente di lasciare residente in RAM solo ciò che è indispensabile: newint09h(), newint16h() e i dati globali (di questi, l'array che contiene le ridefinizioni è l'ultimo, per poterne troncare la parte non utilizzata). In tal modo la funzione resparas() può calcolare quanti paragrafi devono essere residenti basandosi sulla differenza tra l'indirizzo della tabella e quello del PSP, alla quale va sommato lo spazio occupato dalle ridefinizioni dei tasti (il tutto arrotondato per eccesso). 

I gestori newint09h() e newint2Fh() sono dichiarati far e non interrupt: ciò consente di evitare inutili salvataggi automatici di registri non utilizzati e semplifica, nel caso di newint2Fh(), la restituzione di un valore alla routine chiamante. Nella newint09h() la variabile i non può essere dichiarata register: ne risulterebbe complicata la gestione dello stack (se DI e SI sono referenziati nella funzione il compilatore li spinge sullo stack in entrata); di qui l'utilizzo dell'opzione ­rd in compilazione. 

Quando KBDPLUS è residente, newint09h() è attivata ad ogni pressione o rilascio di tasto: viene scandita la tabella delle ridefinizioni alla ricerca di una combinazione di shift e scan code corrispondenti a quella rilevata sulla tastiera. Se la ricerca ha successo, il nuovo codice ASCII e il nuovo scan code sono inseriti nel buffer della tastiera (con conseguente aggiornamento del puntatore alla coda del buffer[5]) e vengono resettati la tastiera e il controllore degli interrupt. In caso contrario, o se il buffer della tastiera è pieno, viene concatenato l'int 09h originale. 

Segue il listato di una versione leggermente più sofisticata del programma KBDPLUS; si tratta di una successiva release, che rimuove uno dei principali limiti della versione appena presentata: l'incapacità di distinguere tra loro i due ALT (ALT sinistro e ALT­GR) ed i due CTRL (sinistro e destro) presenti sulla tastiera. Le modifiche al listato sono minime: compaiono le definizioni delle costanti manifeste ALTGR_V e CTRLR_V (maschere dei bit di shift), nonché ALTGR_C e CTRLR_C (caratteri da utilizzare per ridefinire un tasto mediante ALT­GR e CTRL destro). Ne risulta, ovviamente, ampliato l'array shf di strutture ShfFlag. Il riconoscimento del tasto ALT­GR è effettuato nella newint09h() mediante un controllo basato sul Keyboard Status Byte, che si trova all'indirizzo 0:0496 (nuova costante manifesta KBSTBYTE). 
/******************************************************************************

    KBDPLUS2.C - KBDPLUS 2.5 - Barninga_Z! 14-06-93

        Ridefinitore di tastiera.

    Modalita' di funzionamento.
        Legge un file in formato ASCII e in base al suo contenuto ridefinisce
        la tastiera. Ogni riga del file deve essere lunga max. 80 caratteri
        ed e' strutturata come segue:

              shf spc scc spc nsc spc asc spc commento crlf

        dove:

        spc.......uno o piu' blanks o tabs. Possono anche essere presenti
                  prima di shifts; in tal caso saranno ignorati.
        shf.......uno dei seguenti caratteri o una loro combinazione (senza
                  spazi tra un carattere e l'altro):
                      A  ALT
                      G  ALT GR
                      C  CTRL
                      T  CTRL RIGHT
                      L  LEFT SHIFT
                      R  RIGHT SHIFT
                      N  Nessuno shift (ignorato se non e' il solo presente).
        scc.......scan code in esadecimale (2 cifre) del tasto da ridefinire.
        nsc.......nuovo scan code in esadecimale (2 cifre) per il tasto. Puo'
                  essere uguale a scancode.
        asc.......codice ascii in esadecimale (2 cifre) per il tasto.
        commento..e' opzionale e non ha formato particolare.
        crlf......sequenza CR LF che chiude ogni riga di testo.

        Esempio di riga che ridefinisce CTRL ALT ESC:

     CA 01 1C 0D - Il tasto ESC, premuto con CTRL e ALT, equivale a ENTER.

        Se gia' presente in RAM, KBDPLUS si riconosce e non si installa una
        seconda volta.
        KBDPLUS2 accetta, alternativamente al nome di file, due parametri:
        ?: KBDPLUS2 ?   produce la visualizzazione di uno schermo di aiuto;
        *: KBDPLUS2 *   provoca la disinstallazione del programma (purche' esso
                        sia gia' presente in RAM).

    Compilato sotto TURBO C++ 3.1:

        tcc -O -d -rd -k- -Tm2 kbdplus2.c

******************************************************************************/
#pragma  inline
#pragma  warn -pia
#pragma  option -k-      // no std stack frame!! serve per assembly!

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

#define  PRG            "KBDPLUS2"                     /* nome del programma */
#define  VER            "2.5"                                     /* release */
#define  YEAR           "1993"                                       /* anno */
#define  PSPENVOFF      0x2C     /* offset, nel PSP, del ptr all'environment */
#define  ALT_V          ((int)520)     // 512 + 8
#define  ALTGR_V        ((int)8)       // gestito con kbd st. byte (0x496)
#define  CTRL_V         ((int)260)     // 256 + 4
#define  CTRLR_V        ((int)4)
#define  LSHF_V         ((int)2)
#define  RSHF_V         ((int)1)
#define  NONE_V         ((int)0)
#define  ALT_C          'A'
#define  ALTGR_C        'G'
#define  CTRL_C         'C'
#define  CTRLR_C        'T'
#define  LSHF_C         'L'
#define  RSHF_C         'R'
#define  NONE_C         'N'
#define  SHFMASK        0x30F      /* shift status mask per entrambi i bytes */
#define  INKEYPORT      0x60                /* porta di input della tastiera */
#define  CTRKEYPORT     0x61            /* porta di controllo della tastiera */
#define  ENABLEKBBIT    0x80           /* bit di abilitazione della tastiera */
#define  E_O_I          0x20                    /* segnale di fine interrupt */
#define  I_CTRPORT      0x20           /* porta di controllo degli interrupt */
#define  KBSTBYTE       0x496           /* puntatore al keyboard status byte */
#define  KBDOFF         0x3FE   /* offset di seg implicito nei puntatori - 2 */
#define  MAXLEN         83    /* n. max di carat. in ogni riga del file dati */
#define  BLANK          ' '                      /* spazio bianco (ascii 32) */
#define  ARRDIM         1280  /* dimensione della func-array Dummy() (256*5) */
#define  TSR_TEST       0xAD                            /* HANDSHAKE per TSR */
#define  TSR_YES        0xEDAF  /* se instal., int 16 risponde cosi' al test */
#define  TSR_INST       0x00         /* serv. 0 int 2Fh. Testa se installato */
#define  TSR_FREE       0x01          /* serv. 1 int 2Fh. Disinstalla il TSR */
#define  DEFEXT         ".DEF"          /* il file di default e' KBDPLUS.DEF */
#define  HELP_REQUEST   '?'          /* opzione cmd line per richiedere help */
#define  FREE_REQUEST   '*'          /* opzione cmd line per disinstall. TSR */

#define  int09h     ((void (interrupt *)())*(((long *)Dummy)+0)) /* offset 0 */
#define  int2Fh     ((void (interrupt *)())*(((long *)Dummy)+1)) /* offset 4 */
#define  ShiftFlag  (*((int far *)*(((int *)Dummy)+4)))          /* offset 8 */
#define  HeadPtr    (*((int far *)*(((int *)Dummy)+5)))         /* offset 10 */
#define  TailPtr    (*((int far *)*(((int *)Dummy)+6)))         /* offset 12 */
#define  StartPtr   (*((int far *)*(((int *)Dummy)+7)))         /* offset 14 */
#define  EndPtr     (*((int far *)*(((int *)Dummy)+8)))         /* offset 16 */
#define  nk         (*(((int *)Dummy)+9))                       /* offset 18 */
#define  ResPSP     (*(((unsigned *)Dummy)+10))                 /* offset 20 */
#define  keys       ((struct KbDef far *)DummyFkeys)

#define int09h_asm   Dummy                     /* usata nell'inline assembly */
#define int2Fh_asm   Dummy+4                   /* usata nell'inline assembly */
#define ResPSP_asm   Dummy+20                  /* usata nell'inline assembly */

extern unsigned cdecl _heaplen = 8000;

struct KbDef {
    int  shf;
    char scan;
    char nwscn;
    char nwasc;
};

struct ShfFlag {
    char kc;
    int  kv;
} shf[] = {
    {ALT_C,ALT_V},
    {ALTGR_C,ALTGR_V},          // aggiunta 14-06-93
    {CTRL_C,CTRL_V},
    {CTRLR_C,CTRLR_V},          // aggiunta 14-06-93
    {LSHF_C,LSHF_V},
    {RSHF_C,RSHF_V},
    {NONE_C,NONE_V}       /* NONE deve essere l'ultimo item (ultimo .kv = 0) */
};

void Dummy(void);                /* necessaria per newint09h() e newint16h() */
void DummyFkeys(void);           /* necessaria per newint09h() e newint16h() */

void far newint09h(void)
{
    asm {
        push ax;
        push bx;
        push cx;
        push dx;
        push es;
    }
    for(_SI = 0; _SI < nk; _SI++) {
        if(((*(char far *)KBSTBYTE & keys[_SI].shf) == keys[_SI].shf) ||
                                    ((ShiftFlag & SHFMASK) == keys[_SI].shf)) {
            asm in al,INKEYPORT;
            asm mov cl,al;          /* salva al; ax e' usato per i puntatori */
            if(_CL == keys[_SI].scan) {
                TailPtr += 2;
                if(TailPtr == HeadPtr ||
                                  (TailPtr == EndPtr && HeadPtr == StartPtr)) {
                    TailPtr -= 2;
                    break;   /* se il buffer e' pieno scarica barile al BIOS */
                }
                _CH = keys[_SI].nwscn;
                _CL = keys[_SI].nwasc;
                *((int far *)((TailPtr)+KBDOFF)) = _CX;
                if(TailPtr == EndPtr)
                    TailPtr = StartPtr;
                asm {
                    in al,CTRKEYPORT;       /* legge lo stato della tastiera */
                    mov ah,al;                                   /* lo salva */
                    or al,ENABLEKBBIT;          /* setta il bit "enable kbd" */
                    out CTRKEYPORT,al; /* lo scrive sulla porta di controllo */
                    xchg ah,al;   /* riprende lo stato originale della tast. */
                    out CTRKEYPORT,al;                      /* e lo riscrive */
                    mov al,E_O_I;      /* manda il segnale di fine Interrupt */
                    out I_CTRPORT,al;            /* al controllore dell'8259 */
                    jmp _EXIT;
                }
            }
        }
    }
    asm {
        pop es;
        pop dx;
        pop cx;
        pop bx;
        pop ax;
        pop si;
        jmp dword ptr int09h_asm;
    }
_EXIT:
    asm {
        pop es;
        pop dx;
        pop cx;
        pop bx;
        pop ax;
        pop si;
        iret;
    }
}

void far newint2Fh(void)
{
    asm {
        cmp ah,TSR_TEST;                   /* la chiamata viene da KBDPLUS ? */
        jne _CHAIN;                                             /* no: salta */
        cmp al,TSR_INST;    /* si: e' richiesto il test di gia' installato ? */
        jne _FREETSR;                    /* no: salta al servizio successivo */
        mov ax,TSR_YES;   /* si: carica AX con la "password" per restituirla */
        iret;
    }
_FREETSR:
    asm {
        cmp al,TSR_FREE;               /* e' richiesta la disinstallazione ? */
        jne _CHAIN;                                             /* no: salta */
        mov ax,offset Dummy;              /* carica AX con l'offset di Dummy */
        mov dx,seg Dummy;              /* carica DX con il segmento di Dummy */
        iret;
    }
_CHAIN:
    asm jmp dword ptr int2Fh_asm;             /* concatena gestore originale */
}

void Dummy(void)                    /* spazio dati globali e strutture KbDef */
{
    asm {
        dd 0;                                                  /* ptr int 09 */
        dd 0;                                 /* punta all'int 2Fh originale */
        dw 0417h;                            /* punta allo shift status byte */
        dw 041Ah;                                        /* punta alla testa */
        dw 041Ch;                                         /* punta alla coda */
        dw 0480h;                                        /* punta all'inizio */
        dw 0482h;                                         /* punta alla fine */
        dw 0;                                    /* n. di ridefinizioni (nk) */
        dw 0;                                                 /* PSP del TSR */
    }
}

void DummyFkeys(void)
{
    asm {
        db ARRDIM dup (0);         /* spazio per strutture di template KbDef */
    }
}

char *hlpmsg = "\n\
Type  KBDPLUS ?  for help; KBDPLUS *  to uninstall.\n\
DATA FILE DESCRIPTION (if no name given, "PRG" searches for "PRG""DEFEXT"):\n\
Each line in the file redefines a key and has the following format:\n\
\n\
        shifts spaces scan spaces newscan spaces ascii spaces comment\n\
\n\
spaces...one or more blanks (or tabs).\n\
shifts...the combination of shift keys: one or more of the following:\n\
            A  (the ALT key)\n\
            G  (the ALT GR key)\n\
            C  (the LEFT CTRL key)\n\
            T  (the RIGHT CTRL key)\n\
            L  (the LEFT SHIFT key)\n\
            R  (the RIGHT SHIFT key)\n\
            N  (no shift has to be pressed with the redefined key)\n\
scan.....the hex scan code of the redefined key.\n\
newscan..the new hex scan code for the redefined key. It can be = <scan>.\n\
ascii....the hex ASCII code of the char for the redefined key.\n\
comment..optional entry; useful to scribble some remarks.\n\
\n\
CA 01 1C 0D   Example that makes CTRL ALT ESC equivalent to the ENTER key.";

void release_env(void)
{
    extern unsigned _envseg;

    asm {
        mov es,_envseg; /* ...carica in ES l'ind. di segmento dell'environm. */
        mov ah,0x49;  /* richiede al DOS di liberare il MCB dell'environment */
        int 0x21;
    }
}

int nibble(char c)                              /* hex digit string ===> int */
{
    if(c > (char)0x29 && c < (char)0x40)
        return(c-'0');
    return(((c <= 'Z') ? c : c-BLANK)-('A'-10));
}

char *setcodes(char *bufptr,char far *code)
{
    for(; *bufptr <= BLANK;)
        ++bufptr;
    *code = (char)((nibble(*bufptr) << 4) + nibble(*(++bufptr)));
    return(++bufptr);
}

void readdata(FILE *in)
{
    char shift[MAXLEN], *bufptr;
    register int i, lim;

    lim = ARRDIM / sizeof(struct KbDef);
    for(; fgets(shift,MAXLEN,in) && nk < lim; nk++) {
        for(bufptr = shift; *bufptr <= BLANK;)
            ++bufptr;
        for(; *bufptr > BLANK;)
            ++bufptr;
        *bufptr = (char)NULL;
        for(i = 0; shf[i].kv; i++)
            if(strchr(shift,shf[i].kc))
                keys[nk].shf |= shf[i].kv;
        bufptr = setcodes(bufptr,&(keys[nk].scan));
        bufptr = setcodes(bufptr,&(keys[nk].nwscn));
        (void)setcodes(bufptr,&(keys[nk].nwasc));
        }
}

int readfile(int argc,char **argv)
{
    FILE *in;

    strcpy(strrchr(argv[0],'.'),DEFEXT);
    for(; argc;)
        if(in = fopen(argv[--argc],"rb")) {
            (void)printf(PRG": found %s. Reading...\n",argv[argc]);
            readdata(in);
            (void)fclose(in);
            break;
        }
        else
            (void)printf(PRG": %s not found.\n",argv[argc]);
    return(nk);
}

int resparas(void)
{
    return(FP_SEG(keys)+((FP_OFF(keys)+nk*sizeof(struct KbDef)) >> 4)+1-_psp);
}

int tsrtest(void)
{
    asm {
        mov ah,TSR_TEST;
        mov al,TSR_INST;
        int 0x2F;
        cmp ax,TSR_YES;
        je _RESIDENT;
        xor ax,ax;
    }
_RESIDENT:
    return(_AX);
}

void uninstall(void)
{
    void far * ResDataPtr;

    asm {
        mov ah,TSR_TEST;
        mov al,TSR_FREE;
        int 0x2F;
        mov word ptr ResDataPtr,ax; /* DX:AX e' l'ind. della Dummy residente */
        mov word ptr ResDataPtr+2,dx;         /* e va trattato in back-words */
    }
    setvect(0x09,(void(interrupt *)())(*((long far *)ResDataPtr)));
    setvect(0x2F,(void(interrupt *)())(*(((long far *)ResDataPtr)+1)));
    _ES = *(((unsigned far *)ResDataPtr)+10);
    asm {
        mov ah,0x49;   /* richiede al DOS di liberare il MCB del PSP del TSR */
        int 0x21;
    }
}

void install(void)
{
    asm cli;
    int09h = getvect(0x09);
    int2Fh = getvect(0x2F);
    setvect(0x09,(void(interrupt *)())newint09h);
    setvect(0x2F,(void(interrupt *)())newint2Fh);
    asm sti;
    ResPSP = _psp;
    release_env();
    (void)printf(PRG": Installed... %d key redefinition(s).\n",nk);
    keep(0,resparas());                                             /* TSR ! */
}

void main(int argc,char **argv)
{
    (void)printf(PRG" "VER": Keyboard ReDef Utility - Barninga_Z! "YEAR"\n");
    if(argv[1][0] == HELP_REQUEST)
        (void)printf(PRG": help screen.\n%s",hlpmsg);
    else
        if(tsrtest())
            if(argv[1][0] == FREE_REQUEST) {
                uninstall();
                (void)printf(PRG": Uninstalled: original keys restored.\n");
            }
            else
                (void)printf(PRG": Already installed.\n%s",hlpmsg);
        else
            if(readfile(argc,argv))
                install();
            else
                (void)printf(PRG": Invalid data file name or lines.\n%s",hlpmsg);
}
Sono state introdotte anche alcune modifiche volte ad incrementare l'efficienza complessiva del programma: in particolare, nella newint09h() il ciclo di scansione della tabella dei tasti ridefiniti è gestito esplicitamente mediante il registro SI (nella precedente versione era utilizzata una variabile automatica allocata nello stack). Detta tabella, infine, è stata scorporata dalla funzione jolly Dummy() e lo spazio ad essa necessario è ora riservato mediante una seconda funzione fittizia, la dummyFkeys(); è stata inoltre coerentemente modificata la definizione della costante manifesta keys

Una utility 

A corredo di KBDPLUS presentiamo un semplice programma in grado di visualizzare, nei formati decimale ed esadecimale, lo scan code ed il codice ASCII di ogni tasto premuto. KBDCODES può risultare utile nella preparazione della tabella di ridefinizioni da utilizzare con KBDPLUS
/**************************************************************************

    KBDCODES.C - Barninga_Z! - 1991

        Visualizza scan code e ascii code del tasto premuto. Per uscire
        basta premere ESC due volte di seguito.

    Compilato sotto BORLAND C++ 2.0

        tcc -O -d -mt -lt kbdcodes.c

**************************************************************************/
#include <stdio.h>

#define  ESC      ((char)0x1B)
#define  NORMAL   ((char)0x00)
#define  EXTENDED ((char)0x10)
#define  NORMOPT  '-'
#define  EXTOPT   '+'
#define  YRLIMIT  ((char)0x05)
#define  YRADDR   ((char far *)0xF000FFFCL)

char *msg[] ={"\
KEYBCODE 1.0 : Keyboard Codes : Barninga_Z! : Torino, 14/04/1991\n\n\
Current setting is %s bios service:\n\
KBDCODES +   forces extended bios service;\n\
KBDCODES -   forces normal bios service.\n\n\
Press keys to see ScanCodes and AsciiCodes;\n\
Press ESC twice to exit.\n\n",
"\
ScanCode  = %02Xh (%3d);  AsciiCode = %02Xh (%3d)\n",
"\
normal",
"\
extended"
};

void main(int argc,char **argv)
{
    unsigned char scan, ascii, service = NORMAL;
    register count = 2;
    char far *BiosYear = YRADDR;

    if(*BiosYear > YRLIMIT) {
        service = EXTENDED;
        count = 3;
    }
    if(argc > 1)
        if(*argv[1] == NORMOPT) {
            service = NORMAL;
            count = 2;
        }
        else
            if(*argv[1] == EXTOPT) {
                service = EXTENDED;
                count = 3;
            }
    printf(msg[0],msg[count]);
    for(count = 0; ; count < 2) {
        _AH = service;
        asm int 16h;
        scan = _AH;
        ascii = _AL;
        printf(msg[1],(int)scan,(int)scan,(int)ascii,(int)ascii);
        if(ascii == ESC)
            ++count;
        else
            count = 0;
    }
}
KBDCODES si basa sull'int 16h, di cui invoca il servizio 10h (tastiera estesa) se il bios della macchina è datato 1986 o piu' recente, altrimenti usa il servizio 00h. E' possibile forzare l'uso del servizio 10h invocando il programma con il parametro '+' sulla command line; il parametro '­', al contrario, forza l'utilizzo del servizio 00h. Per uscire dal programma è sufficiente premere il tasto ESC due volte consecutive oppure la sequenza CTRL­BREAK

OK, andiamo avanti a leggere il libro... 

Non ci ho capito niente! Ricominciamo...