KeyClick: Fensteraktionen per Tastatur

GEM stellt auf dem Atari ST ein Fenstersystem bereit, das mit standardisierten Fensterelementen eine recht komfortable Oberfläche ermöglicht. Die Felder für das Schließen von Fenstern oder das Blättern im Fensterinhalt müssen aber per Maus angeklickt werden, was beispielsweise in einer Textverarbeitung umständlich sein kann. Wir implementieren ein kleines Accessory und einen residenten Programmteil, mit denen zusammen man per Tastatur ein Fenster verändern kann: Mit dem Zehnerblock läßt sich Blättern, ein Fenster bewegen und seine Größe verändern.

Ein Fenster besteht aus seinem eigentlichen Inhalt - einem Text oder einer Grafik zum Beispiel - und aus den verschiedenen standardisierten Randelementen wie Full-Box oder den Schiebern zur horizontalen und vertikalen Positionierung. Will man den Fensterinhalt in einer Textverarbeitung um eine Spalte nach links verschieben, muß man zur Maus greifen und auf den dargestellten Pfeil nach links klicken. Stellt die Anwendung nicht selber Tastaturkürzel für solche Aktionen bereit, ist der Griff zur Maus durchaus umständlich.

Das Accessory KeyClick ermöglicht es, die entsprechenden Fensterkommandos von der Tastatur aus zu geben. Dazu wird der Zehnerblock verwendet, der bei den meisten Anwendungen ungenutzt bleibt. Will man also den Fensterinhalt um eine Spalte nach links verschieben, braucht man mit KeyClick nur noch die öffnende runde Klammer auf dem Ziffernblock zu drücken und kann sich den Mausgriff ersparen. Zum Vergrößern eines Fensters reicht ein Tippen auf „+“ des Zehnerblocks, ohne umständliche Mausbewegungen.

Ohne Klick

KeyClick besteht aus zwei Teilen, einem residenten Programm für den AUTO-Ordner und einem Accessory zur Steuerung.

Das residente Programm besteht aus dem C-Teil CLICKTRS.C in Listing 1 und dem Assembler-Teil CLICKHAN.S in Listing 2. Die Projektdatei CLICKTSR.PRJ in Listing 3 erzeugt daraus automatisch KEYCLICK.PRG, das Sie in Ihren AUTO-Ordner kopieren sollten. Die Projektdatei KEYCLICK.PRJ in Listing 4 erzeugt aus KEYCLICK.C in Listing 5 das Accessory KEYCLICK.ACC, das in die Boot-Partition gehört. Listing 6 zeigt das von beiden Programmen benutzte Header-File KEY-CLICK.H.

Als Voreinstellung wird KeyClick nun in jedem Programm, auch auf dem Desktop, aktiv. Die Tasten 0 und < Enter > auf dem Ziffernblock lösen die Aktionen „Fenster schließen“ und „Fenster auf Maximalgröße bringen“ aus. Für eine horizontale Bewegung sind „(“, „/“ und „*“ vorgesehen, die eine Seite nach links, eine Spalte nach links, eine Spalte nach rechts und eine Seite nach rechts auslösen. Damit entspricht dem Klicken in den grauen Bereich neben dem horizontalen Schieber. Für die vertikale Bewegung gilt entsprechend die Tastenreihe 9, 6, 3 und auf dem Ziffernblock.

Zur Veränderung der Fenstergröße dienen und mit 7 und 8 wird das Window horizontal verschoben, mit 4 und 1 vertikal. Dabei benutzt KeyClick eine grobe oder feine Sprungweite. In der groben Einstellung verschiebt sich das Fenster oder seine Größe um 16 Pixel, mit der feinen Einstellung kann es bildpunktweise verändert werden. 5 auf dem Ziffernblock wählt die feine Einstellung und die 2 die grobe.

Die Bilder zeigen diese Zuordnung und machen sie vielleicht intuitiver klar. Mit etwas Gewöhnung beherrscht man die Tastenzuordnung schnell.

Das Accessory läßt in einem kleinen Dialog zwei Einstellungen zu. Aus Wunsch kann KeyClick ganz abgeschaltet werden, so daß der Zehnerblock wieder „normal“ arbeitet. Mit der zweiten Einstellung kann festgelegt werden, daß zusätzlich zu der Zehnerblock-Taste auch < Alternate > gedrückt werden muß.

Der Programmaufbau

Grundlegend für KeyClick ist die Event-Verarbeitung in GEM. Jede Applikation muß in einer Schleife auf Ereignisse warten, die beispielsweise das Drücken einer Taste, eine Menüauswahl oder eine Fensteraktion beschreiben. Der GEM-Be-standteil AES sorgt dafür, daß Benutzeraktionen wie das Verschieben eines Fensters in entsprechende Mitteilungen übersetzt werden.

Die Idee bei KeyClick ist nun, anstelle des AES Mitteilungen zur Fenstermanipulation zu erzeugen. Bei einem Mausklick überprüft das AES, ob sich der Mauszeiger beispielsweise auf dem Pfeil nach links im Fensterrahmen befindet. Ist das der Fall, wird an die laufende Applikation eine Mitteilung geschickt, daß der Benutzer den Fensterinhalt um eine Spalte nach links bewegen will. KeyClick überwacht die Tastatureingaben auf dem Zehnerblock und schickt einfach bei Drücken von „(“ dieselbe Mitteilung ab. Der laufenden Applikation ist es egal, wer die Mitteilung schickt - sie verschiebt den Fensterinhalt.

KeyClick besteht aus einem residenten Teil und einem Accessory. Dabei arbeitet das residente Programm systemnah, überwacht die Tasteneingaben und schreibt mit, welcher Applikation welches Fenster gehört. Das Accessory übernimmt die gefilterten Tastendrücke und schickt entsprechende Mitteilungen per AES ab.

Die Hauptaufgabe des residenten Teils ist die Überprüfung aller Tastatureingaben und das Ausfiltern der Tasten auf dem Zehnerblock. Da für das Absenden einer Fenstermitteilung die Applikations-ID des Fenstereigentümers nötig ist, und diese nicht per AES ermittelt werden kann, muß der residente Teil ebenfalls das Öffnen und Schließen von Fenstern überwachen und die ID jeweils mitschreiben. Zusätzlich werden die Fensterelemente vermerkt, denn ein Fenster ohne Fuller-Button soll keine Full-Mitteilung erhalten.

Das Accessory übernimmt in regelmäßigen Abständen die ausgefilterten Tasten und erzeugt aus ihnen - so die Fensterart es erlaubt - entsprechende Mitteilungen. Beim Abschicken per appl_write werden dann die mitgeschriebenen Applikations-IDs verwendet. Außerdem kann mit dem Accessory KeyClick ein- und ausgeschaltet und das zusätzliche Drücken von < Alternate > gesteuert werden.

Die beiden unabhängig voneinander laufenden Programmteile müssen durch geeignete Datenstrukturen verbunden werden. Sie wird in der Header-Datei als KCC definiert und besteht aus der FIFO-Schlange scans mit Lese- und Schreibindex rindex und windex, den Feldern kind und id, in denen für jedes Fenster-Handle die Art und die ID des Eigentümers steht, und schließlich für die Steuerung von KeyClick die Flags klickon und watchalt.

Da die Struktur im residenten Teil vereinbart wird, muß das Accessory seine Adresse erfahren können. Dazu benutzen wir den Cookie-Mechanismus und legen bei der ersten Initialisierung des residenten Teils einen Cookie „RTKC“ ab, dessen Wert genau diese Adresse enthält. Das Accessory sucht diesen Cookie und kann dann auf die ausgefilterten Tastendrücke und die Fensterdaten problemlos zugreifen.

Trapreich

Da sich der residente Teil in den GEM-Trap einhängt, um die Fenster-Operationen zu überwachen, muß er auf die Initialisierung des AES warten. Man könnte sich dazu in den Vektor exec_os einhängen, was KeyClick aber nicht tut. Vielmehr wird anfangs lediglich der Cookie angelegt und in der Datenstruktur ein zusätzlicher Zeiger init auf die eigentliche Veränderung von Vektoren eingetragen.

Das Accessory ruft anfangs genau diese Routine auf. Da das AES zu diesem Zeitpunkt initialisiert sein muß, geschieht dies zum genau richtigen Zeitpunkt. Übrigens wird das Durchlaufen dieser Initialisierung in dem Flag installed vermerkt, womit vermieden werden kann, daß ein Neuladen des Accessories zu keiner doppelten Installation des residenten Teils führt. Das Verfahren vereinfacht die Installation der GEM-Manipulation erheblich, da das AUTO-Programm nicht mehr selber auf die Initialisierung des AES warten muß.

Bei jeder Mausbewegung oder Tasteneingabe sendet der Tastaturprozessor ein Paket an den ST, der dort mit der ikbd sys-Routine verarbeitet wird. Der residente Teil klinkt sich mit my kbdsys - in Assembler formuliert - ein. Dabei wird zunächst die Original-Routine aufgerufen, die zwischen Mausbewegungen und Tastaturdrücken unterscheidet und die genannten Pakete des Tastaturprozessors dekodiert. Einen Tastendruck schreibt sie in den Zeichenpuffer für die Tastatur, dessen Adresse per Kbdvbase abgefragt wird.

Nach der Original-Routine wird handle_ikbd - in C formuliert - aufgerufen. Diese Funktion testet, ob KeyClick aktiv sein soll, ob überhaupt ein Tastendruck vorlag (es hätte auch eine Mausbewegung sein können), und ob eine Taste auf dem Zehnerblock gedrückt wurde.

Zur Pufferung der Tasten auf dem Zehnerblock wird eine einfache Zeichenschlange benutzt, die bis zu 16 Tastendrücke aufnehmen kann. Eine solche Schlange wird beschrieben durch einen Schreib- und einen Leseindex. Sie ist genau dann voll, wenn der Schreibindex den Leseindex „überholen“ und damit ein noch nicht verarbeitetes Schlangenelement überschrieben würde. Die vierte Bedingung in handle_ikbd testet so, ob noch Platz ist. Schließlich wird - falls gewünscht - auf das Gedrückt sein von < Alternate > geprüft.

Trifft dies alles zu, wird der Tastendruck aus dem Zeichenpuffer genommen und in die Schlange geschrieben. Der Tastenpuffer wird durch eine iorec-Struktur beschrieben. Ein neues Zeichen wird an der Position iotail abgelegt, und zwar als Langwort aus ASCII-, Scan-Code und -falls gewünscht - dem Zustand der Sondertasten. Das Löschen des zuletzt gelesenen Zeichens verlangt also das Zurücksetzen dieses iotail um vier Byte, wobei die Puffergröße iosize berücksichtigt werden muß.

Die Überwachung der Fensteroperationen geschieht durch eine Manipulation der AES-Aufrufe. Jede wind_open-, wind_set-, wind_close- und wind_new-Aktion führt zu einer gesonderten Behandlung. Bei wind_open werden zunächst die Applikations-ID des Aufrufers - sie ergibt sich aus dem global-Feld des AES-Parameterblocks des Aufrufers - und der gewünschte Wert für die Fensterart - zu finden im IntIn-Feld - zwischengespeichert.

Die Rückkehradresse des TRAPs wird dann so verändert, daß nach dem wind_open unser Trap-Handler nochmals arbeitet. Er nimmt das Aufrufergebnis -das Fenster-Handle - und schreibt die ID und die Fensterart mit dem Handle als Index in die Felder id und kind. Bei einem wind_set wird der kind-Eintrag entsprechend dem übergebenen Parameter verändert. wind_close löscht ein Fenster, entsprechend wird der Eintrag im id-Feld auf -1 gesetzt. Bei wind_new kommen alle IDs auf -1.

Aus den Feldern kind und id läßt sich nun ermitteln, wem welches Fenster gehört und wie es aussieht - und das, obwohl der Desktop keine AES-Aufrufe macht. Mit einem wind_get(-1,WF_TOP kann das Handle des obersten Fensters ermittelt werden. Ist id[handle] ungleich -1, steht in kind [handle] die Art des Fensters. Ist id[handle] gleich -1, muß es sich um ein Fenster des Desktops handeln, da alle anderen ja durch wind_open geöffnet wurden und somit bemerkt worden wären. In diesem Fall ist die Applikations-ID gleich 0, und das Fenster hat alle Randelemente.

Die übrigen Routinen des residenten Programms kümmern sich darum, daß der AES-Trap nicht verändert wird. Wir haben dieselbe Methode schon für AUTOFLY verwendet [ 1 ], so daß hier nur eine kurze Beschreibung erfolgen soll.

Das AES setzt nach jedem Lauf eines .TOS- oder .TTP-Programms den TRAP #2 auf den Original-Trap-Handler im ROM zurück. Da dadurch ein Programm, das diesen TRAP umbiegt, ausgeklinkt wäre, muß dies verhindert werden. Daher wird zunächst bei einem Programmende durch PTerm, PTermO oder PTermRes der aktuelle Wert des TRAP #2 gesichert. Der aktuelle Wert deshalb, weil sich ja auch andere Programme nach Key Click dort eingehängt haben können. Ein direktes Schreiben vom my_gem handler würde solche Programme ausschalten.

Nach dem Zurücksetzen des TRAP #2 verändert das AES den Vektor des Critical-Error-Handler. Glücklicherweise verwendet es dazu einen SetExc-Aufruf, so daß genau nach diesem Aufruf der vorher gesicherte Wert des TRAP #2 zurückgeschrieben wird. Um dieses Verfahren zu sichern, muß sich das Programm zusätzlich in das GEMDOS und das BIOS einhängen, um die PTerm...- und SetExc-Aufrufe abzufangen.

Die Belegung des Zehnerblocks

Ganz ordinär

Das Accessory hat nichts mit TRAP-Manipulationen zu tun - es stützt sich im Hauptteil auf AES-Funktionen. Nachdem getestet wurde, ob der residente Teil vorhanden ist, und per Cookie die Adresse der nötigen Datenstrukturen ermittelt wurde, kann die typische Accessory-Schleife beginnen.

Alle 100 ms wird es aktiv und schaut in der scans-Schlange nach, ob Fenster-Tastendrücke vorliegen. Das ist genau dann der Fall, wenn der Leseindex nicht gleich dem Schreibindex ist, da in diesem Fall die Schlange leer ist. Liegt also eine ausgefilterte Taste vor, wird zunächst mit Hilfe von wind_get das Handle des obersten Fensters ermittelt und dann mit id und kind dessen Eigentümer und Art festgestellt. Wie oben beschrieben, werden Desktop-Fenster erkannt und gesondert behandelt.

Die nun folgende Fallunterscheidung stellt je nach Taste eine Fenstermitteilung zusammen. Dabei lassen sich die Fensterposition und -große wieder einfach durch wind_get ermitteln und entsprechend verändern. Abgeschlossen wird die Arbeit durch das Absenden einer Fenstermitteilung an die Applikation per appl_write, der das oberste Fenster gehört. Lediglich bei Veränderung der Schrittweite entsteht keine Mitteilung.

Im Programmtext lassen sich durch #define verschiedene Konstanten festlegen, und zwar mit COARSESTEP und FINESTEP die grobe und feine Schrittweite, mit MINWORK die minimale Größe eines Fensters beim Verkleinern. CYCLETIME beschreibt in Millisekunden die Aufrufhäufigkeit des Accessories. Schließlich legt BUFFERSIZE die Größe der Schlange für die gefilterten Tastendrücke fest.

Die Trennung zwischen Accessory und residentem Programmteil hat verschiedene Effekte. Wird das Accessory ohne KEYCLICK.PRG geladen, läßt sich dieses über den Cookie feststellen. Der residente Teil ohne Accessory bleibt einfach untätig, da das klickon-Pi&g initial ausgeschaltet ist. Wird das Accessory entfernt, werden Tasten nur so lange gefiltert, bis die Schlange voll ist. Danach bleibt KEYCLICK.PRG praktisch inaktiv. Wird dann das Accessory neu geladen, erfolgt keine neue Initialisierung des residenten Programmteils; vielmehr verarbeitetes die gefilterten Tastendrücke, wodurch bei sich leerender Schlange neue Tasten auch wieder gefiltert werden.

Da die eigentliche Initialisierung des residenten Teils vom Accessory ausgelöst wird, braucht KEYCLICK.PRG nicht umständlich auf die Initialisierung des AES zu warten. Und die Trennung erfüllt die Forderung, daß Accessories keine Traps verändern dürfen, da sie bei einem Auflösungswechsel ohne Mitteilung aus dem Speicher entfernt werden und die eigene Trap-Routine dann nicht mehr vorhanden ist, obwohl ein Vektor auf sie zeigt.

RT

Literatur:

[1] AUTOFLY - Nur Fliegen ist schöner, Robert Tolksdorf, ST-Computer 6/91, Seiten 101-112.

/******************************************
* CLICKTSR.C
* Residenter Teil von KeyClick
*
* (c) 1991 MAXON Computer
* 1991 by Robert Tolksdorf
*
* Geschrieben mit TURBO-C V2.0 mit MAS-68K
*
******************************************/

/******************************************
 * TOS-Definitionen 
 */

#include <tos.h>
#include <stdlib.h>

#include "keyclick.h"

/******************************************
 * In CLICKHAN.S definiert 
 */
extern void my_kbdsys(), 
        my_aes(), 
        my_gemdos(), 
        my_bios(),
        (*aes_save)();

extern void *XB_KBDSYS,
        *XB_AES,
        *XB_GEMDOS,
        *XB_BIOS;

KCC     kc;

/******************************************
 * Zeiger auf für uns wichtige Felder des
 * Iorec-Structure. Damit der Interrupt-
 * Handler möglichst schnell ist, werden
 * sie direkt vermerkt.
 */
IOREC   *kbiorec;
int     *iotail,
        *iohead,
        *iosize; 
char    *iobuf,
        *kbshift;   /* Zeiger auf kb_shift */
char    c;
SYSHDR  *rom_start;
long    stackcorr;
long    super_stack;

KBDVBASE *kbdvb;

/************************************************
 *
 * handle_ikbd()
 *
 * Der eigentliche Interrupt-Handler für die
 * Überprüfung eines Tastendrucks. Wird
 * direkt vom Handler in KEYHANDL.S aufgerufen. */

void handle_ikbd(void)
{
    /* Scancode des Tastendrucks am Ende des Puffers auslesen */ 
    c=iobuf[(*iotail)+1]; 
    if ((kc.klickon) &&
            /* liegt ein Tastendruck vor ? */ 
        (*iohead!=*iotail) &&
            /* eine Fenstertaste ? */
        (((c>=KEY_LBRACE) (c<=KEY_ENTER)) ||
         (c==KEY_PLUS) || (c==KEY_MINUS)) &&
            /* ist Platz im Puffer ? */
        (((kc.windex+1) & (BUFFERSIZE-1)) != kc.rindex) &&
            /* Falls gewünscht auf <Alt> achten */
        ((kc.watchalt) ? (*kbshift & 8) : 1 ))
    {
        /* Scancode in Puffer kopieren */ 
        kc.scans[kc.windex++]=c; 
        kc.windex&=BUFFERSIZE-1;
        /* Tastendruck aus Iorec-Puffer nehmen */ 
        *iotail=(*iotail-4) % *iosize;
    }
}

void init(void)
{
    /* Informationen über Iorec der Tastatur übernehmen */ 
    kbiorec=Iorec(1); 
    iotail=&kbiorec->ibuftl; 
    iohead=&kbiorec->ibufhd; 
    iosize=&kbiorec->ibufsiz; 
    iobuf =kbiorec->ibuf;
    /* KeyClick in kbdsys() installieren */
    /* Alten Vektor XBRA-konform merken */ 
    aes_save=my_aes; 
    kbdvb=Kbdvbase();
    XB_KBDSYS=kbdvb->kb_kbdsys; 
    kbdvb->kb_kbdsys=my_kbdsys;
    XB_GEMDOS=Setexc(33,my_gemdos);
    XB_BIOS  =Setexc(45,my_bios);
    XB_AES   =Setexc(34,my_aes); 
    kc.installed=1;
}

/* Cookie-Jar einrichten, Zeiger auf ersten Cookie abliefern */
COOKIE *install_cookie_jar(long n)
{
    COOKIE *cookie;

    cookie=Malloc(sizeof(COOKIE)*n);
    Super(0L);
    *(long *)0x5A0L=cookie;
    Super((void *) superstack); 
    cookie->id=0L; 
    cookie->val=n; 
    return (cookie);
}

/******************************************
*
* main()
*
*/

void main()
{
    char *11    = "\r\n\x1Bp        KeyClick-TSR V0     \x1Bq\r\n" \
                  "1991 by Robert Tolksdorf\r\n" \ "\r\n\n"; 
    char *12    = "Already ";
    char *13    = "Installed\r\n\n";

    SYSHDR      *SysHeader;
    long        jarsize,Stack;
    int         ncookie = 0,i;
    COOKIE      *cookie,*cookieo;

    Cconws(11);
    /* Adress von kbshift ermitteln, in der 
       d.Zustand v.< Alternate > vermerkt ist; 
       da im Systembereich im Super-Modus */ 
    Stack=Super(0L);
    SysHeader=*(long *)0x4F2L; 
    kbshift=(char *)SysHeader->kbshift; 
    cookie=cookieo= *(long *)0x5A0L; 
    rom_start=SysHeader->os_base; 
    stackcorr = (*(int *)0x59EL) ? 8 : 6 ;
    Super((void *)Stack);
    /* Kein Cookie-Jar vorhanden -> neuen einrichten */ 
    if (!cookie)
        cookie=install_cookie_jar(8L); 
    else
        /* sonst durchsuchen */ 
        for (; ( (cookie->id) && (cookie->id! = COOKIEID)); 
        cookie++, ncookie++);
    /* cookie zeigt auf Keyclick- oder Null-Cookie */ 
    if (!cookie->id)
    {
        /* KEYCLICK noch nicht installiert */
        /* Ist noch Platz ?? */
        if (cookie->val<=ncookie)
        {
            /* nein -> neuen einrichten, alten kopieren */ 
            cookie=install_cookie_jar(cookie->val + 8L) ;
            for (;cookieo->id!=0L;(*cookie++)=(*cookieo++)); 
            cookie->id=0L;
            cookie->val=cookieo->val+8L;
        }
        /* Cookie hinterlassen */ 
        jarsize=cookie->val; 
        cookie->id=COOKIEID; 
        cookie++->val=&kc; 
        cookie->id=0L; 
        cookie->val=jarsize; 
        kc.init=init; 
        kc.windex= 
        kc.rindex= 
        kc.watchalt= 
        kc.installed= 
        kc.klickon=0;
        for (i=0; i<WINDOWMAX; kc.id[i++)=-1);
        /* "Installiert"-Meldung */
        Cconws(13);
        /* Und resident im Speicher bleiben */ 
        Ptermres(_PgmSize,0);
    }
    else
    {
        /* "Already Installed" melden */
        Cconws(12);
        Cconws(13);
        /* Mit Fehlercode enden */ 
        exit(1);
    }

}

/* Ende von KEYCLICK.C */
; CLICKHAN.S
;
; 1991 by Robert Tolksdorf
;
; Leitet kbd_sys() um
;

    IMPORT handle ikbd, stackcorr 
    IMPORT kc, rom_start
    EXPORT XB_KBDSYS, XB_AES, XB_GEMDOS
    EXPORT XB_BIOS, my_kbdsys my_aes
    EXPORT my_gemdos, my_bios, aes_save

BUFFERSIZE = 16
WINDOWMAX  = 8
KINDOFFS   = 12+BUFFERSIZE
IDOFFS     = KINDOFFS+WINDOWMAX*2

        TEXT

semaphor: DC.W 0

; XBRA-Header
        DC.L    "XBRARTKC"
XB_KBDSYS: DC.L 0

my_kbdsys :
        MOVE.L  XB_KBDSYS(PC),A1    ; Alten Vektor holen

        TAS.W   semaphor            ; Ist unser Handler frei?
        BEQ     check_key           ; ja, Taste überprüfen

        JMP     (A1)                ; normal weiter

check_key:
        JSR     (A1)                ; Tastendruck verarbeiten
        JSR     handle_ikbd(PC)     ; und überprüfen

        CLR.W   semaphor            ; wir sind fertig
        RTS                         ; und beenden

; XBRA-Header
        DC.L    "XBRARTKC"
XB_AES: DC.L    0

; jeder AES/VDI-Call landet hier 
my_aes:
    CMPI.B      #$C8,D0             ; ist es ein AES-Call
    BNE         standard_call       ; nein -> normal weiter

do_aes_call:
    MOVEM.L     A0-A2/D0,-(A7)      ; A0,A1,A2,D0 sichern
    MOVE.L      D1,A1               ; AES-Parmblock-Adr holen
    MOVE.L      8(A1),A2            ; IntIn-Adresse holen
    MOVE.L      4(A1),A0
    MOVE.L      (A1),A1             ; Control-Adresse holen
    CMPI.W      #105, (A1)          ; wind_set Call ?
    BNE         test_delete         ; nein -> weiter
;
; Ein wind_set-Aufruf, auf WF_KIND testen
;
    CMPI.W      #1, 2(A2)           ; IntIn[1]==WF_KIND?
    BNE         continue_standard   ; nein -> zum normalen GEM-Call
;
; Ein wind_set(handle,WF_KIND, kind)-Aufruf -> kind merken
;
    MOVEA.L     #kc+KINDOFFS, A1
    MOVE.W      (A2), D0            ; handle aus IntIn[0]
    LSL.W       #1,D0
    MOVE.W      4(A2), (A1,D0.W)    ; kind aus IntIn[2]
    BRA         continue_standard

test_delete:
    CMPI.W      #103, (A1)          ; wind_delete Call ?
    BNE         test_new            ; nein -> weiter
;
; Ein winddelete(handle)-Aufruf -> ID- löschen
;
    MOVEA.L     #kc+IDOFFS,A1
    MOVE.W      (A2), D0            ; handle aus IntIn[0]
    LSL.W       #1,D0
    MOVE.W      #-1,(Al,D0.W)       ; id[handle]--1 BRA continue_standard

test_new:
    CMPI.W      #109, (A1)          ; wind_new Call ?
    BNE         test_create         ; nein -> weiter
;
; Ein wind_new()-Aufruf -> alle ID's löschen
;
    MOVEA.L     #kc+IDOFFS,A1
    MOVEQ.L     #-1,D0
    MOVE.L      D0,(A1)+            ; id[0-l]
    MOVE.L      D0,(A1)+            ; id[2-3]
    MOVE.L      D0,(A1)+            ; id[4-5]
    MOVE.L      D0,(A1)             ; id[6-7]

    BRA         continue_standard

test_create:
    CMPI.W      #100, (A1)          ; wind_create ?
    BNE         continue_standard

;
; Ein wind_create(kind)-Aufruf -> kind merken, hinterher 
; ID holen und handle ablegen
;
    MOVE.W      (A2), curr_kind     ; kind aus IntIn[0]
    MOVE.W      4(A0), curr_id      ; id aus global[2] 
    MOVE.L      18(SP),returnsav    ; trap-return Adresse sichern 
    MOVE.L      #after_create,18(SP); unseren Restore einmogeln

continue_standard:
    MOVEM.L     (A7)+,A0-A2/D0

standard_call:
    MOVE.L      XB_AES(PC),-(A7)    ; zum normalen GEM-Vektor
    RTS
;
; Ein wind_create landet hinterher hier
;
after_create:
    MOVE.L      A1,-(A7)
    MOVE.L      D0,-(A7)

    MOVE.L      D1,A1
    MOVE.L      12(A1),A1           ; IntOut-Adresse

    MOVE.W      (A1), D0            ; handle aus IntOut[0]
    LSL.W       #1,D0
    MOVEA.L     #kc+KINDOFFS, A1
    MOVE.W      curr_kind(PC),(A1,D0.W) ; kind ablegen
    LEA.L       WINDOWMAX* 2(A1) ,A1
    MOVE.W      curr_id(PC), (A1,D0.W) ; id ablegen

    MOVE.L      D0,(A7)+
    MOVE.L      A1,(A7)+

    MOVE.L      return_sav(PC), -(A7) ; weiter
    RTS

curr_id: DS.W 1
curr_kind: DS.W 1

;
; Ab hier Sicherung des #2-Vektors!
;
; XBRA-Header
        DC.L "XBRARTKC"
XB_GEMDOS: DC.L 0

my_gemdos:
        MOVE.L  A0, tmp_sv              ; A0 sichern
        MOVE.L  A7,A0
        ADD.L   stackcorr(PC),A0
        BTST.B  #5,(A7)                 ; Supervisor?
        BNE     called_in_super
        MOVE.L  USP, A0

called_in_super:
        TST.W   (A0)                    ; PTERM0 (#0) ??
        BEQ     save_gemtrap            ; ja -> sichern

        CMPI.W  #76,(A0)                ; PTERM ??
        BEQ     save_gemtrap            ; ja -> sichern

        CMPI.W  #49, (A0)               ; PTERMRES ??
        BNE     do_gemdos               ; auch nicht -> weiter

save_gemtrap:
        MOVE.L  $88, aes_save           ; TRAP #2 sichern

do_gemdos:
        MOVE.L  tmp_sv(PC),A0           ; A0 restaurieren
        MOVE.L  XB_GEMDOS(PC),-(A7)
        RTS

; XBRA-Header
         DC.L "XBRARTKC"
XB_BIOS: DC.L 0


; jeder Bios-Call landet hier my_bios:
    BTST.B      #5, (A7)
    BEQ         do_bios ; nur Superaufrufe interessant

    MOVE.L      A0,tmp_sv ; A0 sichern
    MOVE.L      A7, A0
    ADD.L       stackcorr(PC),A0

    CMPI.L      #$00050101,(A0)     ; Setexc(101,...) ?
    BNE         cont_bios           ; anderer Vektor

    MOVE.L      4(A0),A0            ; neuer Vektor ins ROM ?
    CMPA.L      rom_start(PC),A0
    BLT         cont bios           ; nicht ins ROM

    MOVE.L      2(SP),return_sav    ; trap-return Adresse sichern 
    MOVE.L      #restore,2(SP)      ; unseren Restore einmogeln

cont_bios:
    MOVE.L      tmp_sv(PC),A0       ; A0 restaurieren

do_bios:
    MOVE.L      XB_BIOS(PC),-(A7)   ; zum alten Bios-Vektor
    RTS

aes_save:       DS.L 1
return_sav:     DS.L 1
tmp_sv:         DS.L 1

restore:
    MOVE.L      aes_save(PC), $88   ; TRAP #2 sichern
    MOVE.L      return_sav(PC),-(A7) ; weiter
    RTS

    END

; Ende von CLICKHAN.S

Listing 2: Der Assembler-Teil von CLICKTSR.C

; CLICKTSR.PRJ Automatisches Erstellen von KEYCLICK.PRG

KEYCLICK.PRG        ; Das erzeugte Programm
.S[-S]              ; Privilegierte Instruktionen beim Assembler

=

TCSTART.O ; startup code

CLICKTSR.C          ; Das Hauptmodul
CLICKHAN.S          ; Der Assembler-Teil

TCSTDLIB.LIB        ; Standard library
TCTOSLIB.LIB        ; TOS library
TCGEMLIB.LIB        ; AES and VDI library

; Ende von CLICKTSR.PRJ

Listing 3: Die Projektdatei für KEYCLICK.PRG

; KEYCLICK.PRJ : Automatisches Erstellen von KEYCLICK.ACC

KEYCLICK.ACC        ; Das erzeugte Accessory
=

TCSTART.O           ; startup code

KEYCLICK.C          ; Das Hauptmodul

TCSTDLIB.LIB        ; Standard library
TCTOSLIB.LIB        ; TOS library
TCGEMLIB.LIB        ; AES and VDI library

; Ende von KEYCLICK.PRJ

Listing 4: Die Projektdatei für KEYCLICK.ACC

/******************************************
*  KEYCLICK.C
*  Fenster-Elemente per Tastatur anklicken
*
*  1991 by Robert Tolksdorf
*  (c) 1991 MAXON Computer
*  Geschrieben mit TURBO-C V2.0 mit MAS-68K
*
******************************************/

/******************************************
 * GEM-, VDI- und TOS-Definitionen
 */

#include <aes.h>
#include <vdi.h> 
#include <tos.h>

#include "keyclick.h"

/******************************************
 * Definition der Schrittweiten für grobe
 * und feine Veränderungen, minimale Größe
 * der Arbeitsfläche 
 */

#define COARSESTEP 16
#define FINESTEP 1 
#define MINWORK 48
#define CYCLETIME 100

char mentit[] = " KeyClick";

/******************************************
*
* main()
*
* Als Applikation und Accessory anmelden,
* Tastatur-Tests installieren und Dialog
* zum An- und Abschalten durchführen.
* Bei Vorliegen von Zeichen im internen
* Puffer, Mitteilungen verschicken.
*/

void main()
{
    int changestep = 16,
        wx,wy,ww,wh,        /* Fensterausmaße */
        mwx,mwy,mww,mwh,    /* maximale Größe */
        sy,sw,sh,           /* Bildschirmausmasse */
        wkind, topowner, 
        event, message[8], 
        wmessage[8], 
        workout[57]; 
    long Stack;
    /* Die Texte für die vier möglichen Dialoge */ 
    char alert[2][2][65] = {
        "[2][KeyClick|Robert Tolksdorf][KC ein|ALT ein|Abbruch]",
        "[2][KeyClick|Robert Tolksdorf][KC aus|ALT ein|Abbruch]",
        "[2][KeyClick|Robert Tolksdorf][KC ein|ALT aus|Abbruch]",
        "[2][KeyClick|Robert Tolksdorf][KC aus|ALT aus|Abbruch]"};

    COOKIE  *cookie;
    KCC     *kc;

    /* Anmeldung als Applikation
       anmelden, dabei KeyClick gleich als Sender von Mitteilungen 
       vermerken */ 
    wmessage[1]=appl_init();
    /* Standardmitteilungs */ 
    wmessage[2]=0;
    /* Ausmaße des Bildschirms ermitteln */ 
    vg_extnd(graf_handle(&wx,&wx,&wx,Ssy),0,workout); 
    sw=workout[0]; 
    sh=workout[1];
    /* Adress von kbshift ermitteln */
    Stack=Super(0L); 
    cookie= *(long *)0x5A0L;
    Super((void *)Stack); 
    if (cookie)
        for (;((cookie->id) && (cookie->id!=COOKIEID)); cookie++);
        if (cookie->id==COOKIEID)
        {
            kc=cookie->val; 
            if (!kc->installed) 
                kc->init(); 
            menu_register(wmessage[1], mentit);
            /* Hauptschleife, wartet auf Ereignis und stellt dann 
            kleinen Dialog zum An- und Abschalten dar oder verarbeitet 
            eine gepufferte Fenster-Taste */
            do
            {
                /* Auf Mitteilung oder das
                Vergehen von 100ms warten */ 
                event=evnt_multi(MU_MESAG | MU_TIMER,
                        0,0,0,0,0,0,0,0,0,0,0,0,0, 
                        message, CYCLETIME, 0, 
                        &wx,&wx,&wx,&wx,&wx,&wx);
                /* AC_OPEN-Mitteilung eingetroffen ? */ 
                if ((event & MU_MESAG) && (message[0]==AC_OPEN))
                    /* Kleinen Dialog mit Alert-Box durchführen */ 
                    switch (form_alert(3,a1ert[kc->watchalt][kc->klickon]))
                    {
                        /* Button Ja */
                        case 1: kc->klickon=!kc->klickon;
                                break;
                        /* Button Nein */ 
                        case 2: kc->watchalt=!kc->watchalt;
                                break;
                        /* Button Abbruch */ 
                        default: break;
                    }
                /* 100ms vergangen und Fenstertaste vorhanden ? */ 
                if ((event & MU_TIMER) && (kc->windex!=kc->rindex))
                {
                    /* Welches Handle hat das oberste Fenster ? */ 
                    if ((wind_get(-1,WF_TOP, &wmessage[3])) && (wmessage[3]))
                    {
                        topowner=kc->id[wmessage[3]]; 
                        wkind=kc->kind[wmessage[3]]; 
                        if (topowner==-1)
                        {
                            topowner=0;
                            wkind=NAME+CLOSER+FULLER+MOVER+
                                SIZER+UPARROW+DNARROW+ 
                                VSLIDE+LFARROW+ RTARROW+HSLIDE;
                        }
                        /* Mitteilung vorbereiten,
                        Sliderveränderung als Default */ 
                        wmessage[0]=0;
                        /* Fenstertaste verarbeiten */ 
                        switch (kc->scans[kc->rindex])
                        {
                            case KEY_0:
                                /* Window-Closed Mitteilung */ 
                                if (wkind & CLOSER)
                                    wmessage[0]=WM_CLOSED; 
                                break; 
                            case KEY_ENTER:
                                /* Window-Fulled Mitteilung */ 
                                if (wkind & FULLER)
                                    wmessage[0]=WM_FULLED; 
                                break; 
                            case KEY_LBRACE:
                                /* Seite nach links */ 
                                if (wkind & HSLIDE)
                                {
                                    wmessage[4]=WA_LFPAGE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_RBRACE:
                                /* Spalte nach links */ 
                                if (wkind & LFARROW)
                                {
                                    wmessage[4]=WA_LFLINE; 
                                    wmessage[0]=WMARROWED;
                                }
                                break;
                            case KEY_SLASH:
                                /* Spalte nach rechts */ 
                                if (wkind & RTARROW)
                                {
                                    wmessage[4]=WA_RTLINE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_STAR:
                                /* Seite nach rechts */ 
                                if (wkind & HSLIDE)
                                {
                                    wmessage[4]=WA_RTPAGE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_9:
                                /* Seite nach oben */ 
                                if (wkind & VSLIDE)
                                {
                                    wmessage[4]=WA_UPPAGE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_6:
                                /* Zeile nach oben */ 
                                if (wkind & UPARROW)
                                {
                                    wmessage[4]=WA_UPLINE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_3:
                                /* Zeile nach unten */ 
                                if (wkind & DNARROW)
                                {
                                    wmessage[4]=WA_DNLINE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_DOT:
                                /* Seite nach unten */ 
                                if (wkind & VSLIDE)
                                {
                                    wmessage[4]=WA_DNPAGE; 
                                    wmessage[0]=WM_ARROWED;
                                }
                                break; 
                            case KEY_MINUS:
                                /* Verkleinern */ 
                                if (wkind & SIZER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, &wmessage[4],&wmessage[5], &ww, &wh); 
                                    wind_get(wmessage[3],WF_WORKXYWH, &mwx, &mwy, &mww, &mwh) ; 
                                    wmessage[0]=WM_SIZED; 
                                    wmessage[6]=((mww-changestep)>MINWORK? ww-changestep : ww); 
                                    wmessage[7]=((mwh-changestep)>MINWORK? wh-changestep : wh);
                                }
                                break; 
                            case KEY_PLUS:
                                /* VergröBern */ 
                                if (wkind & SIZER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, &wmessage[4],&wmessage[5] , &ww,&wh); 
                                    wind_get(wmessage[3],WF_FULLXYWH, &mwx, &mwy, &mww, &mwh) ; 
                                    wmessage[0]=WM_SIZED; 
                                    wmessage[6]=((ww+changestep)<mww? ww+changestep : mww); 
                                    wmessage[7]=((wh+changestep)<mwh? wh+changestep : mwh);
                                }
                                break; 
                            case KEY_5:
                                /* Grob verschieben/Größe ändern */ 
                                changestep=COARSESTEP; 
                                break; 
                            case KEY_2:
                                /* Fein verschieben/Größe ändern */ 
                                changestep=FINESTEP; 
                                break; 
                            case KEY_7:
                                /* Nach links verschieben */ 
                                if (wkind & MOVER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, 
                                        &wx,&wmessage[5],
                                        &wmessage[6],&wmessage[7]); 
                                        wmessage[0]=WM_MOVED; 
                                        wmessage[4]=((wx-changestep)>changestep ? wx-changestep : changestep);
                                }
                                break; 
                            case KEY_8:
                                /* Nach rechts verschieben */ 
                                if (wkind & MOVER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, 
                                        &wx,&wmessage[5],
                                        &wmessage[6],&wmessage[7]); 
                                        wmessage[0]=WM_MOVED; 
                                        wmessage[4]=((wx+changestep)< sw-changestep ? wx+changestep : sw-changestep);
                                }
                                break; 
                            case KEY_4:
                                /* Nach oben verschieben */ 
                                if (wkind & MOVER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, 
                                        &wmessage[4],&wy,
                                        &wmessage[6],&wmessage[7]); 
                                        wmessage[0]=WM_MOVED; 
                                        wmessage[5]=((wy-changestep)>sy ? wy-changestep : sy);
                                }
                                break; 
                            case KEY_1:
                                /* Nach unten verschieben */ 
                                if (wkind & MOVER)
                                {
                                    wind_get(wmessage[3],WF_CURRXYWH, 
                                        &wmessage[4],&wy,
                                        &wmessage[6],&wmessage[7]); 
                                        wmessage[0]=WM_MOVED; 
                                        wmessage[5]=((wy+changestep)<sh-changestep ? wy+changestep : sh-changestep);
                                }
                                break;
                        }
                        /* Mitteilung mit 16 Bytes Länge an Applikation 0 abschicken */ 
                        if (wmessage[0])
                            appl_write(topowner,16,wmessage);
                    }
                    /* Fenstertaste aus Buffer nehmen */ 
                    kc->rindex=(kc->rindex+1) & (BUFFERSIZE-1);
                }
            } while(1);
            /* Old Accessories never die -they just loop again */
        }
    else
    {
        form_alert(1,"[3][Ohne den residenten|"\ "Teil in KEYCLICK.PRG|"\ "kann KEYCLICK nicht|"\ "arbeiten][Abbruch]");
        do
            evnt_mesag(message); 
        while(1);
    }
}
/* Ende von KEYCLICK.C */

Listing 5: Das Accessory KEYCLICK.C

/****************************************
 * Definitionen der Scancodes der Tasten 
 * auf dem Zehnerblock
 */

#define KEY_LBRACE 0x63
#define KEY_RBRACE 0x64
#define KEY_SLASH 0x65 
#define KEY_STAR 0x66 
#define KEY_7 0x67
#define KEY_8 0x68
#define KEY_9 0x69
#define KEY_4 0x6A
#define KEY_5 0x6B
#define KEY_6 0x6C
#define KEY_1 0x6D
#define KEY_2 0x6E
#define KEY_3 0x6F
#define KEY_0 0x70
#define KEY_DOT 0x71
#define KEY_ENTER 0x72 
#define KEY_MINUS 0x4A 
#define KEY_PLUS 0x4E

/******************************************
 * Inhalt und Struktur des Cookies 
 */

#define COOKIEID 0x52544B43L

typedef struct{
    long id, val;
} COOKIE;

/******************************************
 * Die Struktur, auf die das Wertefeld des
 * Cookies zeigt.
 */

#define BUFFERSIZE 16 
#define WINDOWMAX 8

typedef struct (void (*init)(void);
            char    scans[BUFFERSIZE]; 
            int     windex; 
            int     rindex; 
            int     klickon; 
            int     watchalt; 
            int     kind[WINDOWMAX]; 
            int     id[WINDOWMAX]; 
            int     installed; } KCC;

/* Ende von KEYCLICK.H */

Listing 6: Die Header-Datei KEYCLICK.H


Robert Tolksdorf
Aus: ST-Computer 01 / 1992, Seite 126

Links

Copyright-Bestimmungen: siehe Über diese Seite