===============================================================
Dokumentation des Moduls HotKey V1.0      Bernd Preusing 5.5.89
===============================================================

Das Modul HotKey dient einfach gesagt dazu, ein Programm im
Hintergrund schlafen zu lassen, bis eine bestimmte Tastenkombi-
nation gedrückt wird. Dann wird eine beliebige Prozedur aufge-
rufen, nach deren Beendigung weitergeschnarcht werden kann.

Das Modul stellt drei Prozeduren und einige Konstanten zur Verfügung.
Die Hauptprozedur ist InstallKey, die beiden anderen sind eigentlich
nur 'Abfallprodukte, die man aber auch nach Belieben benutzen kann.

PROCEDURE InstallHandler(VAR Intr: Interrupt; install:BOOLEAN);
(* :Input.	Intr: eine initialisierte Exec.Interrupt-Struktur
   :Input.		.node.pri = Priorität des Input-Handlers (>50!)
   :Input.		.data = Zeiger auf Daten (nur bei Bedarf)
   :Input.		.code = Adresse des Input-Handlers
   :Input.	install: TRUE: Handler installieren, FALSE: löschen
   :Semantic.	Der Input-Handler wird in die Kette des input.device
   :Semantic.	eingetragen oder daraus gelöscht.
   :Note.	Dies wird von InstallKey benutzt; bei eigener Nutzung
   :Note.	muß eine TermProcedure den Handler entfernen!
*)

PROCEDURE AddEvents(events: InputEventPtr);
(* :Input.	events: eine (verkette) Liste von InputEvents, die
   :Input.		durch sämtliche Input-Handler läuft.
   :Semantic.	Hiermit können dem System Events vorgegaukelt werden.
   :Note.	Rückkehr erst nach kompletter Bearbeitung.
*)

PROCEDURE InstallKey(Key:CARDINAL; QSet:QualifierSet; KeyProc:PROC;
		     PortName:ADDRESS);
(* :Input.	Key: der rawKey-code der heißen Taste
   :Input.	QSet: der/die Qualifier dazu
   :Input.	KeyProc: die Prozedur, die aufgerufen werden soll
   :Input.	PortName: der Name für einen MsgPort. Falls ein gleichnamiger
   :Input.		  Port bereits existiert, wird dies und das andere
   :Input.		  Programm beendet.
   :Semantic.	Diese Prozedur kehrt niemals zurück, sie wartet auf den
   :Semantic.	HotKey und ruft dann jeweils die KeyProc auf.
*)

Während die KeyProc aktiv ist, ist der InputHandler ausgeschaltet.

Die Konstanten dienen dazu, einige gängige Tasten und Qualifier
verfügbar zu machen, ohne daß man noch von InputEvent importieren
muß.
Als Tasten sind dies: HELP, ESC und F1 (..F10)
und als Qualifier: LAMIGA, RAMIGA, LALT, RALT, ALTALT, OH,
		   LMB und RMB (Maustasten).

Ein Beispiel für die Benutzung (ein anderes ist BackText):

MODULE HotKeyTest;
FROM SYSTEM	IMPORT ADR;
FROM HotKey	IMPORT InstallKey, F1, RAMIGA;
FROM Terminal	IMPORT WriteString, WriteLn;

PROCEDURE Hot();
BEGIN
  WriteString('Rechte Amiga-Taste mit F2 gedrückt.');
  WriteString('Zum Beenden HotKeyTest nochmals starten.');
  WriteLn;
END Hot;

BEGIN
  WriteString('Drücke Amiga-rechts und F2!'); WriteLn;
  InstallKey(F1+1,RAMIGA,Hot,ADR('HotKeyTestPort1.0'));
END HotKeyTest.


Wie gesagt: 'einfach gesagt'. Das Ganze ist natürlich etwas
komplizierter. Hier werden MsgPorts, Messages, Devices und
InputHandler benutzt. Das letztere gibt mir Anlaß, endlich ein
mal mit den Gerüchten und falschen Bemerkungen um diese ominösen
InputHandler aufzuräumen!

Das input.device erzeugt beim Systemstart einen Task mit der Prio-
rität 60. Dieser sendet dann laufend IORequests an timer.device,
gameport.device und keyboard.device. Sobald ein Request beantwortet
wird, wird daraus ein InputEvent erzeugt und an die InputHandler-
Kette übergeben.
Das input.device führt eine nach Prioritäten sortierte Liste der
InputHandler. In dieser Liste sind nach dem Reset nur zwei Einträge,
nämlich Intuition mit der Priorität 50 und das console.device mit
der Priorität 0.
Das Event wird nun an den ersten übergeben, indem die Register A0
mit dem Zeiger auf das InputEvent und A1 mit dem data-Zeiger aus
der Interrupt-Struktur, mit dem der InputHandler angemeldet wurde,
geladen werden und die code-Prozedur als Unterprogramm aufgerufen
wird.
Der erste InputHandler gibt nun sein Ergebnis im Register D0 zurück.
Hier gibt es nun vier mögliche Fälle:
a) er konnte mit dem Event nichts anfangen, dann gibt er das selbe
   zurück.
b) er schluckt das Event, dann gibt er NIL zurück.
   In diesem Fall werden nachfolgende Handler nicht mehr aufgerufen.
c) er verändert das Event und gibt es zurück.
d) er erzeugt aus dem übergebenen MEHERE Events und gibt sie als
   verkettete Liste zurück (InputEvent.nextEvent).
Aus d) folgt, daß außer dem Handler mit der höchsten Priorität alle
so implementiert sein müssen, daß sie eine verkette Liste bearbeiten
können. Da man aber nie weiß, ob der eigene Handler die höchste
Priorität hat, muß man immer damit rechnen.

Es gibt aber da noch die input.device-Funktion WriteEvent, mit der
EIN eigenes Event an die Handler-Kette übergeben werden kann (das
device setzt nextEvent immer auf NIL).

Aus dem eben gesagten ergibt sich nun folgendes:
a) der Handler muß reentrant sein, da er sowohl vom Task des device
   als auch von jedem anderen Task via WriteEvent aufgerufen werden
   kann. Hierzu reicht es völlig, globale Variablen nur zu LESEN.
   Bei M2Amiga muß also unbedingt die StackPrüfung abgeschaltet
   sein, andere Laufzeitfehler führen normalerweise zum Guru.
b) Der Stackbedarf der Prozedur sollte möglichst gering sein, da man
   die Stackgröße des aufrufenden Tasks nicht kennt.
c) Der Handler sollte möglichst schnell sein, da sehr viele Events
   durch die Kette laufen (z.B. Mausbewegungen)
d) er muß die Amiga-Spezifikation für Unterprogramme erfüllen, daß
   sämtliche Register außer A0,A1,D0 und D1 unverändert bleiben.
e) ein InputHandler ist KEIN Interrupt! Disable, Forbid etc. sind
   also völlig überflüssig. Die Interrupt-Struktur dient nur als
   Träger von Liste, Priorität, Code- und Datenzeiger.

Ein Beispiel für einen absolut 'sauberen' InputHandler:

(* $S- (Stackprüfung unbedingt aus, und zwar VOR der Prozedur!) *)
PROCEDURE InputHandler(events{A0}: InputEventPtr;
		       keys{A1}  : CodeRecPtr (* data nur bei Bedarf *)
		       ): InputEventPtr;
VAR OldEvents: InputEventPtr;
BEGIN
  (* Alle Register auf den Stack, A4 und A5 übernimmt der Compiler *)
  (* Es werden wahrscheinlich nicht alle Register verändert, aber  *)
  (* sicher ist sicher! *)
  INLINE(48E7H,0011111100110010L); (* MOVEM.L D2-D7,A2-A3/A6,-(A7) *)
  OldEvents:=events; (* Hierzu hätte ich auch D0 mitretten können. *)
  WHILE events # NIL DO (* WITH wäre hier Unsinn, da in Register! *)
    IF .. AND ... THEN (* wartet nur auf etwas Bestimmtes *)
       (* Bei Prozeduraufrufen werden A0 und A1 automatisch gerettet. *)
       (* Aber bitte keine Prozedur, die Stackcheck macht! *)
       (* Hier darf man alles machen, was ein Task darf. *)
       ....
       events^.class:=null; (* einfach ungültig machen *)
       .... (* oder z.B. OldEvents verändern, neu verketten, etc.. *)
       ....
    END;
    events:=events^.nextEvent;
  END;
  (* Gerettete Register wiederherstellen *)
  INLINE(4CDFH,0100110011111100L); (* MOVEM.L (A7)+,D2-D7,A2-A3/A6 *)
  RETURN OldEvents;
END InputHandler;
(* $S=  (alte Stackprüfung wiederherstellen) *)
