Jeder fünfte Brief, der die ST-Computer-Redaktion bezüglich der ST-Ecke erreichte, bezog sich auf die Maus. Die meisten der Schreiber beklagten, daß sie beim Verändern der relativen Mauskoordinaten über den Tastaturprozessor nur Mißerfolge erzielten. Wir wollen uns auch nicht weiter mit dem Tastaturprozessor befassen, sondern wollen ähnlich wie vor einigen Monaten, als wir eine Joystickroutine veröffentlichten, eine Mausroutine vorstellen, die die Möglichkeiten dieses Verfahrens darstellen soll.
In einigen Programmen, die sich für den ST auf dem Markt befinden, bemerkt man, daß der Mauszeiger insofern manipuliert worden ist, daß er der Maus in der Hand des Anwenders erstaunlich langsam folgt. Dies ist zum Beispiel für genaues Zeichnen oder Positionieren vorteilhaft. Nach einigem Nachforschen bin ich bei der Firma IMAGIC auf Hilfe gestoßen, die das kleine Geheimnis der Maus lüfteten...
An und für sich ist die Vorgehens weise ähnlich der Einbindung einer eigenen Joystickroutine. Für alle Leser, die das ST-COMPUTER MAGAZIN vom April mit der Joystick-Routine nicht haben, wiederhole ich die Fakten ein zweites Mal.
Im Speicher Ihres STs befindet sich eine Tabelle, deren Inhalt Zeiger auf bestimmte Routinen sind. Diese Routinen bedienen den Joystick, den MIDI-Port und einige andere Dinge. Daher wollen wir uns diese Tabelle näher anschauen:
Bei den Routinen handelt es sich praktisch um Service-Routinen, die, wenn es nötig ist, vom Betriebssystem aufgerufen werden. Glücklicherweise liegt diese Tabelle im frei zugänglichen RAM-Bereich, und man kann sogar die Adresse dieser Tabelle ermitteln. Weiß man nun die Parameter, die vom Betriebssystem übergeben werden, so kann man anstatt der Originalroutinen eigene einklinken, die diesen Service übernehmen. Während midisys indirekt durch midivec springt, verzweigt die Routine ikbdsys in die folgenden vier Routinen: Bei diesen Routinen statvec, mousevec, clockvec und joyvec handelt es sich bei den Parametern um die Informationspakete der vom Tastaturprozessor verarbeiteten Prozesse. Die Adresse, an der die Pakete zu finden sind, steht auf dem Stack und in AO. Man sollte darauf achten, daß die Serviceroutine mit RTS abgeschlossen sein muß und nicht länger als eine Millisekunde ist. Midivec schreibt die vom MIDI-Interface empfangenen Bytes ( dieses Byte steht in DO ) in einen dafür vorgesehenen Buffer; die Routinen vkbderr und vmierr werden angesprungen, wenn bei der Tastatur oder beim Midi-Port ein Überlauf stattgefunden hat.
Diesen Monat wollen wir die Maus bedienen. Zunächst schauen wir uns an, wie wir die Adresse der Tabelle ermitteln. Dazu benutzen wir die Routine Kbdvbase() des XBIOS:
zeiger = Kbdvbase();
KEYBD VECS *zeiger, KbdvbaseQ;
Als Rückgabeparameter erhält man einen Zeiger auf die Tabelle der Vektoren. Als nächstes wenden wir uns unserer neuen Mausroutine zu. Dabei wollen wir nicht die Originalroutine gänzlich ersetzen, vielmehr werden wir die Werte des Mauspaketes ein wenig manipulieren und in die alte Routine springen, die dann unsere manipulierten Werte, ohne daß Sie es weiß, weiterverarbeitet. Wie schon oben erwähnt, wird die Adresse des Mauspakets als Parameter in AO übergeben. Betrachten wir also das Mauspaket:
typedef struct {
midivec: /* MIDI-Eingabe */
vkbderr: /* Tastatur-Fehler-Routine */
vmiderr: /* MIDI-Fehler */
statvec: /* Status von IKBD lesen */
mousevec: /* Maus-Routine !!!! */
clockvec: /* Uhrzeit-Abfrage */
joyvec: /* Joystickroutine s. ST-Computer APRIL 87 */
midisys: /* MIDI-Systemvektor */
ikbdsys: /* IKBD-Systemvektor */
}KEYBD_VECS
Sinn unserer neuen Mausroutine ist es, die Geschwindigkeit unserer Maus zu verdoppeln. Dies erreichen wir, indem wir die relative X- und Y-Koordinate mit einem Faktor, hier zwei, multiplizieren oder durch Addition verdoppeln. Aufgabe ist es also, den alten Mausvektor zu ermitteln und sich zu merken. Dann wird die Adresse unserer neuen Routine in die Tabelle geschrieben. Die neue Mausroutine nimmt sich die relativen Koordinaten, verdoppelt sie direkt im Paket und springt dann in die alte Routine, die - nichtsahnend - den Mauszeiger neuzeichnet. Wir haben uns aber noch einen besonderen Gag einfallen lassen: Durch Druck der rechten Maustaste läßt sich der Effekt der Quick-Maus ein- und ausschalten. Wie das Verfahren in der Praxis aussieht, wollen wir uns an Listing 1 anschauen, nicht ohne daraufhingewiesen zu haben, daß Assembler schwer zu umgehen war und wir deshalb die wichtigen Routinen in Assembler geschrieben haben. Leider ist der Assembler-Source nicht besonders elegant, was aber nicht an uns, sondern an den eingeschränkten Möglichkeiten des INLINE-Assemblers liegt. Abgesehen davon kommt es auch nicht darauf an, besonders trickreich, sondern deutlich erkennbar zu programmieren.
Die Routine mouse() ist so ausgelegt, daß bei einer Übergabe des Parameters 0 die neue Mausroutine new_mouse einklinkt, während die Übergabe einer 1 die Adresse der Original-Mausroutine wieder in die Tabelle zurückschreibt. Dies ist wichtig, falls das Programm irgendwann wieder verlassen wird, denn dann wird ihre Mausroutine gelöscht. Wenn Sie trotzdem noch vom Betriebssystem angesprungen wird, herrscht Bombenstimmung... Zunächst wird die Adresse der Tabelle ermittelt. Dies geschieht absichtlich im Initialisierungsteil und nicht in der eigentlichen Mausroutine und wird in kbdv_addr zwischengespeichert. Der Grund ist nicht die Zeitersparnis, sondern daß das Ausführen einer XBIOS-Routine sehr unschöne Ereignisse zur Folge haben kann. Als nächstes wird geprüft, welchen Wert das Argument der Routine mouse hatte -dieses Argument liegt an neunter Stelle auf dem Stack. Wurde eine Null übergeben, springen wir in mouse_on. Diese Unterroutine holt die Adresse der Mausroutine und speichert sie in mouse_vec ab. Danach schreibt sie die Adresse unserer Routine in die Tabelle. Wurde die Eins als Parameter übergeben, schreibt mouse_off nur die Adresse der alten Mausroutine des Betriebssystems aus mouse_vec in die Tabelle rurück. Wenden wir uns also der eigentlichen Mausroutine zu.
Zunächst werden die gebrauchten Register gerettet. Im ersten Byte steht nun der Code des Paketes. Ist dieses Paket zwischen 0xf8 und Oxfc, so handelt es sich um ein Mauspaket, und es ist für uns interessant.
Nun schauen wir, ob momentan eine, beziehungsweise die rechte, Maustaste gedrückt worden ist. Falls dies der Fall ist, wird noch zusätzlich überprüft, ob die Taste im vorhergehenden Durchgang einen anderen Status hatte. Dies hat den Sinn, daß nur dann unsere Quick-mouse eingeschaltet wird, wenn der Benutzer die Taste wieder losgelassen hatte. Würden wir dies nicht beachten, erfolgte die Umschaltung zwischen langsamer und schneller Maus jedesmal dann, wenn gedrückt ist. Bei einem Maustastendruck könnte das einige zehnmal sein. Ist die Quick-mouse erwünscht, so vermerken wir dies im High-Byte von mouse_btn (not.b 1 (A1)). Die eigentliche Quick-mouse ist relativ einfach: In ihr werden die relative X-Koordinate und die relative Y-Koordinate einfach durch eine Addition verdoppelt. Danach wird nach dem Retten des Registers die Adresse der alten Mausroutine auf den Stack gelegt und mit einem RTS dort hingesprungen.
Durch ein wenig Beschäftigung mit der Assembler-Routine wird die Vorgehensweise sicherlich schnell klar. Nun noch ein paar Kleinigkeiten am Rande. Wir versuchten diese Routine als Programm zu schreiben, das man in den AUTO-Ordner legen kann. Obwohl wir wußten, daß unsere Routine in Ordnung war, geschah nichts. Nach einigem Überlegen kamen wir auf des Rätsels Lösung. Die Vorgehensweise beim Booten des Rechners ist die, daß er zunächst alle Programme im AUTO-Ordner startet und dann alle Vektoren im Rechner setzt. Das bedeutet natürlich, daß nach Einklinken unserer Routine unser Vektor wieder überschrieben wurde und somit nicht zum Zuge kam. Eine Lösung wäre das Programm in den VBL einzuschleifen und von diesem aus später die Mausroutine einzuklinken. Dies empfanden wir aber für die ST-Ecke als zu kompliziert, da wir das Augenmerk auf die wichtigsten Punkte lenken wollten. Es geht aber auch einfacher! Man organisiert das Programm als Accessory, denn zum Zeitpunkt des Ladens einer Accessory sind alle Vektoren schon gesetzt. Die Accessory läuft nach Setzen der neuen Mausroutine in einer Endlosschleife. Da eine Accessory nie verlassen wird, benutzen wir in unserem Beispiel die mouse() auch nicht mit Parameter 1.
Viel Spaß wünsche ich Ihnen nun beim Ausprobieren dieser Routine und nicht vergessen: Ein- und Ausschalten der Quick-mouse funktioniert über die rechte Maustaste, und das Programm muß als Accessory gelinkt werden.
/**********************************************************/
/* */
/* QUICK-MOUSE */
/* Idee und Programmierung: Jörg Drücker & Stefan Höhn */
/* Beratung: Alex */
/* */
/**********************************************************/
#include <osbind.h> /* Definitionen des OS */
#define SUPEREXEC 0x26 /* Supervisor-Eintritt */
#define XBIOS 14 /* TRAP-Nummer des XBIOS */
/**********************************************************/
/* */
/* Routine zum Einbinden der eigenen Mausroutine */
/* arg: =0 Einhängen der neuen Routine */
/* arg: =1 Wiedereinbinden des alten Vektors */
/* */
/**********************************************************/
mouse(arg)
char arg;
{
asm{
move #34,-(hl) /* Funktionsnummer Kbdvbase() */
trap #XBIOS /* Xbios-Aufruf */
addq.l #2,A7 /* stack korrigieren */
lea kbdv_addr,Al /* Adresse für Vektortabelle */
move.l D0,(A1) /* Adresse der Vektortabelle */
lea mouse_on,A0 /* Adresse der Einbinde-Routine */
tst.b 9(A6) /* Arg = 0 ? */
beq do_mouse /* Ja */
lea mouse_off,A0 /* Adresse der Ausblend-Routine */
do_mouse: pea (A0) /* Adresse der Routine auf Stack */
move.w #SUPEREXEC,-(A7) /* Supervisor */
trap #XBIOS /* Xbios-Einsprung */
addq.l #6,A7 /* Stack korrigieren */
jmp ende /* Bis dann... */
mouse_on: movea.l kbdv_addr,A0 /* Adresse Keyboard-Tabelle */
lea mouse_vec,A1
move.l 16(A0),(A1) /* Alten Vektor sichern */
lea new_mouse,A1
move.l A1,16(A0) /* Neuen Vektor setzen */
rts
mouse_off: movea.l kbdv_addr,A0 /* Adresse Keyboard-Tabelle */
lea mouse_vec,A1
move.l (A1),16(A0) /* Alten Vektor setzen */
rts
new_mouse: movem.l D0-D1/A0-A1,-(A7) /* alle Register retten */
lea mouse_btn,A1 /* Adresse des alten Status */
move.b (A0)+,D0 /* Code aus Paket holen */
cmpi.b #0xf8, D0 /* Mauscode: >=f8 <=fc */
bmi end_mouse
cmpi.b #0xfc, D0
bpi
end_mouse
andi.b #0x01, D0 /* linke S rechte Maustaste */
beq no_toggle /* nicht gedrückt */
move.b (A1),D1 /* alter Status */
eor.b D0,D1 /* alter Status EXOR neuer Status */
andi.b #0x01,Dl /* Unterschied zum letzten Mal ? */
beq no_toggle /* Wenn bei EXOR 0, dann war es gleich */
not.b1(A1) /* Toggle Quick-Flag */
no_toggle: move.b D0,(A1) /* alten Status sichern */
tst.b 1(A1) /* Teste Quick-Flag */
beq end_mouse /* nicht gesetzt, also nichts tun */
move.b (A0),D0 /* Relativer X-Wert des Maus-Pakets */
add.b D0,(A0)+ /* Relativen X-Wert verdoppeln */
move.b (A0),D0 /* Relativer Y-Wert des Maus-Pakets */
add.b D0,(A0)+ /* Relativen Y-Wert verdoppeln */
end_mouse: movem.l (A7)+,D0-D1/A0-A1 /* Register zurückschreiben */
move.l mouse_vec,-(A7) /* durch alten Vektor springen */
rts /* Ciao */
mouse_vec: dc.l 0 /* alter Mausvektor */
kbdv_addr: dc.l 0 /* Vektor-Tabelle */
mouse_btn: dc.w 0 /* Status des Buttons */
ende:
}
}
/**************************************************/
/* Diese Routine initialisiert nur den Mausvektor */
/* und bleibt resident... */
/**************************************************/
main()
{
appl_init(); /* Wichtig für AES-Routinen */
mouse(0); /* Maus einbinden */
while(1) /* Endlos währt am längsten...*/
evnt_timer(10000,0);
}