Andere Rechner - andere Sitten. Wenn man regelmäßig mit zwei verschiedenen Rechnersystemen arbeitet, wünscht man sich sehr bald die jeweils guten Eigenschaften des einen Rechners auch auf dem anderen. Am besten wäre ein Rechner, der alle guten Merkmale beider Systeme in sich vereint...
Neben dem Atari arbeite ich auch noch mit SUNs, auf denen das Fenstersystem X-Windows läuft. Als sehr angenehm habe ich dort die Tatsache empfunden, daß ein Fenster automatisch aktiviert wird, wenn der Mauszeiger in das Fenster fährt. Beim ST muß man in das Fenster klicken. Nun hätte mich der eine Klick nicht weiter gestört, wenn da nicht ein Programm namens 'DC Topper' übers Netz gekommen wäre, welches versprach, die soeben beschriebene Funktion auch auf dem ST zur Verfügung zu stellen. Also das Programm auf Diskette gezogen und auf dem ST gestartet... nichts passiert. Nach einem Blick in das README-File stellt sich heraus, daß es nur im GEM-Desktop funktioniert. Nun gut, dann verlassen wir eben GEMINI und probieren es mit dem originalen Desktop. Und siehe da, es funktioniert.
Da ich aber Programme, die genau mit dem Programm zusammenarbeiten, das ich nicht benutze, ziemlich unnütz finde, landete es nach sehr kurzer Zeit im Papierkorb. Eigentlich schade, denn wenn es mit dem Desktop funktioniert, sollte es eigentlich auch mit anderen Programmen keine Probleme geben.
Ich habe also probiert, das Programm selbst zu schreiben und bin zu einem recht zufriedenstellenden Resultat gekommen. Die Aufgabe des Programms sollte die Simulation eines Mausklicks sein, sobald sich der Mauszeiger über einem nicht aktiven Fenster befindet. Das Programm muß nur bei GEM-Programmen aktiv sein, denn nur diese benutzen Fenster. Und da man auf die Fensterstrukturen legal nur aus GEM-Programmen zugreifen kann, wurde der Clicker ein Accessory und kein AUTO-Ordner-Programm.
Direkt aus der Aufgabenstellung ergab sich die Aufspaltung in zwei Teilprobleme:
Das erste Problem war recht schnell gelöst, da es für alle benötigten Funktionen AES-Aufrufe gibt. Zuerst muß man mit wind_get() das aktuelle Fenster in Erfahrung bringen [siehe Listing 1, Funktion click_main(). Gibt es kein Fenster, sind wir fertig. Andernfalls muß man mit wind_find() das Fenster unter dem Mauszeiger erfragen. Falls es eines gibt und es ungleich dem aktuellen Fenster ist, müssen wir es nur noch aktivieren, womit wir schon beim zweiten Problem angelangt sind. Hier gibt es schon wesentlich mehr Möglichkeiten:
Mit der Funktion Kbshift() stellt man ein, daß die linke Maustaste gedrückt ist; nach einer kleinen Pause stellt man wieder den ursprünglichen Zustand her. Das hört sich eigentlich recht gut an, hat aber den entscheidenden Nachteil, daß es überhaupt nicht funktioniert.
Mit der Funktion appl_tplay() kann man aufgenommene Ereignisse abspielen. Ich spielte das Ereignis Mausklick ab, dies wurde aber erst ausgeführt, nachdem ich auf eine Maustaste drückte. Das machte sich dadurch bemerkbar, daß nach dem Klick in ein nicht aktives Fenster dies aktiv (das ist noch normal) und gleichzeitig ein darin befindliches Objekt selektiert wurde (das passiert erst nach dem zweiten Klick). Offensichtlich war mit dieser Funktion auch nicht mehr herauszuholen.
Nach einem Blick in [1] fand ich die Funktion wind_set(). Damit kann man u. a. ein Fenster in den Vordergrund bringen. Das war eigentlich genau das, was ich wollte. Also ausprobiert... und es funktioniert. Man kann damit tatsächlich ein Fenster einer anderen Applikation manipulieren. Einen kleinen Nachteil will ich hier aber nicht verschweigen: Es gibt Programme (z. B. LASER C), die aus Geschwindigkeitsgründen nicht die standardmäßigen GEM-Slider benutzen. Diese werden bei dieser Methode leider nicht mitgezeichnet. Manch andere Programme merken sich, welches Fenster im Vordergrund ist. Wenn jemand anderes ein Fenster dieses Programmes in den Vordergrund setzt, bekommt das Programm nichts davon mit und geht weiterhin davon aus, daß das alte Fenster noch aktiv ist. Vor allem beim Verschieben kann das zu etwas unerwarteten (aber harmlosen) Effekten führen.
Die kleine Unvollkommenheit der 3. Methode kann man umgehen, wenn man mittels appljwriteO eine Message verschickt, daß das Fenster getopped wurde. Selbst bei LASER C werden die Fenster dann vollständig neu gezeichnet. Leider ist nichts und niemand perfekt, und so hat auch diese Methode ihre gewaltigen Nachteile: Bei appl_write() muß man die Applikations-ID des Empfängers angeben. Es gibt aber keine Funktion, die ermittelt, zu welcher Applikation ein Fenster gehört. Deshalb adressiere ich die Message immer an die Haupt-Applikation (ID 0). Das ist auch meistens richtig so und arbeitet sehr stabil. Falls nun aber ein Accessory-Fenster 'angeklickt' wird, geht die Meldung an das Hauptprogramm. Die Reaktionen darauf sind recht unterschiedlich:
Im Programm sind die letzten 3 Möglichkeiten verwirklicht. Mit Hilfe von #define kann man wählen, welche Version verwendet werden soll:
TOP_WINDOW
steht für Methode 3, diese ist sehr zu empfehlen.
SEND_MESSAGE
steht für Methode 4, sollte auch mal ausprobiert werden.
ASM_CLICK
steht für die Methode 5, sie ist eigentlich nur interessant, wenn Sie das Programm noch erweitern wollen. Dabei muß man überwachen, ob vor einem Fenster eine Dialogbox oder ein Menü erschienen ist, und dann keinen Klick simulieren.
In der Endlosschleife wird auf MESSAGE- und TIMER-Events gewartet. Pure C bietet hierfür die Funktion EvntMulti() an. Diese arbeitet genau wie der normale evnt_multi(), nur daß anstatt 23 Parametern ein Pointer auf die Parameterliste (Struktur EVENT) übergeben werden muß. Das ist im Hilfesystem nicht erwähnt, aber im Include-File AES.H zu finden.
Trifft eine AC_OPEN-Meldung ein, kann man in einer Alert-Box das Accessory ein- bzw. ausschalten. Der augenblickliche Zustand ist Default. Bei einer AC_CLOSE-Meldung warten wir ein wenig (WAIT_CLOSE gibt die Zeit in ms an). Diese Meldung bekommt ein Accessory immer dann, wenn ein Programm gestartet oder beendet wird. In dieser Zeit läßt der Clicker dann alle Fenster in Ruhe.
Falls ein TIMER-Ereignis eingetreten ist, prüfen wir in click_main(), ob ein Fenster in den Vordergrund zu klicken ist, und tun dies dann in der Funktion do_click(). Die Zeit zwischen zwei TIMER-Events kann man mit WAIT_LOOP festlegen. 500 ms sind meiner Meinung nach recht akzeptabel.
Das Programm wurde mit dem Pure-C-Compiler-Paket entwickelt. Achten Sie darauf, daß genau ein Makro (TOPJ/VINDOW, SEND_MESSAGE oder ASM_CLICK) definiert ist. Listing 2 ist nur für Version 5 nötig, ansonsten braucht es nicht eingetippt zu werden (dann aber auch aus der Project-Datei entfernen). Wenn Sie Turbo C benutzen, müssen Sie die Namen der Bibliotheken im Project-File entsprechend ändern. Bei anderen Compilern müssen Sie wahrscheinlich EvntMulti() durch den üblichen evnt_multi() ersetzen.
Literatur:
[1] Jankowski, Reschke, Rabich: ATARI ST Profibuch
/**********************************************/
/* */
/* Clicker.C für Clicker.ACC */
/* */
/* Simuliert einen Mausklick beim Wechsel */
/* in ein anderes Fenster */
/* */
/* V1.0 2. 7.91 Laser C */
/* V1.1 22. 8.91 Pure C */
/* V1.2 5. 1.92 ST Computer */
/* */
/* by Ulrich Mast (c) 1992 MAXON Computer */
/* */
/**********************************************/
#include <tos.h>
#include <aes.h>
/**********************************************/
#define FALSE 0
#define TRUE 1
#define WAIT_LOOP 500
#define WAIT_CLOSE 4000
#define TOP_WINDOW
#undef SEND_MESSAGE
#undef ASM_CLICK
/**********************************************/
void click_main(void);
void do_click(int);
void asm_init(void);
long asm_click(void);
/**********************************************/
int ap_id;
char *alert[] =
{
"[1][ | Auto Window Clicker | written 1992 by Uli ] [Off|On]",
"[3][ | Auto Window Clicker | ist ein Accessory! ][Quit]"
};
/**********************************************/
void
main()
{
EVENT event;
int active=TRUE;
ap_id=appl_init();
if(! app)
{
menu_register(ap_id," Clicker ");
#ifdef ASM_CLICK
asm_init();
#endif
while(TRUE)
{
event.ev_mflags=MU_MESAG|MU_TIMER;
event.ev_mtlocount=WAIT_LOOP;
event.ev_mthicount=0;
EvntMulti(&event);
if (event.ev_mwich & MU_MESAG)
{
if(event.ev_mmgpbuf[0]==AC_OPEN)
if(form_alert(active?2:1,alert[0])==2)
active=TRUE;
else
active=FALSE;
if(event.ev_mmgpbuf[0]==AC_CLOSE)
evnt_timer(WAIT_CLOSE,0);
}
if(event.ev_mwich & MU_TIMER)
if(active)
click_main();
}
}
form_alert(1,alert[1]);
appl_exit();
}
/**********************************************/
void
click_main() {
{
int wind,top;
int d;
int x,y;
wind_get(0,WF_TOP,&top,&d,&d,&d);
if(top>0)
{
evnt_button(1,0,2,&x,&y,&d,&d);
wind=wind_find(x,y);
if(wind>0)
if(wind!=top)
do_click(wind);
}
}
/**********************************************/
void
do_click(int wind)
{
#ifdef TOP_WINDOW
wind_set(wind,WF_TOP,0,0,0,0);
/* Fenster aktiv */
#endif
#ifdef SEND_MESSAGE
int msg[8];
msg[0]=WM_TOPPED; /* Message */
msg[1]=ap_id; /* Unsere ID */
msg[2]=0; /* keine Überlänge */
msg[3]=wind; /* Fenster ID */
msg[4]=0;
msg[5]=0;
msg[6]=0;
msg[7]=0;
appl_write(0,16,msg); /* -> APP 0 */
#endif
#ifdef ASM_CLICK
Supexec(asm_click);
#endif
}
Listing 1: Das Hauptprogramm
*************************************************
*
* Click.S für Clicker.ACC
*
* (c) 1992 MAXON Computer
*
************************************************
EXPORT asm_init
EXPORT asm_click
************************************************
*
* Sucht die Adresse der Maueroutine
*
asm_init:
movem.l d0-a6,-(sp)
move.w #34,-(sp) ; Kbdvbase()
trap #14
addq.l #2,sp
add.l #16,d0 ; KBDVECS.mousevec
move.l d0,mouse_vec ; -> mouse vec
pea asm_init_2 ; Supexec (asm_init_2)
move.w #38,-(sp)
trap #14
addq.l #6,sp
movem.l (sp)+,d0-a6
rts
asm_init_2:
lea mouse_adr,a0 ; Adr. der Mausroutine
move.l mouse_vec,a2 ; -> mouse adr
move.l (a2),(a0)
rts
************************************************
*
* Simuliert einen Mausklick der linke Taste
* an der aktuelle Position
*
* Aufruf durch Supexec(asm_click)
*
asm_click:
movem.l d0-a6,-(sp)
move.w sr,-(sp)
ori #$700,sr ; bitte nicht stören
move.l mouse_adr,a2 ; Taste drücken
lea druecken,a0
jsr (a2)
move.l mouse_adr,a2 ; Taste loslassen
lea loslassen,a0
jsr (a2)
move.w (sp)+,sr
movem.l (sp)+,d0-a6
rts
************************************************
DATA
druecken:
DC.B $FA,0,0 ; Mauspaket 'Drücken'
EVEN
loslassen:
DC.B $F8,0,0 ; Mauspaket 'Loslassen'
EVEN
mouse_vec:
DC.L 0 ; Vektor auf Mausroutine
mouse_adr:
DC.L 0 ; Adresse der Mausroutine
END
Listing 2: Die Assembler-Routinen
; Clicker.Prj
;
; Project-Datei für Clicker.ACC unter Pure C
;
clicker.acc ; das wollen wir haben
.S [-S] ; mit Privileged Instructions
.L [-F -S=1024] ; ohne Fastload Bit
; Stack = 1024 Bytes genügt
=
pcstart.o ; Startup-Code
clicker.c ; der C-Source
click.s ; die Assembler-Routine
; kann auch weggelassen werden
; (siehe Text)
pcstdlib.lib ; und die Bibliotheken
pctoslib.lib
pcgemlib.lib
Listing 3: Das Project-File für Pure C