;  IMPORTANT NOTE
;  --------------       

;   This source code is given AS IT IS to the Amiga community, following
;   a suggestion from Hans Guijt. Explicit permission is given to use it for 
;   any non-profit purpose, either in parts or as a whole, without any 
;   further permission from the author. The author himself reserves the right 
;   to use this source code at any time in the future again. For commercial 
;   use of this source code, prior written permission from the auther is 
;   required.


;   Contact electronic mail addresses:  grimm@particle.phys.ethz.ch
;                                       oliver.grimm@desy.de

;   Postal address:     Oliver Grimm
;                       Steinbecker Str. 69
;                       21244 Buchholz

;                       Germany

;                                        (Addresses as of March 1999)

	
;
; Recorder V1.4   -   Written and Copyright ©1991/1996 by Oliver Grimm
;
; Recorder speichert ankommende InputEvent-Strukturen und kann sie
; wieder abspielen.
; Damit kontinuierlich aufgezeichnet werden kann, werden zwei Puffer
; verwendet, die von der Interrupt-Routine beschrieben und vom
; Hauptprogramm in die Datei gesichert werden.
;
; V1.0 März 1991  
;       -  erste Programmversion
; V1.1 März 1996
;       -  2-Puffer-Technik, Programmüberarbeitung
;       -  Abspielen ohne Verzögerung entfernt
;       -  Speicherung der Startposition der Maus eingebaut
; V1.2 März 1996  
;       -  Programm löst sich automatisch von der Shell
; V1.3 April 1996 
;       -  Während des Abspielens sind Maus und Tastatur gesperrt
;       -  Crash beim Ende von PLAY beseitig (SendIO statt DoIO verwendet) 
;       -  Programm läßt sich nicht mehr mehrmals starten
;       -  Start/Stop Sequenz wird gefiltert
;       -  Argumentauswertung mit ReadArgs
;       -  NOWAIT, TERSE und PRIORITY Optionen
;       -  Event-interne Zeit zum Timing verwendet, kürzeres Eventfile
;       -  Qualifer-'d' Testsequenz eingebaut
;       -  Wartezeiten zwischen Events können mit RETURN abgebrochen 
;       -  Beim Abspeichern wird nicht mehr ein Event verschluckt
; V1.4 Mai/Juni 1996
;       -  QUIET, REPEAT, QUAL Optionen
;       -  Bei Abbruch der Aufzeichnung werden Qualifers nun
;          korrekt losgelassen (Problem mit AutoCLI behoben). 
;       -  Startup/Terminate Routinen geändert (spart viele Labels)
;


SysBase = 4

LVOFindTask = -294
LVOAddHead = -240
LVOAddPort = -354
LVORemPort = -360
LVOOpenDevice = -444
LVOCloseDevice = -450
LVOOpenLibrary = -552
LVOCloseLibrary = -414
LVOAllocMem = -198
LVOFreeMem = -210
LVOAllocEntry = -222
LVOFreeEntry = -228 
LVODoIo = -456
LVOAbortIO = -480
LVOAllocSignal = -330
LVOFreeSignal = -336
LVOSetSignal = -306
LVOWait = -318
LVOSignal = -324
LVOCopyMem = -624

LVOCreateNewProc = -498
LVOOpen = -30
LVOClose = -36
LVORead = -42
LVOWrite = -48
LVOOutput = -60
LVOSeek = -66
LVODisplayBeep = -96
LVODelay = -198
LVOReadArgs = -798
LVOFreeArgs =  -858

DefPri = 80             ; Default-Priority des Input-Handlers

MaxEvents = 1000        ; maximale Eventzahl, die gepuffert werden kann
EventLen = 22           ; Bytes pro Event

BufSize = MaxEvents*EventLen+100        ; resultierende Puffergröße
MemSize = BufSize*2                     ; zwei Puffer


CALLSYS MACRO
        move.l  SysBase,a6
        jsr     LVO\1(a6)
        ENDM

CALLDOS MACRO
        move.l  DosBase(pc),a6
        jsr     LVO\1(a6)
        ENDM

CALLINT MACRO
        move.l  IntBase(pc),a6
        jsr     LVO\1(a6)
        ENDM


;
;
; ----  Startroutine für den Prozeß  ----
;
;

 move.l  a0,RDArgs              ; Argumente aus der Shell sichern
 move.l  d0,4+RDArgs
 
 lea     DosName(pc),a1         ; dos.library Version >=36 öffnen
 move.l  #36,d0
 CALLSYS OpenLibrary
 move.l  d0,DosBase
 beq     Exit

 move.l  RDArgs(pc),a0
 cmp.b   #"?",(a0)              ; wenn Parameter '?', dann Info ausgeben
 bne.s   noInform
 lea     Information(pc),a2
 bsr     PrintText
 bra     noMain_Signal

noInform:
 lea     ProcessName(pc),a1     ; Überprüfen, ob Recorder schon einmal
 CALLSYS FindTask               ; gestartet wurde. Dann Meldung ausgeben.
 tst.l   d0
 beq.s   NotThere
 lea     ErrorMsg(pc),a2
 bsr     PrintText 
 bra     noMain_Signal
 
NotThere:
 moveq   #-1,d0                 ; Signal belegen
 CALLSYS AllocSignal
 moveq.l #1,d1                  ; aus Nummer Maske erstellen
 lsl.l   d0,d1
 move.l  d1,Main_Signal         ; Signalmaske speichern
 move.l  d0,Main_SigNum         ; Signalnummer speichern
 bmi     noMain_Signal

 sub.l   a1,a1
 CALLSYS FindTask
 move.l  d0,Main_Task
 
 lea     MemList(pc),a0         ; Speicher allokieren
 CALLSYS AllocEntry
 btst.l  #31,d0                 ; Bit 31 gesetzt --> Fehler
 bne.s   noProcMem
 move.l  d0,a4                  ; Register a4 hält MemList Adresse
 move.l  d0,SegmentMem
 
 lea     Beginn_Process(pc),a0  ; Prozeß Code kopieren
 move.l  16(a4),a1              ; Zieladresse
 move.l  20(a4),d0              ; Länge
 CALLSYS CopyMem

 move.l  16(a4),TagList+4       ; Startadresse eintragen
  
 lea     ProcessName(pc),a0     ; Prozeßname eintragen
 lea     Beginn_Process(pc),a1
 sub.l   a1,a0
 add.l   16(a4),a0
 move.l  a0,TagList+12

 CALLDOS Output         ; Ausgabehandle für Prozeß
 move.l  d0,TagList+20
 
 move.l  #TagList,d1
 CALLDOS CreateNewProc  ; Prozeß starten
 tst.l   d0
 beq.s   noNewProc

 move.l  Main_Signal(pc),d0
 CALLSYS Wait
 bra.s   noProcMem
 
noNewProc: 
 move.l  a4,a0                  ; Wenn Start fehlgeschlagen, Speicher
 CALLSYS FreeEntry              ; wieder freigeben

  
noProcMem:
 move.l  Main_SigNum(pc),d0
 CALLSYS FreeSignal
 
noMain_Signal:
 move.l  DosBase(pc),a1
 CALLSYS CloseLibrary
 
Exit:
 clr.l   d0
 rts


; --- Daten der Startroutine

Main_SigNum: dc.l 0

MemList: dc.l 0,0               ; Node der MemList
         dc.b 10,0
         dc.l 0
         dc.w 1 
         dc.l 1,End_Process-Beginn_Process      ; Type und Länge
         
TagList: dc.l $80000000+1003, 0         ; Startadresse des Prozeß 
         dc.l $80000000+1012, 0         ; Name 
         dc.l $80000000+1005, 0         ; Output Handle
         dc.l $80000000+1007, 0         ; Output Handle nicht schließen
         dc.l 0,0

Information:
   dc.b 10,$9b,"1mUsage:",$9b,"0m Recorder ",$9b,"33m(Rec|Play|App) FILENAME Options",$9b,"31m",10,10
   dc.b "You must specify one of the operating modes and a filename. ",10
   dc.b "The possible options are as follows:",10,10
   dc.b "   Quick      Play back at high speed",10
   dc.b "   NoWait     Start recording/play back immediately",10
   dc.b "   Terse      Join consecutive mousemove events together",10
   dc.b "   Quiet      Don't give out beeps",10
   dc.b "   Num n      Play back events n times",10
   dc.b "   Qual n     Set control qualifier to n (Default is Control-Left Amiga)",10
   dc.b "   Pri n      Set priority of input handler to n - THIS MIGHT BE DANGEROUS",10,10
   dc.b "Recording and play back is started and stopped by pressing the control",10
   dc.b "qualifier + 's'. Control qualifier + 'd' gives out a display beep if",10
   dc.b "Recorder is currently recording or playing back.",10
   dc.b "Hitting RETURN in play back will play the next event immediately.",10 
   dc.b "Refer to the documentation for more details.",10,10,0

ErrorMsg: dc.b "Recorder already running.",10,10,0

 even

;
;
; ---  Code des eigentlichen Prozeßes  (muß pc-relativ sein)  ---
;
; Während des Startups steht in a5 die Adresse des aktuellen Fehlerstrings.
;


Beginn_Process:
 sub.l   a1,a1                  ; eigene Taskadresse finden
 CALLSYS FindTask

 lea     Task(pc),a0            ; Task in diverse Ports eintragen
 move.l  d0,(a0)

 move.l  d0,a0
 lea     74(a0),a0              ; Programmspeicher in MemEntry eintragen         
 move.l  SegmentMem(pc),a1
 CALLSYS AddHead

 lea     ReplyPort(pc),a1       ; Reply-Port anmelden
 CALLSYS AddPort

 lea     ReplyPort(pc),a0       ; Reply-Port in IOReqest für input device
 lea     IOReq(pc),a1           ; und timer device eintragen
 move.l  a0,14(a1)
 lea     TimeReq(pc),a1
 move.l  a0,14(a1)


 lea     DosName(pc),a1         ; Dos-Library öffnen
 moveq   #0,d0
 CALLSYS OpenLibrary
 lea     DosBase(pc),a0
 move.l  d0,(a0)
 beq     noDos

 lea     Titel(pc),a2           ; Title ausgeben
 bsr     PrintText

 lea     Error11(pc),a5
 lea     IntName(pc),a1         ; IntuitionLibrary öffnen
 moveq   #0,d0
 CALLSYS OpenLibrary
 lea     IntBase(pc),a0
 move.l  d0,(a0)
 beq     StartupError

 lea     Error5(pc),a5
 lea     IOReq(pc),a1           ; input.device öffnen
 moveq   #0,d0
 moveq   #0,d1
 lea     InputName(pc),a0
 CALLSYS OpenDevice
 lea     InputErr(pc),a0
 move.l  d0,(a0)
 bmi     StartupError

 lea     Error9(pc),a5
 lea     TimeReq(pc),a1         ; timer.device öffnen
 moveq   #0,d0
 moveq   #0,d1
 lea     TimerName(pc),a0
 CALLSYS OpenDevice
 lea     TimerErr(pc),a0
 move.l  d0,(a0)
 bmi     StartupError

 lea     Error1(pc),a5 
 lea     Template(pc),a0        ; Argumente parsen
 move.l  a0,d1
 lea     Args(pc),a0
 move.l  a0,d2
 lea     RDArgs(pc),a0
 move.l  a0,d3
 CALLDOS ReadArgs
 lea     ArgHandle(pc),a0
 move.l  d0,(a0)
 beq     StartupError

 lea     Mode(pc),a0
 move.l  4+Args(pc),d0
 beq.s   noRec
 move.b  #-1,(a0)               ; Aufnehmen
noRec:
 move.l  8+Args(pc),d0
 beq.s   noPlay
 move.b  #1,(a0)                ; Abspielen
noPlay:
 move.l  12+Args(pc),d0
 beq.s   noApp
 move.b  #1,(a0)                ; Anhängen
 lea     Append(pc),a1
 move.b  #1,(a1)
noApp:
 tst.b   (a0)                   ; wenn keine Modus, dann Fehler
 beq     StartupError

noSpeed:
 lea     Error8(pc),a5
 move.b  #DefPri,d0             ; Priorität des InputHandlers
 move.l  16+Args(pc),a0         
 move.l  a0,d1
 beq.s   DefaultPri
 move.l  (a0),d0
 cmp.l   #127,d0
 bgt     StartupError
 cmp.l   #-128,d0
 blt     StartupError
DefaultPri:
 lea     9+Interrupt(pc),a0
 move.b  d0,(a0) 
 
 lea     Error2(pc),a5
 move.l  #MemSize,d0     ; Speicher für Events besorgen
 move.l  #65536,d1       ; Speicher löschen
 CALLSYS AllocMem
 lea     Memory(pc),a0
 move.l  d0,(a0)
 beq     StartupError

 lea     MemPointer(pc),a0
 move.l  d0,(a0)                ; Zeiger für Interrupt-Routine
 lea     CurrentMem(pc),a0
 move.l  d0,(a0)
 add.l   #BufSize,d0
 lea     SecondMem(pc),a0
 move.l  d0,(a0)

 lea     Error3(pc),a5 
 moveq   #-1,d0                 ; ein Signal für Interrupt belegen
 CALLSYS AllocSignal            ; Routine löscht auch Signal
 moveq.l #1,d1                  ; aus Nummer Maske erstellen
 lsl.l   d0,d1
 lea     Signal(pc),a0
 move.l  d1,(a0)                ; Signalmaske speichern
 lea     SigNum(pc),a0
 move.l  d0,(a0)                ; Signalnummer speichern
 bmi     StartupError

 move.l  40+Args(pc),a0         ; Qualifiers gesetzt ?
 move.l  a0,d0
 beq.s   SetNum
 move.l  (a0),d0
 and.w   #65284,d0              ; nur Ctrl-Amiga-Alt-Shift Tasten erlauben
 beq.s   QualOK
 lea     Error10(pc),a2
 bsr     PrintText
 bra     noFile
QualOK:
 lea     Qualifier(pc),a1       ; neuen Qualifier eintragen
 move.w  2(a0),(a1)
  
SetNum:
 move.l  36+Args(pc),a0         ; REPEAT Option verwendet ?
 move.l  a0,d0
 beq.s   OpenFile
 move.l  (a0),d0                ; gegeben Anzahl > 0 ?
 bmi.s   OpenFile
 beq.s   OpenFile
 lea     Repeat(pc),a1          ; dann eintragen
 move.l  d0,(a1)
 
OpenFile:
 lea     Error4(pc),a5
 move.l  Args(pc),d1    ; Datei öffnen  
 move.l  #1006,d2
 move.b  Mode(pc),d0    ; 1006 = Write / 1005 = Read
 bmi.s   Write
 move.l  #1005,d2
Write:
 CALLDOS Open
 lea     FileID(pc),a0
 move.l  d0,(a0)         ; Filehandle sichern
 beq.s   StartupError

 lea     Interrupt(pc),a1
 lea     InputHandler(pc),a0
 move.l  a0,18(a1)
 lea     ProcessName(pc),a0     ; Interrupt-Name eintragen
 move.l  a0,10(a1)

 lea     Error7(pc),a5
 lea     IOReq(pc),a1        ; Input-Handler installieren
 move.w  #9,28(a1)           ; Kommando 'AddHandler'
 lea     Interrupt(pc),a0
 move.l  a0,40(a1)
 CALLSYS DoIo
 lea     HandlerErr(pc),a0
 move.l  d0,(a0)
 bne     StartupError


 lea     StartInfo(pc),a2     ; Bedienungs-Info ausgeben
 move.l  24+Args(pc),d0
 beq.s   QuickText
 lea     StartInfo2(pc),a2
QuickText:
 bsr     PrintText
 move.b  Mode(pc),d0
 bmi     Record
 bra     Play

 
 ; Programm beenden

StartupError:
 move.l  a5,a2          ; Fehlertext ausgeben
 bsr     PrintText
 
Terminate:
 move.l  HandlerErr(pc),d0
 bne.s   noHandler
 lea     IOReq(pc),a1        ; Input-Handler entfernen
 move.w  #10,28(a1)          ; Kommando 'RemHandler'
 lea     Interrupt(pc),a0
 move.l  a0,40(a1)
 CALLSYS DoIo

noHandler:
 move.l  FileID(pc),d1   ; Datei schließen
 beq.s   noFile
 CALLDOS Close

noFile:
 move.l  SigNum(pc),d0
 bmi.s   noSignal
 CALLSYS FreeSignal

noSignal:
 move.l  Memory(pc),d0
 beq.s   noMem
 move.l  d0,a1
 move.l  #MemSize,d0
 CALLSYS FreeMem

noMem:
 move.l  TimerErr(pc),d0        ; Rückgabewert von OpenDevice() ist ungleich
 bne.s   noTimer                ; Null bei Fehler
 lea     TimeReq(pc),a1
 CALLSYS CloseDevice

noTimer:
 move.l  InputErr(pc),d0
 bne.s   noInput
 lea     IOReq(pc),a1
 CALLSYS CloseDevice

noInput:
 move.l  IntBase(pc),d0
 beq.s   noInt
 move.l  d0,a1
 CALLSYS CloseLibrary

noInt:
 move.l  DosBase(pc),d0
 beq.s   noDos
 move.l  d0,a1
 CALLSYS CloseLibrary

 move.l  ArgHandle(pc),d1       ; ArgHandle=0 ist ungefährlich
 CALLDOS FreeArgs

noDos:
 lea     ReplyPort(pc),a1
 CALLSYS RemPort

 bsr     SignalTask
 rts


 ;
 ; ---- Record InputEvents ----
 ;

Record:
 move.l  FileID(pc),d1          ; Kopfeintrag in Datei schreiben
 lea     FileHeader(pc),a0
 move.l  a0,d2       
 moveq.l #36,d3                 ; Länge des Headers           
 CALLDOS Write

 lea     EventBuf(pc),a0
 clr.l   (a0)
 
 bsr     SignalTask          ; Startup Routine kann beenden
 
RecordMore:                  ; Einsprung für Append
 lea     SaveNum(pc),a0
 clr.l   (a0)
 
 move.l  Signal(pc),d0       ; auf Signal warten
 CALLSYS Wait

 move.l  FileID(pc),d1       ; Eventliste abspeichern
 move.l  SecondMem(pc),d2
 move.l  SaveNum(pc),d3      ; Länge der Eventliste ermitteln
 mulu    #EventLen,d3
 CALLDOS Write

 move.b  RunFlag(pc),d0      ; wurde Aufzeichnung gestopped ?
 cmp.b   #1,d0
 beq.s   RecordMore
 
 move.l  FileID(pc),d1       ; UnPress-Events für die Qualifier schreiben
 lea     QuitEvent(pc),a0    ; Dieses verhindert Probleme mit z.B. AutoCLI
 move.l  a0,d2
 move.l  #7*22,d3            ; 7 Events
 CALLDOS Write
 lea     EventNum(pc),a0
 addq.l  #7,(a0)
   
 move.l  FileID(pc),d1       ; Anzahl der Events in die ersten 8 Bytes
 moveq.l #0,d2               ; der Datei schreiben
 move.l  #-1,d3
 CALLDOS Seek

 move.l  FileID(pc),d1
 lea     EventNum(pc),a0
 move.l  a0,d2
 moveq.l #4,d3
 CALLDOS Write
          
 bra     Terminate


 ;
 ; ---- Play InputEvents ---
 ;

Play:
 move.l  FileID(pc),d1          ; 36 Byte File-Header einlesen
 move.l  Memory(pc),d2
 moveq.l #36,d3
 CALLDOS Read

 move.l  Memory(pc),a2          ; Anzahl Events
 lea     EventNum(pc),a0
 move.l  (a2)+,(a0)
 lea     EventBuf(pc),a0
 move.l  EventNum(pc),(a0)
 beq     Terminate
 
 lea     4+FileHeader(pc),a1    ; Erkennungsstring vergleichen
 moveq   #31,d0
Compare:
 cmpm.b  (a2)+,(a1)+
 bne.s   Failed
 dbra    d0,Compare
 bra.s   HeaderOK

Failed:                         ; Datei ist keine InputEvent Datei
 lea     Error6(pc),a2
 bsr     PrintText
 bra     Terminate

HeaderOK:
 bsr     SignalTask             ; Startup Routine kann nun beenden

WaitForPlay: 
 move.l  Signal(pc),d0          ; auf dieses Signal warten (hier:Start)
 CALLSYS Wait

BigPlayLoop:
 move.l  EventBuf(pc),d4
 cmp.l   #2*MaxEvents,d4
 bls.s   NumOK
 move.l  #2*MaxEvents,d4
 
NumOK:
 lea     EventBuf(pc),a0
 sub.l   d4,(a0)
 
 move.l  FileID(pc),d1          ; Nun die eigentlichen Events lesen
 move.l  Memory(pc),d2
 move.l  d4,d3                  ; zu ladende Eventanzahl
 mulu    #EventLen,d3
 CALLDOS Read

 move.l  Memory(pc),a2
 subq.l  #1,d4
  
PlayLoop:
 move.l  20+Args(pc),d0         ; Quick Option gewählt ?
 bne.s   noWait
 move.l  LastTime(pc),d0        ; Ist dies das erste Event ?
 beq.s   noWait
 move.l  14(a2),d1              ; falls Event keine Zeitinfo hat
 beq.s   noWait

 move.l  18(a2),d2
 sub.l   LastTime(pc),d1
 sub.l   4+LastTime(pc),d2
 bpl.s   Wait
 subq.l  #1,d1
 add.l   #1000000,d2
Wait:
 lea     TimeReq(pc),a1
 move.w  #9,28(a1)
 cmp.l   #315360000,d1          ; maximale Wartezeit: 10 Jahre
 bhi.s   noWait                 ; Verhindert deadlock - SEHR lange Wartezeit 
 move.l  d1,32(a1)
 move.l  d2,36(a1)
 CALLSYS DoIo

noWait:
 lea     LastTime(pc),a0        ; Vergleichszeit setzen
 move.l  14(a2),(a0)
 move.l  18(a2),4(a0)

 lea     IOReq(pc),a1        ; Input-Event abschicken
 move.w  #11,28(a1)          ; Kommando 'WriteEvent'
 clr.b   30(a1)
 move.l  #22,36(a1)          ; Länge der Event Struktur
 move.l  a2,40(a1)           ; Adresse der Event Struktur
 CALLSYS DoIo

 move.b  RunFlag(pc),d0      ; auf vorzeitigen Abbruch testen
 beq     Terminate

 lea     EventLen(a2),a2     ; nächstes Event
 dbra    d4,PlayLoop

 move.l  EventBuf(pc),d0     ; Noch mehr Events nachzuladen ?
 bne     BigPlayLoop
  
 bsr     Beep                ; Bildschirme blinken lassen

 move.b  Append(pc),d0       ; Append aktiviert ?
 bne.s   AppendMore
 
 lea     Repeat(pc),a0       ; Abspielen wiederholen ? (REPEAT Option)
 subq.l  #1,(a0)
 beq     Terminate

 move.l  FileID(pc),d1       ; Dateizeiger wieder auf erstes Event setzen
 moveq.l #36,d2
 move.l  #-1,d3
 CALLDOS Seek
 
 lea     EventBuf(pc),a0     ; Eventzahl neu setzen
 move.l  EventNum(pc),(a0)

 lea     RunFlag(pc),a0      ; in der Pause Eingaben ermöglichen
 move.b  #-1,(a0)
 bra     WaitForPlay         ; Wiederholen
  
 ; Verbindungsroutine von Play und Record für Append

AppendMore:
 lea     Mode(pc),a0
 move.b  #-1,(a0)        ; Mode von Append auf Record ändern
 
 moveq.l #0,d0           ; Signal für Record wieder löschen
 move.l  Signal(pc),d1       
 CALLSYS SetSignal

 lea     RunFlag(pc),a0
 move.b  #-1,(a0)
 bra     RecordMore


 ;
 ; Interrupt-Routine, die jedes InputEvent erhält (Adresse in a0),
 ; sofern es keine Handler mit höherer Priorität gibt
 ; In der äußeren Schleife werden mittels ie_NextEvent verknüfte Events
 ; abgearbeitet.
 ;
 
InputHandler:
 movem.l d1/a0-a3/a5/a6,-(sp)

 move.b  RunFlag(pc),d0 ; wenn beendet, Neustart nicht erlauben
 beq     IntEnd 
 move.l  a0,a5
 
OuterLoop:
 move.l  a5,a0
 
 move.w  #$22,d1        ; Qualifer + 'd' gedrückt ?
 bsr     CheckCtrl
 tst.w   d0
 beq.s   noBlink
 move.b  RunFlag(pc),d0 ; Dann Bildschirm blinken wenn am Laufen
 bmi.s   noBlink
 bsr     Beep           ; Bildschirm blinken (auch wenn QUIET Option)
 bra     EndLoop
 
noBlink: 
 move.b  RunFlag(pc),d0         ; Aufzeichnung läuft ?
 bpl     Running                ; RunFlag kann hier nur noch +1 oder -1 sein

 move.l  24+Args(pc),d0         ; Sofort starten ?
 bne.s   ImmediateStart

 move.w  #$21,d1                ; Qualifier + 's' gedrückt ?
 bsr     CheckCtrl
 tst.w   d0
 beq     EndLoop
 bsr     Beep                   ; Startbeep

ImmediateStart:
 lea     MouseEvent(pc),a0      ; Mausposition beim Start speichern
 move.l  IntBase(pc),a6
 move.w  68(a6),12(a0)          ; y - Pos
 move.w  70(a6),10(a0)          ; x - Pos

 lea     RunFlag(pc),a3         ; RunFlag setzen
 move.b  #1,(a3)
 
 move.b  Mode(pc),d0            ; bei 'Record' Mauspositionsevent
 bmi     RecordEvent            ; speichern
 
 bsr     Bell                   ; sonst Play Routine aufwecken
 bra     EndLoop

Running:
 cmp.b   #4,4(a0)        ; nur Tastatur- und Mausevents aufzeichnen
 bhi     EndLoop         ; (Event Nummern 0, 1, 2, 4)
 cmp.b   #3,4(a0)
 beq     EndLoop

 move.w  #$21,d1                ; Qualifier + 's' gedrückt ?
 bsr     CheckCtrl
 tst.w   d0
 beq.s   ProcessEvent   
 
WakeUp:
 bsr     Beep            ; Bildschirme wieder blinken lassen

 lea     SaveNum(pc),a3
 move.l  EventBuf(pc),(a3)
 lea     SecondMem(pc),a3
 move.l  CurrentMem(pc),(a3)

 lea     RunFlag(pc),a3  
 clr.b   (a3)
 bsr     Bell           ; Task aufwecken (nur Record kümmert's)
 bra     IntEnd

ProcessEvent:
 move.b  Mode(pc),d0
 bmi.s   RecordEvent

 move.l  Memory(pc),a1          ; Bei Play sollen keine externen Maus- und
 cmp.l   a1,a0                  ; Tastaturevents zugelassen sein. Da aber
 bcs.s   ClearEvent             ; auch die eingespielten Events hier durch-
 add.l   #MemSize,a1            ; laufen, werden diese an ihrer Adresse
 cmp.l   a1,a0                  ; erkannt, die im allokierten Bereich
 bge.s   ClearEvent             ; liegt, und natürlich nicht gelöscht.
 bra     EndLoop
ClearEvent: 
 cmp.b   #1,4(a0)               ; falls Return gedrückt wurde, soll das
 bne.s   noReturn               ; nächste Event sofort gespielt werden
 cmp.w   #$44,6(a0)
 bne.s   noReturn
 clr.b   4(a0)
 lea     TimeReq(pc),a1
 CALLSYS AbortIO                ; Dazu wird Warteauftrag abgebrochen
 bra     EndLoop
noReturn: 
 clr.b   4(a0) 
 bra     EndLoop
 
RecordEvent:
 move.l  28+Args(pc),d0 ; sollen Mausevents zusammengefaßt werden ?
 beq.s   noPrev
 
 cmp.b   #2,4(a0)
 bne.s   noMouse        ; Wenn Event RAWMOUSE, IEQUALIFIER = RELATIVEMOUSE,
 move.w  8(a0),d0       ; dann nur Anfangs- und Endposition speichern
 bclr    #15,d0
 beq.s   noMouse
 tst.w   d0
 bne.s   noMouse
 cmp.w   #$ff,6(a0)
 bne.s   noMouse
 
 lea     RelFlag(pc),a1 ; war letztes Event von gleicher Art ?
 tst.b   (a1)
 bne     EndLoop
 move.b  #1,(a1)
 bra.s   noPrev
 
noMouse:
 lea     RelFlag(pc),a1
 tst.b   (a1)
 beq.s   noPrev
 clr.b   (a1)
 
 move.l  MemPointer(pc),a3      ; relative Bewegung nachtragen
 move.b  #4,4-EventLen(a3)      ; Typ von RAWMOUSE auf POINTERPOS ändern
 move.l  IntBase(pc),a2
 move.w  68(a2),12-EventLen(a3) ; y - Pos
 move.w  70(a2),10-EventLen(a3) ; x - Pos
 clr.w   8-EventLen(a3)         ; RelativeMouse Flag löschen
 
noPrev:
 lea     EventBuf(pc),a3
 cmp.l   #MaxEvents,(a3)        ; wenn Eventpuffer voll, auf zweiten
 bcs.s   BufOK                  ; Puffer umschalten

 move.l  SaveNum(pc),d0         ; Wenn Speichern nicht beendet, abbrechen
 bne     WakeUp
 
 lea     SaveNum(pc),a3
 move.l  EventBuf(pc),(a3)      ; für Speicherroutine Anzahl sichern
 move.l  SecondMem(pc),d0
 lea     MemPointer(pc),a3
 move.l  d0,(a3)

 lea     SecondMem(pc),a3
 move.l  CurrentMem(pc),(a3)    ; Puffer vertauschen
 lea     CurrentMem(pc),a3
 move.l  d0,(a3)
 move.l  a0,a3                  ; Zeiger auf Event sichern
 bsr     Bell
 move.l  a3,a0
 lea     EventBuf(pc),a3
 clr.l   (a3)

BufOK:
 moveq   #21,d0                 ; komplettes InputEvent speichern
 move.l  MemPointer(pc),a2
CopyEvent:
 move.b  0(a0,d0.w),0(a2,d0.w)
 dbra    d0,CopyEvent

 lea     MemPointer(pc),a3
 add.l   #EventLen,(a3)
 lea     EventNum(pc),a3
 addq.l  #1,(a3)
 lea     EventBuf(pc),a3
 addq.l  #1,(a3)
 
 clr.l   (a2)           ; mögliche Verkettung der Events im Puffer löschen

EndLoop:
 move.l  (a5),a5        ; Zeiger auf nächstes Event, wenn vorhanden
 move.l  a5,d0
 bne     OuterLoop
 
IntEnd:
 movem.l (sp)+,d1/a0-a3/a5/a6
 move.l  a0,d0
 rts


; Überprüfen, ob Control Qualifier und Taste Nr. d1 gedrückt/losgelassen.
; Event dann löschen, und wenn losgelassen d0=TRUE (Word)

CheckCtrl:
 cmp.b   #1,4(a0)       ; RAWKEY ?
 bne.s   noCtrl
 move.w  8(a0),d0
 and.w   #$ff,d0
 cmp.w   Qualifier(pc),d0
 bne.s   noCtrl
 cmp.w   6(a0),d1       ; Tastencode d1 gedrückt ?
 bne.s   noCtrl
 
 moveq   #1,d0          ; dann Flag setzen
 clr.b   4(a0)
 rts

noCtrl:
 clr.w   d0
 rts
 
 ; Task aufwecken durch Signal

Bell:
 move.l  Task(pc),a1    ; Task aufwecken
 move.l  Signal(pc),d0
 CALLSYS Signal
 rts

 ; Wenn nicht Option QUIET Bilschschirm blinken lassen

Beep:
 move.l  32+Args(pc),d0
 bne.s   noBeep 
 sub.l   a0,a0
 CALLINT DisplayBeep
noBeep:
 rts
 
 ;
 ; ---  Text ins CLI ausgeben (a2: Adresse des Nullterminieten Strings)  ---
 ;
 
PrintText:
 CALLDOS Output         ; Ausgabekanal ermitteln
 move.l  d0,d1
 move.l  a2,a0          ; Länge des Strings ermitteln
 moveq   #0,d3
Length:
 tst.b   (a0)+
 beq.s   PrintIt
 addq.l  #1,d3
 bra.s   Length

PrintIt:
 move.l  a2,d2
 CALLDOS Write
 rts


 ;
 ; ---  Diese Routine signalisiert dem Startup-Prozeß  ---
 ;
 
SignalTask:
 move.l  Main_Task(pc),d0
 beq.s   DoNot
 move.l  d0,a1                  ; nur einmal signalisieren
 move.l  Main_Signal(pc),d0
 CALLSYS Signal
 
 lea     Main_Task(pc),a0
 clr.l   (a0)
 
DoNot:
 rts
 
 
   
 ;
 ; --- Datenbreich ---
 ;

Version: dc.b "$VER: Recorder 1.4 (21.6.1996)",0

InputName: dc.b "input.device",0
TimerName: dc.b "timer.device",0

DosName: dc.b "dos.library",0
IntName: dc.b "intuition.library",0

ProcessName: dc.b "Recorder",0

RelFlag: dc.b 0         ; TRUE, wenn RAWMOUSE Events aufgezeichnet
RunFlag: dc.b -1        ; -1: noch nicht gestartet, 0: beendet, 1: läuft

Mode: dc.b 0            ; -1: Record, 1: Play
Append: dc.b 0          ; TRUE, wenn Append aktiviert

 ; In den ersten 4 Bytes des Fileheaders wird die Anzahl der Events
 ; gespeichert (Länge des Headers: 36 Bytes). VERxx gibt Formatversion an.

FileHeader: dc.b "----$VER02$ Recorder InputEvent File"

Titel: dc.b 10,$9b,"1;33mRecorder V1.4",$9b,"0;33m by Oliver Grimm",10
       dc.b "Written in 1991/1996  -  All rights reserved.",$9b,"0;31;40m",10,10,0   

StartInfo:  dc.b "Startup OK. Waiting for start control sequence.",10,10,0
StartInfo2:  dc.b "Startup OK. Use stop control sequence to abort.",10,10,0

Error1:  dc.b "Error: Invalid command line   -   Type 'Recorder ?' for help",10,10,0
Error2:  dc.b "Error: Insufficient free memory",10,10,0
Error3:  dc.b "Error: No free signal",10,10,0
Error4:  dc.b "Error: Unable to open file",10,10,0
Error5:  dc.b "Error: Unable to open input device",10,10,0
Error6:  dc.b "Error: Specified file is not a valid RECORDER file",10,10,0
Error7:  dc.b "Error: Unable to install input handler",10,10,0
Error8:  dc.b "Error: Illegal priority",10,10,0
Error9:  dc.b "Error: Unable to open timer device",10,10,0
Error10:  dc.b "Error: Illegal qualifier",10,10,0
Error11: dc.b "Error: Unable to open intuition library",10,10,0


Template: dc.b "File/A,R=Rec/S/K,P=Play/S/K,A=App/S/K,Pri/N/K,Q=Quick/S/K,N=NoWait/S/K,T=Terse/S/K,Quiet/S/K,Num/N/K,Qual/N/K",0

 even

Args: dc.l 0,0,0,0,0,0,0,0,0,0,0

SegmentMem: dc.l 0      ; Adresse der MemList von Startroutine

Main_Signal: dc.l 0
Main_Task: dc.l 0

OldExceptCode: dc.l 0

DosBase: dc.l 0
IntBase: dc.l 0

TimerErr: dc.l 1        ; wird Null bei erfolgreichem öffnen
InputErr: dc.l 1
HandlerErr: dc.l 1

Memory: dc.l 0          ; Basisadresse des angeforderten Speichers
FileID: dc.l 0

SigNum: dc.l 0          ; Nummer der Signals
Signal: dc.l 0          ; Maske des Signals (Bit SigNum gesetzt)

MemPointer: dc.l 0      ; aktuelle Speicherposition
CurrentMem: dc.l 0      ; Basisadresse des aktuellen Puffers
SecondMem: dc.l 0       ; Basisadresse des zweiten Puffers


EventNum: dc.l 0        ; totale Anzahl der InputEvent-Strukturen
EventBuf: dc.l 0        ; Anzahl im aktuellen Puffer
SaveNum:  dc.l 0        ; Anzahl zu speichernder Events

StartPos: dc.w 0,0      ; Start-Mausposition wenn RAWMOUSE Event
CurrentPos: dc.w 0,0    ; aktuelle Mausposition

Qualifier: dc.w $8+$40  ; Control-Sequence Qualifier (Default CTRL-LAmiga)
Repeat: dc.l 1          ; Anzahl der Durchläufe

LastTime: dc.l 0,0      ; Zeitpunkt des letzten Events

ArgHandle: dc.l 0
RDArgs: dc.l 0,0,0      ; Struktur für ReadArgs
        dc.l 0,0,0,0,0
        
IOReq: ds.b 48          ; IOStdRequest Stuktur
TimeReq: ds.b 40        ; TimeRequest Struktur

MouseEvent: dc.l 0      ; Eventstruktur zum Positionieren der Maus
            dc.b 4,0
            dc.w 0,0,0,0
            dc.l 0,0,0

QuitEvent:  dc.l 0
            dc.b 1,0
            dc.w $63,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $60,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $64,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $66,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $67,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $65,$8000
            dc.l 0,0,0,0,0
            dc.b 1,0
            dc.w $61,$8000
            dc.l 0,0,0,0
            
ReplyPort: dc.l 0,0             ; nächste, vorherige Node
           dc.b 4,0             ; Type, Priorität
           dc.l 0               ; Name
           dc.b 0,0             ; Flags, Signalbit
Task:      dc.l 0               ; Signaltask
           dc.l 0,0,0           ; Listenkopf
           dc.b 0,0

Interrupt:
 dc.l 0,0
 dc.b 0,80      ; Priorität wird Nachgetragen
 dc.l 0         ; Name wird nachgetragen
 dc.l 0,0       ; Adresse des InputHandler wird nachgetragen

End_Process: dc.w 0

 END
