;*; Updated on 25-Nov-91 at 8:21 AM by Michele Tonti; edit time: 0:00:25
;*************************** AMUS Program Label ******************************
; Filename: INPTMR.M68                                      Date: 11/25/91
; Category: SBR          Hash Code: 446-237-522-113      Version:
; Initials: RELI/AM      Name: MIKE SELVY
; Company: REL, Inc.                               Telephone #: 5039292704
; Related Files: INPTMR.BAS,INPTMR.DOC
; Min. Op. Sys.:                               Expertise Level:
; Special: Requires NOECHO
; Description: Routine obtains input from the job's terminal input buffer,
; timing out if a given number of seconds passes. Timing aborts if character
; from buffer is C/R or ^C.
;*****************************************************************************
;
;       NAME:  INPTMR.M68       09/11/19/MCS
;
;       AUTHOR:  Mike Selvy
;       EDIT HISTORY:
;         When   Who What
;       11/16/84 TAD Tom Dahlquist wrote TIMEIN.M68/.SBR
;       09/11/91 MCS Mike Selvy plagiarized TIMEIN.M68
;       11/21/91 MCS now timeout from last char, not start of routine
;       11/23/91 MCS handles ^C immediately instead of after timeout
;       11/24/91 MCS sets noecho and image mode
;       11/24/91 MCS if TIMEOUT>1 echo char; if TIMEOUT<1 noecho char
;
       OBJNAM  .SBR
;
;       FUNCTION:  This routine obtains input from the job's terminal
;       input buffer, timing out if a given number of seconds passes.
;       Timing aborts if the character from the input buffer is a [c/r]
;       (carriage return) or ^C.  The timeout restarts from the last
;       character received.
;
;       CALLING SEQUENCE:
;       XCALL INPTMR,BUFFER,TIMEOUT   where
;       Name    Type    Use
;       BUFFER  Any     This is where the characters are returned.  The
;                       size of this field implies the maximum # of
;                       bytes that can be returned.
;       TIMEOUT Float   This is how long to wait for input before returning
;                       to Basic.  If this number is negative, character
;                       will not be echoed back to user.  NOTE:  The # of
;                       characters received is returned in this field.  (If
;                       routine timed-out or a ^C, then -1 is returned
;                       instead of char count).
       SEARCH  SYS
       SEARCH  SYSSYM
       SEARCH  TRM
;       Map of timer queue block usage
       ASECT
TLINK:  BLKL    1                       ; used by system...-> next in q
TCOUNT: BLKL    1                       ; used by system...timer count
TADDR:  BLKL    1                       ; address of routine to execute
TJCB:   BLKL    1                       ; address of our JCB
TFLAG:  BLKL    1                       ; address of flag to set on timeout
       PSECT
       VMAJOR=1.               ; System version (BBS SYSTEM)
       VMINOR=1.               ; Program version
       VSUB=2.                 ; Tested flag (should match thru system)
       VEDIT=110.              ; (any change)

INPTMR: PHDR    -1,PV$RSM!PV$WSM,PH$REU!PH$REE
       CMPW    (A3)+,#2                ; must be two args...
       JNE     RETURN                  ; die if not.

       LEA     A3,2(A3)                ; skip arg. type (s/b string)
       MOV     (A3)+,A0                ; A0 -> buffer...
       MOV     (A3)+,D2                ; D2 = length of buffer...
       DEC     D2                      ; D2 = length of buffer - 1...

       MOVW    (A3)+,D6                ; test type of last arg...
       ANDW    #7,D6                   ; must be float...
       CMPW    D6,#4
       JNE     RETURN                  ; die of not.

       MOV     @A3,A1                  ; A0 -> float #
       FFTOL   @A1,D0                  ; D0 now contains timeout value
       MOV     D0,D5                   ; save number in D5 for later
       CMP     D0,#0                   ; timeout less than 0?
       JEQ     RETURN                  ; (timeout=0? that was dumb...)
       BPL     GRZRO                   ; if not<0 skip to GRZRO
       CLR     D0                      ; put a 0 in D0
       SUB     D5,D0                   ; sub D5 from 0, D0 now positive
GRZRO:  MOV     D2,D6                   ; D6 = length of buffer - 1...
       MOV     A0,A6                   ; A6 -> buffer...
CLEAR:  CLRB    (A6)+                   ; clear out return buffer...
       DBF     D6,CLEAR
       CLR     D3                      ; D3 used to count chars received.
;       CLR     D1                      ; D1 is char received from input buffer

       JOBIDX  A6                      ; keep address of trmdef in A5...
       MOV     JOBTRM(A6),A5
       ORW     #T$IMI,T.STS(A5)        ; set image mode  (auto-reset at exit)
       ORW     #T$ECS,T.STS(A5)        ; set noecho mode  (auto-reset at exit)
;
;       OK, set up a timer queue block, then loop looking for characters.
;       If/when input buffer (terminal) is empty, we goto sleep via a JWAIT.
;       When awakened, one of the following has happened:  a) input buffer
;       has chars. in it; or b) elapsed time has expired.  We can tell which
;       by checking a flag byte.  If timed out, check how much time since
;       last character was received.  If greater than our timeout number,
;       we return, otherwise requeue the timer block with a new value.
;
       CALL    SETQ                    ; set up timer Q block...


TIMTCK: TST     T.ICC(A5)               ; looking for characters...
       BEQ     NOPE                    ; if Y (no chars), br out of loop
TIMKBD: KBD     TIMQT                   ; (else) get char, (br TIMQT if ^C)
       CMPB    D1,#15                  ; check if <c/r>
       BEQ     TIMQT                   ; if Y, br out of TIMTCK loop
       CMP     D5,#0                   ; timeout less than 0?
       BMI     TINCHO                  ; then dont echo character
       TTY                             ; (char already in D1)
TINCHO: MOVB    D1,(A0)+                ; store it,
       INC     D3                      ; count it,
       DBF     D2,TIMTCK               ; if D2 > -1, dec D2 and loop again
TIMQT:  CALL    UNSETQ                  ; get rid of timer Q block...
       BR      STOREL                  ; and leave.

NOPE:   GTIMEI  D4                      ; D4 = current time...
NOPE2:  TST     T.ICC(A5)               ; look again, is anything now there?
       BNE     TIMKBD                  ; if N (= chars), br into TIMTCK loop
JWAIT:  JWAIT   J.TIW                   ; (else) sleepy time...
       TSTB    @A4                     ; did we time out?
       BEQ     TIMKBD                  ; if N, br into TIMTCK loop
;                                       ; (else:)
       GTIMEI  D6                      ; D6 = current time
       SUB     D4,D6                   ; D6 = time elapsed since last char
       MOV     D0,D7                   ; D7 = timeout seconds
       SUB     D6,D7                   ; D7 = difference
       BLE     TIMOUT                  ; if zero or neg, we timed out
       MOV     D0,D7                   ; RESET TIMER WITH OLD TIMEOUT
       MUL     D7,#10000.              ; (else) requeue block with
       MOV     D7,TCOUNT(A1)           ; new count...
       CLRB    @A4                     ; clear out flag byte...
       TIMER   @A1                     ; requeue it...
       BR      NOPE2                   ; look again, then back to sleep

TIMOUT: QRET    A1                      ; get rid of Q block...
NOWAIT: TST     T.ICC(A5)               ; last look for chars
       BEQ     NOCR                    ; if Y (no chars), br to exit
       KBD     NOCR                    ; (else) get char (br NOCR if ^C)
       CMPB    D1,#15                  ; check if <c/r>
       BEQ     STOREL                  ; if Y, br to exit
       CMP     D5,#0                   ; timeout less than 0?
       BMI     NONCHO                  ; then dont echo character
       TTY                             ; (char already in D1)
NONCHO: MOVB    D1,(A0)+                ; store it,
       INC     D3                      ; count it,
       DBF     D2,NOWAIT               ; if D2 > -1, dec and last look again
NOCR:   CLR     D3                      ; clear char count and decrement
       DEC     D3                      ; return -1 if timed out
;
;       Store character count and return.
;
STOREL: MOV     @A3,A0                  ; A0 -> TIMEOUT variable
       FLTOF   D3,@A0                  ; store char. count in TIMEOUT
RETURN: RTN                             ; exit back to basic
;
;       Set up a timer Q block and start the clock ticking.....
;
SETQ:   QGET    A1                      ; get a system Q block...
       BNE     DIEQ                    ; die if none available...
       JOBIDX  A6                      ; A6 -> our JCB...
       MOV     A6,TJCB(A1)
       MOV     D0,D7
       MUL     D7,#10000.              ; convert secs to timer ticks...
       MOV     D7,TCOUNT(A1)
       LEA     A6,WAKEME               ; A6 -> timer exit routine...
       MOV     A6,TADDR(A1)
       MOV     A4,TFLAG(A1)            ; A4 -> flag byte (in BASIC area)
       CLRB    @A4                     ; clear flag byte...
       TIMER   @A1                     ; start timer...
       RTN
DIEQ:   EXIT
;
;       Come here to get rid of timer Q entry.  This is complicated due to
;       three possibilities:  1) we don't find our entry in the timer Q--don't
;       do anything; 2) we find it and it is the first entry--we stick a
;       substitute exit routine into the Q block itself which just returns
;       the block to the system when called; 3) we find it and it is not the
;       first block in the Q--we remove it, add its time to the next block
;       in the Q, and return it to the system.
;
UNSETQ: SUPVR
       SVLOK
       MOV     TIMQUE,D6               ; A6 -> first timer Q block...
       BEQ     DONEDQ                  ; if none, all done.
       MOV     D6,A6
       CMP     A6,A1                   ; ours first?
       BEQ     CHGQ                    ; go change it if so...
DQLOOP: MOV     A6,A2                   ; A2 -> previous Q block...
       MOV     @A6,D6
       BEQ     DONEDQ                  ; if no more, all done.
       MOV     D6,A6
       CMP     A6,A1                   ; ours?
       BNE     DQLOOP                  ; if not, go get next...
       MOV     @A1,A6                  ; A6 -> next block in Q...
       MOV     A6,@A2                  ; remove us from Q, and
       BEQ     1$                      ; if there is a next block we
       MOV     TCOUNT(A1),D6
       ADD     D6,TCOUNT(A6)           ; add our timer count to its.
1$:     QRET    A1                      ; return Q block to system...
DONEDQ: LSTS    #0                      ; user mode, allow interrupts
       RTN

CHGQ:   MOV     #DEQL-1,D1              ; length of dummy exit routine...
       LEA     A6,TJCB(A1)             ; where to move it...
       MOV     A6,TADDR(A1)
       LEA     A2,DEQSTF
CQLOOP: MOVB    (A2)+,(A6)+
       DBF     D1,CQLOOP
       LSTS    #0                      ; user mode, allow interrupts
       RTN

DEQSTF: SUB     #14,A0
       QRET    A0
       RTN
DEQL    =       .-DEQSTF
;
;       This is the timer exit routine.  It sets the flag byte and returns.
;
WAKEME: MOV     TFLAG-14(A0),A6         ; A6 -> flag byte...
       SETB    @A6                     ; set flag...
       MOV     TJCB-14(A0),A0          ; A0 -> our JCB...
       JRUN    J.TIW+J.NXT             ; run job...
       RTN

       END