Program InputHandlerDemo;

{
 Dieses Programm ist etwas für Spezialisten und demonstriert, daß man
 sogar so systemnahe Dinge wie Input-Event-Handler in Pascal schreiben
 kann.

 Der vom Programm installierte Input-Handler "vertauscht" einige
 Tasten mit ihren jeweiligen Nachbarn, läßt bei anderen Tasten den
 Bildschirm häßlich aufblitzen und sperrt wieder andere Tasten ganz.
 Als einzige nützliche Funktion ermöglicht der Handler es, mit der
 Help-Taste das gerade aktive Fenster nach vorn zu holen.

 Währenddessen versetzt sich das Programm selbst in den Waiting State
 (verbraucht also keine Rechenzeit), bis es vom Handler ein Signal
 erhält (das jener beim Empfang der Tastenkombination "Shift links -
 Shift rechts - Escape" sendet). Dann entfernt es den Handler wieder.

 Alternativ wäre vorstellbar, daß der Handler dem Hauptprogramm
 nicht nur ein Signal, sondern bei bestimmten Events eine Message
 an einen Messageport schickt. Dann kann man ein Utility schreiben,
 daß bei gewissen Tastenkombinationen bestimmten Operationen
 ausführt, und zwar unabhängig davon, welches Window beim Drücken
 dieser Tasten gerade aktiv ist...

 Ein Handler sollte übrigens möglichst keine Pascal-Prozeduren oder
 -Funktionen benutzen, denn das Laufzeitsystem von Kickpascal ist
 nicht als "Shared Library" ausgelegt, d. h. es kann wegen gewissen
 internen statischen Datenstrukturen nicht von zwei parallel laufenden
 Prozessen - z. B. einem Task und einem Input-Handler - gleichzeitig
 benutzt werden. Ein Beispiel: Wenn man mit "Write" eine Zahl ausgibt,
 verwendet die entsprechende Funktion der Laufzeitbibliothek einen
 bestimmten Pufferspeicher, um die Zahl in eine Zeichenfolge zu
 wandeln. Wenn nun das Hauptprogramm vom Handler unterbrochen wird,
 während es gerade eine Zahl ausgibt, und der Handler ebenfalls eine
 Zahl schreiben will, gibt es ein Chaos. Solche Konflikte dürften
 zwar in der Regel nicht zu einem Guru führen, aber die Programme
 werden dadurch instabil.
}

Uses ExecIO, Intuition;

{$opt b-;
  incl "exec/interrupts.h",
       "devices/input.h",
       "devices/inputevent.h",
       "intuition/intuitionbase.h" }

Type CodeTyp = Array[1..15] of Word;

Var port        : p_MsgPort;    { Port zur Kommunikation mit dem Device }
    ioreq       : p_IOStdReq;   { Hier reicht Standard-Struktur }
    inter       : Interrupt;    { Interrupt-Struktur zum Handler }
    err         : integer;      { Fehlernummern der Device-Operationen }
    ThisTask    : p_Task;       { Zeiger auf diesen Task }
    Sig, SigBit : Long;         { Signalnummern }
    _paslibbase : Byte; IMPORT; { Pascal-interne Adresse }
    InterfaceCode : Codetyp;    { Speicher für kurze Assembler-Routine }


{$opt b-,s- : Handler darf Stack nicht prüfen, da er sonst fälschlich
  einen Stacküberlauf feststellen und aussteigen würde - GURU! }

Function Handler (data: Ptr; event: p_InputEvent) : p_InputEvent;

  { Diese Funktion wird (mit Umweg über den Assembler-Teil) bei jedem
    Input-Event aufgerufen. Als Parameter erhält sie einen Zeiger auf
    eine Liste von Input-Event-Strukturen und eine Kopie des Zeigers
    "is_Data" der Interrupt-Struktur (wird hier nicht benötigt). Die
    Funktion muß als Ergebnis einen Zeiger auf eine (evtl. veränderte)
    Liste zurückgeben.
    Beachten Sie, daß Kickpascal Parameter auf dem Stack in genau
    umgekehrter Reihenfolge als C erwartet. Deshalb ist "data" hier
    der erste Parameter und kann ersatzlos gestrichen werden. }

  Const Qual = IEQUALIFIER_LSHIFT or IEQUALIFIER_RSHIFT;
        { Qualifier für Abbruch-Taste }

  Var e2    : p_InputEvent;
      ibase : ^IntuitionBase;
      err   : Boolean;

  Begin
    e2 := event;
    ibase := IntBase;

    While e2 <> Nil Do
      { Liste durchlaufen }
      Begin

        If (e2^.ie_Class and IECLASS_RAWKEY) <> 0 Then
          Case e2^.ie_Code and $7f of
            $00..$3F : e2^.ie_Code := e2^.ie_Code xor 1;
                       { Tasten vertauschen }
            $40..$44 : DisplayBeep(ibase^.FirstScreen);
                       { Screen blitzen lassen }
            $45      : If (e2^.ie_Qualifier and Qual) = Qual Then
                         { Ende-Signal abschicken: }
                         Signal(ThisTask, 1 shl SigBit);
            $46..$5E : e2^.ie_Class := IECLASS_NULL;
                       { Tasten-Ereignis unterdrücken }
            $5F      : err := WindowToFront (ibase^.ActiveWindow);
                       { Window nach vorn holen }
          Otherwise ;
          End;

        e2 := e2^.ie_NextEvent          { nächstes Element der Liste }
      End;

    Handler := event;            { Zeiger auf alte Liste zurückgeben }

  End;

{$opt s0}



Begin { Hauptprogramm }

  { Zunächst wird hier auf eine etwas eigenwillige Methode
    Maschinencode eingebunden: }

  InterfaceCode :=
    CodeTyp($48E7, $7F00,                    { MOVEM.L d1-d7,-(a7) }
            $48E7, $00FE,                    { MOVEM.L a0-a6,-(a7) }
            $4BF9, Addr(_paslibbase) shr 16,
                   Addr(_paslibbase)and$FFFF,{ LEA _paslibbase,a5  }
            $4EB9, Addr(Handler) shr 16,
                   Addr(Handler) and $FFFF,  { JSR Handler         }
            $4CDF, $7F00,                    { MOVEM.L (a7)+,a0-a6 }
            $4CDF, $00FE,                    { MOVEM.L (a7)+,a1-a7 }
            $4E75                            { RTS                 }
           );
  { Die Assembler-Routine macht folgendes:
    Alle Register werden sicherheitshalber auf den Stack gerettet,
    denn Kickpascal-Prozeduren tun dies nicht(!) selbst. Dies
    geschieht in zwei MOVEM-Zeilen, um zu erreichen, daß die Register
    a0 und a1 zuunterst auf dem Stack liegen und somit vom in Pascal
    geschriebenen eigentlichen Handler als Parameter übernommen werden
    (a0 zeigt auf die Event-Liste, a1 auf die hier nicht benötigten
    Daten). Kickpascal-Code benötigt in a5 einen Zeiger auf die
    "Paslibbase" (Anfangsadresse des Global Vector und Basis der
    Laufzeitbibliothek). Die Adresse kann vom Linker importiert
    werden und wird mit einer LEA-Anweisung ins Adressregister 5
    geschrieben.
    Dann wird per JSR in die Prozedur "Handler" eingesprungen.
    Zum Schluß werden die Register rekonstruiert, außer d0, das den
    Rückgabewert der Function enthält. }

  { Input-Device öffnen: }
  port := CreatePort ("Input-Device-Port", 0);
  ioreq := CreateStdIO (port);
  Open_Device ('input.device', 0, ioreq, 0);
  Writeln('Device geöffnet.');

  { Alles für die Kommunikation zwischen Handler und Hauptprogramm
    vorbereiten: }
  ThisTask := FindTask (Nil);
  SigBit := AllocSignal (-1);
  If SigBit < 0 Then Error ('Kein freies Signal!');

  { Interrupt-Struktur initialisieren: }
  With inter, is_node Do
    Begin
      is_Code := ^InterfaceCode;
      is_Data := Nil;
      ln_Name := 'Input-Handler in Kickpascal';
      ln_Pri  := 51  { Intuition hat die Priorität 50. Wir erhalten
                       also die Events, noch bevor Intuition sie zu
                       sehen bekommt. }
    End;

  { Jetzt wird's spannend: }
  ioreq^.io_Data := ^inter;
  ioreq^.io_Command := IND_ADDHANDLER;
  err := DoIO (ioreq);
  Writeln ('Handler hinzugefügt - Tastatur vermurkst.');
  Writeln ('Programm kann mit Shift-Shift-Esc abgebrochen werden.');

  { Warten, bis der Event-Handler sich meldet: }
  sig := Wait (1 shl SigBit);

  { System wieder aufräumen: }
  ioreq^.io_Data := ^inter;
  ioreq^.io_Command := IND_REMHANDLER;
  err := DoIO (ioreq);
  writeln('Handler entfernt.');

  FreeSignal (SigBit);

  Close_Device (ioreq);
  DeleteStdIO (ioreq);
  DeletePort (port)

End.

