****************************************************************************
*
*                        NORMALIZED KEY CODE CONVERTER
*
*                                Release 2.81
*
*---------------------------------------------------------------------------
*
*  Edition  :  53
*  Author   :  Harald Siegmund
*  Assembler:  MadMac 1.05
*  Notes    :  Definition of the symbol NKCNOGEM will suppress inclusion
*              of the GEM part, which saves about 3.5 KByte of memory.
*              The functions nkc_multi()/nkc_amulti() and the GEM parameter
*              block are not available then.
*
*              Definition of the symbol NKCOWNPB will NKCC force to use its
*              own GEM parameter blocks (they won't be exported and shared
*              with the Turbo C library). The symbol is ignored if NKCNOGEM
*              is defined.
*
*---------------------------------------------------------------------------
*
*  History  :
*
*  1989:
*     May 14: creation of file (NKCC 1.00)
*     Jun 07/15-16/19/21/Jul 11: improvements, changes, debugging
*  1990:
*     Jan 13: global functions now available in two versions:
*             parameters passed via registers (e.g. for Turbo C)
*             parameters passed via stack (underscore before label name)
*
*     May 23-24/26-28: creation of NKCC 2.00 (almost every function rewritten)
*     Jun 16/27: assembler-entry added by Gerd Knops
*     Jun 30/Jul 02: return shift key flags in nkc_multi()/amulti() as NKF?_...
*     Jul 09: nkc_kstate()
*     Aug 03: nkc_cmp()
*     Aug 18: fatal bug in nkc_kstate() fixed
*     Sep 15: nkc_timer()
*     Sep 15-17/30: own button event handler
*     Oct 03: debugging
*             nkc_vlink()/vunlink()
*             documentation syntax changed (LRef/XRef removed; Reg: changed)
*     Oct 05: debugging mouse button handler (problems with menu bar)
*     Oct 07: Control/Alternate + character always returns capital characters
*             changing key code comparism rules in nkc_cmp()
*     Oct 22: debugging nkc_cmp() (did sometimes recognize unique key codes)
*             debugging ASCII code input in nkc_amulti()
*     Dec 11/15/17: MU_XTIMER
*  1991:
*     Jan 11: don't call AES with set MU_XTIMER flag
*     Mar 31: ensure that evnt_multi() parameters are restored when being
*             corrupted by a bug in AES!!
*     Apr 01: debugging
*     Apr 13: nkc_conv() renamed to nkc_tconv()
*     Apr 13-14: nkc_gconv()
*     May 10: debugging (nk_beend())
*     May 29: debugging (double shift key compare)
*             export nkc_toupper and nkc_tolower
*     Jul 05: completing history
*     Jul 31: version number before XBRA header
*     Aug 06: " is now a deadkey!
*     Aug 07: more deadkeys...
*     Aug 22: deadkeys can now be enabled/disabled separately
*             nkc_set() changed
*             nkc_init() returns version #
*             bug fixed: nkc_cmp() corrupted high words of D3 and D4
*     Sep 07/14: nkc_cmp() improved; NKF?_RESVD now in use
*     Nov 05: adjustments to ASCII input feature of TOS 3.06
*     Nov 16: bug fixed in nkc_cmp() mechanism
*             Control key emulation
*     Dec 29: small corrections and extensions of the documentation
*             source documentation syntax now in extra file
*  1992:
*     Jan 03: changing documentation of nkc_init()
*             revising info lines in function headers
*     Jan 12: adjusting nkc_init() to its new documentation!
*             appending .b respectively .l to all instructions with a default
*              operand size which is not .w
*             changing macro XBRA_HD
*     Feb 11: adjusting button event handler to MINT
*     Feb 28: NKCOWNPB switch
*
*---------------------------------------------------------------------------
*
*  Features to come (in future versions...):
*
*  -  when receiving key codes from AES, NKCC uses the *current* state of
*     the Alternate/Control/Shift keys because the original state was lost
*     (forgotten by AES!). Wrong key codes may be the result. This will be
*     fixed some time.
*
*  -  NKCC will support additional (and more complex) deadkeys, such like
*     Arabian ones. A communication interface between NKCC and a text editor
*     will be built in for that purpose (Arabian languages' deadkeys can
*     affect existing text!).
*
*  -  when NKCC finds itself in memory (perhaps installed by an accessory),
*     interrupts and variables will be shared between both NKCCs (preventing
*     the installation of lots of interrupts and thus saving processor time).
*
****************************************************************************
*KEY _NAME "NKCC"
*END

VERSION        =     $0281                   ; NKCC's version #


*START
****************************************************************************
*                         ASSEMBLER CONTROL SECTION                        *
****************************************************************************

*KEY =NKCNOGEM
*     if defined (on the assembler's command line), this symbol will supress
*     inclusion of the GEM part of NKCC (see comment in file header)
*
*KEY =NKCOWNPB
*     if defined, NKCC will use its own GEM parameter blocks

               .include "nkcc.sh"            ; NKCC definitions
*END
*KEY _END



*START
****************************************************************************
*                                  EXPORT                                  *
****************************************************************************

                                             ; functions
               .globl   nkc_init             ; init NKCC
               .globl   nkc_exit             ; exit NKCC
               .globl   nkc_set              ; set special key flags
               .globl   nkc_conin            ; NKCC key input via GEMDOS
               .globl   nkc_cstat            ; console input status via GEMDOS
               .globl   nkc_tconv            ; TOS key code converter
               .globl   nkc_gconv            ; GEM key code converter
               .globl   nkc_kstate           ; return shift key state
               .globl   nkc_timer            ; return 200 Hz system clock
               .globl   nkc_cmp              ; compare two key codes
               .globl   nkc_vlink            ; link function to XBRA vector list
               .globl   nkc_vunlink          ; unlink function from XBRA list

               .globl   nkc_toupper          ; upper case conversion table
               .globl   nkc_tolower          ; lower case conversion table

               .if      !(^^defined NKCNOGEM)

               .globl   nkc_multi            ; NKCC multi event
               .globl   nkc_amulti           ; multi event, assembler binding

               .if      !(^^defined NKCOWNPB)

               .globl   _GemParBlk           ; TC compatible GEM param'block
               .globl   _contrl              ; sub-arrays of GPB
               .globl   _global
               .globl   _intin
               .globl   _intout
               .globl   _ptsin
               .globl   _ptsout
               .globl   _addrin
               .globl   _addrout

               .endif

               .endif
*END



****************************************************************************
*                                  EQUATES                                 *
****************************************************************************

                                             ; AES event mask bits
MU_KEYBD       =        $0001                ; keyboard
MU_BUTTON      =        $0002                ; mouse button clicks
MU_M1          =        $0004                ; mouse movement #1
MU_M2          =        $0008                ; mouse movement #2
MU_MESAG       =        $0010                ; message
MU_TIMER       =        $0020                ; timer

conterm        =        $484                 ; .B: system key flags
_hz_200        =        $4ba                 ; .L: 200 Hz system clock
_sysbase       =        $4f2                 ; .L: ^OS header



****************************************************************************
*                             ABSOLUTE SECTION                             *
****************************************************************************

               .abs                          ; button event info block

BI_BSTATE:     .ds.w    1                    ; requested button state
BI_BMASK:      .ds.w    1                    ; requested button mask
BI_REVCOND:    .ds.w    1                    ; event condition reverse flag
BI_MAXCLICKS:  .ds.w    1                    ; max # of clicks to wait for
BI_DCLTIME:    .ds.w    1                    ; AES double click time
BI_BMODE:      .ds.w    1                    ; button handler mode
BI_SBSTATE:    .ds.w    1                    ; button state at start of event
BI_PRVBSTATE:  .ds.w    1                    ; previous button state
BI_NCLICKS:    .ds.w    1                    ; # of mouse clicks
BI_DCBUT:      .ds.w    1                    ; double click button mask
BI_DCSTATE:    .ds.w    1                    ; double click button state
BI_BENDTIME:   .ds.l    1                    ; button event timer end value
BI_BEVALID:    .ds.b    1                    ; flag: button event valid
BI_CVALID:     .ds.b    1                    ; flag: button condition valid
BI_LOCK:       .ds.b    1                    ; flag: button event locked
               .even
BI:                                          ; size



****************************************************************************
*                                  MACROS                                  *
****************************************************************************

****************************************************************************
*
* LWDIVU: divide long value <da> by word value <db>, use <dt> for temp storage
*
* - the result will be stored as longword in <da>
* - <db> stays unchanged
* - <da> and <dt> must be data registers
*
****************************************************************************

               .macro   LWDIVU .db,.da,.dt

               move.l   \.da,\.dt            ; copy long-value
               clr      \.dt                 ; clear its low word
               swap     \.dt                 ; dt.w = high word of long-value
               divu     \.db,\.dt            ; high word / word-value
               swap     \.da                 ; long-value, low and high swapped
               move     \.dt,\.da            ; da.w = result of high/word-value
               swap     \.da                 ; da.w = low word of long-value
               move     \.da,\.dt            ; remainder and low-word
               divu     \.db,\.dt            ; divide by word-value
               move     \.dt,\.da            ; so we get low word of result

               .endm



****************************************************************************
*
* XBRA_HD: XBRA header for NKCC functions
*
* The following structure is stored at the current memory location:
*
* VERSION.L       NKCC's version # (actually not a part of a standard XBRA
*                 header; may be checked in future versions)
* "XBRA".L        magic longword
* "NKCC".L        NKCC's XBRA-ID
* oldvec.L        initial value of buffer for old vector content (passed as
*                 macro parameter); if not defined, the default 0 is used
*
****************************************************************************

               .macro   XBRA_HD .oldvec

               .dc.l    VERSION              ; version number
               .dc.b    "XBRA"               ; XBRA header
               .dc.b    "NKCC"               ; ID

               .if      \?{.oldvec}          ; oldvec defined?

               .dc.l    \.oldvec             ; use it

               .else                         ; else:

               .dc.l    0                    ; use default

               .endif

               .endm



****************************************************************************
*                            LOCAL TEXT SECTION                            *
****************************************************************************

               .text

****************************************************************************
*
*  nk_gci: GEMDOS console character input
*  #
*
*  This function returns when
*
*     a) either any key is pressed or
*     b) the Alternate key is released
*
*  The Alternate check can be suppressed by setting the input parameter in
*  D0.W to zero. The routine works then just as a "get key" function.
*
*  In:   R  D0.W           Alternate key check flag:
*                          zero = don't check it
*                          non-zero = do Alternate check
*  Out:  W  D0.w           key code in normalized format
*                          for details see nkc_tconv()
*        W  D1.W           flag:
*                          zero = D0.W not valid; Alternate was released
*                          non-zero = D1.W valid; Alternate still pressed
*                             (also returned when Alternate check supressed)
*        W  CCR            set according content of D1.W
*
*  Reg:  D:01234567   A:01234567   CCR
*          U***....     ****....   =D1.W
*
****************************************************************************

nk_gci:        move     d0,d3                ; Alternate check flag
               beq.s    .getkey              ; no check? just get key

               move.l   pshift,a3            ; ^shift key state

.loop:         btst.b   #3,(a3)              ; Alternate still pressed?
               beq.s    .abort               ; no->

               move     #11,-(sp)            ; Cconis: get key input status
               trap     #1                   ; Gemdos
               addq     #2,sp                ; clean stack

               tst      d0                   ; any key pressed?
               beq.s    .loop                ; no->

.getkey:       move     #7,-(sp)             ; Crawcin: get key
               trap     #1                   ; Gemdos
               addq     #2,sp                ; clean stack
               bsr      nkc_tconv            ; convert key code
               moveq.l  #-1,d1               ; flag: key code valid
               rts                           ; bye

.abort:        moveq.l  #0,d1                ; flag: key code not valid
               rts                           ; bye



****************************************************************************
*
*  nk_spec: special key code handling
*  #
*
*  nk_spec() performs the special key handling defined by <sflags> (deadkey
*  management, ASCII code input, Control key emulation).
*
*  In:   R  D0.w           key code in normalized format
*                          for details see nkc_tconv()
*        X  A0.L           ^function which gets new key codes and checks
*                          the Alternate key state
*                          For the calling conventions consult nk_gci() or
*                          nk_mci(). The function MUST NOT modify the CPU
*                          registers D5-D7 and A5-A6.
*  Out:  W  D0.w           (new) key code in normalized format
*        W  CCR            set according contents of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          U*******     *******.   =D0.w
*
****************************************************************************

nk_spec:       move.l   a0,a6                ; save ^function
               btst.b   #NKSb_ALTNUM,sflags + 3 ; ASCII input enabled?
               beq.s    .ctrlkey             ; no->


*              ASCII input

               moveq.l  #0,d6                ; reset digit counter
               moveq.l  #0,d5                ; reset ASCII code
               bra.s    .isdigit             ; check if valid key

.notvalid:     tst      d6                   ; key not valid; first one?
               beq.s    .ctrlkey             ; yes-> exit

.getkey:       move     d0,d7                ; save previous key code
               moveq.l  #-1,d0               ; check Alternate
               jsr      (a6)                 ; get key
               beq.s    .makeascii           ; Alternate released? abort loop

.isdigit:      move     d0,d1                ; key code
               and      #NKFf_NUM | NKFf_ALT,d1 ; isolate flags
               cmp      #NKFf_NUM | NKFf_ALT,d1 ; with Alternate and numeric?
               bne.s    .notvalid            ; no->

               cmp.b    #'0',d0              ; must be in range from 0 ...
               blo.s    .notvalid

               cmp.b    #'9',d0              ; ... to 9
               bhi.s    .notvalid

               mulu     #10,d5               ; new digit in ASCII code
               sub      #'0',d0              ; ASCII->number
               add      d0,d5                ; add new digit
               addq     #1,d6                ; count digits
               cmp      #3,d6                ; 3rd digit added?
               bne.s    .getkey              ; no-> next key

.makeascii:    move     d7,d0                ; previous key code
               and      #NKFf_CAPS,d0        ; isolate CapsLock flag
               move.b   d5,d0                ; add key code (ASCII)


*              Control key emulation

.ctrlkey:      btst.b   #NKSb_CTRL,sflags + 3   ; enabled?
               beq.s    .deadkey             ; no ->

               btst.l   #NKFb_CTRL,d0        ; Control flag set?
               beq.s    .deadkey             ; no ->

               btst.l   #NKFb_ALT,d0         ; Alternate flag set?
               bne.s    .deadkey             ; yes ->

               cmp.b    #$40,d0              ; check ASCII code
               blo.s    .deadkey             ; too low ->

               cmp.b    #$5f,d0              ; check again
               bhi.s    .deadkey             ; too high ->

                                             ; clear function and Control flags
               and      #~(NKFf_FUNC|NKFf_CTRL),d0
               sub.b    #$40,d0              ; new ASCII code


*              check if key = deadkey

.deadkey:      tst      d0                   ; function key?
               bmi.s    .ret                 ; yes-> no deadkey

               lea      deadtab,a0           ; ^deadkey table
               move     sflags,d4            ; deadkey flags
               beq.s    .exit                ; no deadkeys at all->

.finddead:     move     (a0)+,d2             ; get option flag mask
               beq.s    .exit                ; end of table-> no deadkey

               move     (a0)+,d1             ; get key code
               addq     #4,a0                ; skip ^match table
               and      d4,d2                ; is this deadkey enabled?
               beq.s    .finddead            ; no ->

               cmp.b    d0,d1                ; deadkey found?
               bne.s    .finddead            ; no->


*              key is dead key, get second key and merge them

               move.l   -4(a0),a5            ; ^deadkey match table
               move     d0,-(sp)             ; save key code
               clr      d0                   ; no Alternate check
               jsr      (a6)                 ; get second key
               move     sflags,-(sp)         ; save deadkey flags
               clr      sflags               ; disable deadkeys temporary
               move.l   a6,a0                ; do ASCII input/ctrl key check
               bsr      nk_spec              ; by calling myself!

               move     (sp)+,sflags         ; restore deadkey flags
               move     (sp)+,d7             ; restore first key code
               tst      d0                   ; function key?
               bmi.s    .nomatch             ; yes-> doesn't match with deadkey

.findmatch:    move     (a5)+,d1             ; merged and second key code
               beq.s    .nomatch             ; end of table->

               cmp.b    d0,d1                ; second key code found?
               bne.s    .findmatch           ; no->

               lsr      #8,d1                ; yes: merged code in .B
               and      #NKFf_CAPS,d0        ; clear all except CapsLock
               move.b   d1,d0                ; CapsLock flag + merged key
               bra.s    .exit                ; exit

.nomatch:      move     d0,prvkey            ; save second key
               st.b     prvvalid             ; set validation flag
               move     d7,d0                ; return first key

.ret:          rts                           ; bye

.exit:         tst      d0                   ; set CCR
               rts                           ; bye



               .if      !(^^defined NKCNOGEM)

****************************************************************************
*
*  nk_bioscode: convert GEM key code to GEMDOS/BIOS format
*  # R
*
*  This is a simple converter which takes key codes in AES format (two
*  words) and transforms them to the BIOS key format (one longword).
*
*  In:   R  D0.W           key code
*                          high byte = scan code, low byte = ASCII code
*        R  D1.W           shift key state flags
*                          Bit 0: right Shift
*                          Bit 1: left Shift
*                          Bit 2: Control
*                          Bit 3: Alternate
*  Out:  W  D0.L           key code in BIOS format
*                          for details see nkc_tconv()
*
*  Reg:  D:01234567   A:01234567   CCR
*          U*......     *.......    *
*
****************************************************************************

nk_bioscode:   swap     d0                   ; key code in high word
               move     d1,d0                ; shift key state
               move.l   pshift,a0            ; ^shift key state system variable
               move.b   (a0),d1              ; get it
               and      #~NKFf_CAPS,d1       ; isolate CapsLock flag
               or       d1,d0                ; merge with other shift flags
               swap     d0                   ; shift state in high word
               lsl.l    #8,d0                ; move 'em up
               lsr      #8,d0                ; now key code in BIOS format
               rts                           ; bye



****************************************************************************
*
*  nk_mci: multi event console character input
*  # U
*
*  This function returns when
*
*     a) either any key is pressed or
*     b) the Alternate key is released
*
*  The Alternate check can be suppressed by setting the input parameter in
*  D0.W to zero. The routine works then just as a "get key" function.
*
*  In:   R  D0.W           Alternate key check flag:
*                          zero = don't check it
*                          non-zero = do Alternate check
*  Out:  W  D0.w           key code in normalized format
*                          for details see nkc_tconv()
*        W  D1.W           flag:
*                          zero = D0.W not valid; Alternate was released
*                          non-zero = D1.W valid; Alternate still pressed
*                             (also returned when Alternate check supressed)
*        W  CCR            set according content of D1.W
*
*  Reg:  D:01234567   A:01234567   CCR
*          U***....     ****....   =D1.W
*
****************************************************************************

nk_mci:        move     d0,d3                ; save Alternate check flag
               move.l   pshift,a3            ; ^shift key state variable

.loop:         lea      _contrl,a0           ; ^control array
               move     #25,(a0)+            ; opcode
               move     #16,(a0)+            ; size of intin
               move     #7,(a0)+             ; size of intout
               move     #1,(a0)+             ; size of addrin
               clr      (a0)                 ; size of addrout

               lea      _intin,a0            ; ^intin array
               move     #MU_TIMER|MU_KEYBD,(a0) ; wait for timer and key strokes
               clr.l    7*4(a0)              ; waiting time: 0 ms = minimum!

               move     #200,d0              ; opcode for AES
               move.l   #aespb,d1            ; ^AES parameter block
               trap     #2                   ; call GEM

               lea      _intout,a0           ; ^intout array
               move     (a0),d0              ; event mask
               move.b   (a3),d1              ; shift key state
               and      #$f,d1               ; clear unused bits
               move     #MU_KEYBD,d2         ; did keyboard event occure?
               and      d0,d2                ; check bit
               bne.s    .keybd               ; yes->

               tst      d3                   ; do Alternate check?
               beq.s    .loop                ; no->

               btst.l   #3,d1                ; Alternate still pressed?
               bne.s    .loop                ; yes->

               moveq.l  #0,d1                ; Alternate released: abort
               rts

.keybd:        move     10(a0),d0            ; key code
               bsr      nk_bioscode          ; convert to BIOS keycode
               bsr      nkc_tconv            ; convert to normalized format
               moveq.l  #1,d1                ; key code is valid
               rts                           ; bye



****************************************************************************
*
*  nk_gemtrap: GEM trap handler
*  # S T2
*
*  This function is linked to the GEM trap vector at initialization time.
*  It waits for evnt_dclick() calls (get/set double click time) and saves
*  new click times in an own buffer.
*
*  In:   R  D0.W           opcode:
*                          115 = VDI
*                          200 = AES
*        R  A0.L           AES/VDI parameter block
*  Out:  -
*
*  Reg:  D:01234567   A:01234567   CCR
*          ..*.....     **......    *
*
****************************************************************************

               XBRA_HD                       ; XBRA header

nk_gemtrap:    cmp      #200,d0              ; AES?
               bne.s    .exit                ; no ->

               move.l   d1,a0                ; ^parameter block
               move.l   (a0),a1              ; ^contrl array
               cmp      #26,(a1)             ; opcode for evnt_dclick?
               bne.s    .exit                ; no ->

               move.l   8(a0),a1             ; ^intin array
               tst      2(a1)                ; set new time?
               beq.s    .exit                ; no ->

               move     (a1),d2              ; yes: get new time
               cmp      #4,d2                ; limit check
               bls.s    .save                ; ok: save new value

               moveq.l  #4,d2                ; out of range: use maximum

.save:         move     d2,bvar+BI_DCLTIME   ; save it

.exit:         move.l   nk_gemtrap-4(pc),-(sp)  ; old vector address
               rts                           ; jump to old routine



****************************************************************************
*
*  nk_butvec: VDI mouse button change handler
*  # I S
*
*  This function handles changes of the mouse button state. It is used
*  for NKCC's own button event handler.
*
*  In:   R  D0.W           actual state of mouse buttons
*  Out:  W  D0.W           actual state of mouse buttons (unchanged)
*
*  Reg:  D:01234567   A:01234567   CCR
*          ........     ........    *
*
****************************************************************************

               XBRA_HD  nk_bvret             ; XBRA header

* Some milliseconds after the installation of this function, the old vector
* address is not set (VDI has to return first). A mouse click during this
* time would lead to a system crash because the new handler jumps through
* this vector. This is the reason why it's initial content points to a
* RTS instruction. (I know, this is a very very improbable situation, but
* it IS possible! And that's reason enough to prepare for it and write
* some silly lines of comment about it!)

nk_butvec:     movem.l  d5-d7/a0-a1,-(sp)    ; save registers
               lea      bvar,a1              ; ^button event info block
               move     d0,d7                ; new button mask
               move     BI_BSTATE(a1),d6     ; requested button state
               eor      d6,d7                ; XOR 'em
               move     BI_BMASK(a1),d6      ; button mask
               and      d6,d7                ; isolate valid bits
               tst      BI_REVCOND(a1)       ; condition reverse flag
               bne.s    .rev                 ; reverse ->

               tst      d7                   ; 0-> all button conditions true
               seq.b    d6
               bra.s    .chkmode

.rev:          eor      d6,d7                ; invert condition flags
               cmp      d6,d7                ; at least one condition true?
               sne.b    d6

* the registers contain now:
*
* d6.b = button condition flag
* d7.w = button condition mask (bits with value 0 indicate true condition)

.chkmode:      move.b   d6,BI_CVALID(a1)     ; save flag
               move     BI_BMODE(a1),d5      ; check current mode
               beq.s    .standby             ; standby mode ->

               subq     #1,d5
               beq.s    .wait                ; wait mode ->


.hold:         tst.b    d6                   ; button condition true?
               beq.s    .reset               ; no ->

               move     #1,BI_NCLICKS(a1)    ; 1 mouse click
               move     d0,BI_SBSTATE(a1)    ; use current button state
               st.b     BI_BEVALID(a1)       ; generate mouse click
               bra.s    .exit

.reset:        clr      BI_BMODE(a1)         ; switch to standby mode
               bra.s    .exit

.standby:      tst.b    BI_BEVALID(a1)       ; old click still in buffer?
               bne.s    .exit                ; yes ->

               tst.b    d6                   ; button condition true?
               beq.s    .exit                ; no ->

               move     d0,BI_SBSTATE(a1)    ; save button state
               moveq.l  #1,d5
               move     d5,BI_BMODE(a1)      ; change to wait mode
               move     d5,BI_NCLICKS(a1)    ; init # of clicks

               lea      timetab,a0           ; ^double click time table
               move     BI_DCLTIME(a1),d5    ; double click time
               add      d5,d5                ; *4 for index
               add      d5,d5
               move.l   (a0,d5),d5           ; get double click time
               add.l    sysclock,d5          ; add current timer value
               move.l   d5,BI_BENDTIME(a1)   ; save as end time

               moveq.l  #1,d5                ; find first button

.findbut:      add      d5,d5                ; next button
               lsr      d7                   ; check next bit
               bcs.s    .findbut             ; condition not true ->

               lsr      d5                   ; first found changed button

.dcbut:        move     d5,BI_DCBUT(a1)      ; save double click button mask
               and      d0,d5                ; get 'active' state
               move     d5,BI_DCSTATE(a1)    ; save it

               bra.s    .exit


.wait:         tst.b    BI_BEVALID(a1)       ; old click still in buffer?
               bne.s    .exit                ; yes ->

               move     BI_DCBUT(a1),d5      ; double click button mask
               move     BI_PRVBSTATE(a1),d7  ; previous button state
               eor      d0,d7                ; get button state changes
               and      d5,d7                ; did dclick button state change?
               beq.s    .exit                ; no ->

               and      d0,d5                ; isolate state of dclick button
               cmp      BI_DCSTATE(a1),d5    ; changed it to 'active' state?
               bne.s    .exit                ; no ->

               moveq.l  #1,d7                ; yes: add 1 to # of clicks
               add      BI_NCLICKS(a1),d7
               cmp      BI_MAXCLICKS(a1),d7  ; > max # of clicks to wait for?
               bhi.s    .exit                ; yes ->

               move     d7,BI_NCLICKS(a1)    ; no: save new # of clicks

.exit:         move     d0,BI_PRVBSTATE(a1)  ; save button state
               movem.l  (sp)+,d5-d7/a0-a1    ; restore registers
               move.l   nk_butvec-4(pc),-(sp)   ; old vector address

nk_bvret:      rts                           ; jump to old routine




****************************************************************************
*
*  nk_bestart: start special handling of button event
*  #
*
*  In:   -
*  Out:  W  D7.W           handler mode (used in nk_beend)
*
*  Reg:  D:01234567   A:01234567   CCR
*          **.....W     **......    *
*
****************************************************************************

nk_bestart:    moveq.l  #0,d7                ; default: no special handling
               tst.b    buthnd               ; our handler installed?
               beq      .exit                ; no ->

               lea      _intin,a0            ; ^event mask
               moveq.l  #MU_BUTTON,d0        ; request for button event?
               and      (a0),d0
               beq      .exit                ; no ->

               lea      bvar,a1              ; ^button event variables
               move     2(a0),d7             ; nclicks + reverse flag
               move.b   d7,BI_MAXCLICKS+1(a1)   ; isolate and save nclicks
               clr.b    d7                   ; isolate reverse flag
               move     d7,BI_REVCOND(a1)    ; save reverse flag
               move     4(a0),BI_BMASK(a1)   ; save button mask
               move     6(a0),BI_BSTATE(a1)  ; save button state
                                             ; new mouse button parameters:
               clr      4(a0)                ; mask = 0
               clr      6(a0)                ; state = 0
               moveq.l  #1,d7                ; new handler mode
               move     d7,2(a0)             ; also used for new nclicks
                                             ; -> button condition always true

               moveq.l  #MU_TIMER,d0         ; request for timer event?
               and      (a0),d0
               beq.s    .no_timer            ; no ->

               moveq.l  #2,d7                ; new mode
               move.l   28(a0),d0            ; time = 0 ms?
               beq.s    .exit                ; yes: fine, exactly what we need

               moveq.l  #3,d7                ; new mode
               swap     d0                   ; time in Motorola format
               cmp.l    prvtime,d0           ; identical to previous time?
               beq.s    .nulltime            ; yes ->

               move.l   d0,prvtime           ; no: save timer event value
               LWDIVU   #5,d0,d1             ; convert to 200 Hz resolution
               add.l    sysclock,d0          ; + current timer value
               move.l   d0,tendtime          ; this is our timer end value

               bra.s    .nulltime            ; and set own one


*              user doesn't want timer events - but we need them!

.no_timer:     moveq.l  #-1,d0               ; reset previous timer event value
               move.l   d0,prvtime
               or       #MU_TIMER,(a0)       ; set bit: check timer events

.nulltime:     clr.l    28(a0)               ; time: 0 ms

.exit:         rts                           ; bye




****************************************************************************
*
*  nk_beend: end special handling of button event
*  #
*
*  In:   R  D7.W           handler mode:
*                          0     button event handler inactive
*                          1     user did not check timer events
*                          2     user did check timer events with time = 0 ms
*                          3     user did check timer events with time > 0 ms
*  Out:  U  _intout.W[]:   updated mouse event information
*
*  Reg:  D:01234567   A:01234567   CCR
*          **......     **......    *
*
****************************************************************************

nk_beend:      lea      _intout,a0           ; ^event mask
               move     d7,d0                ; which mode?
               beq.s    .exit                ; no special handling

               lea      bvar,a1              ; ^button event info block
               move     BI_PRVBSTATE(a1),6(a0)  ; return button state

               moveq.l  #MU_BUTTON,d1        ; did button event occure?
               and      (a0),d1              ; (if not: just moving a window
                                             ;  or something like that)
               beq.s    .lock                ; no -> lock mouse button event

               tst.b    BI_LOCK(a1)          ; is button event locked?
               bne      .unlock              ; yes -> unlock it

               tst.b    BI_BEVALID(a1)       ; event valid?
               bne.s    .butev               ; yes ->

               move     BI_BMODE(a1),d1      ; no: get handler mode
               beq.s    .nombut              ; standby mode ->

               subq     #1,d1                ; wait mode?
               beq.s    .wait                ; yes ->

.hold:         clr      12(a0)               ; # of clicks = 0
               bra.s    .evtimer

.wait:         move.l   BI_BENDTIME(a1),d1   ; button event end time
               cmp.l    sysclock,d1          ; end of time reached?
               bhi.s    .nombut              ; no ->

               addq     #1,BI_BMODE(a1)      ; change to hold mode
               tst.b    BI_CVALID(a1)        ; was last button condition valid?
               bne.s    .butev               ; yes ->

               clr      BI_BMODE(a1)         ; no: standby mode

.butev:        move     BI_NCLICKS(a1),12(a0)   ; return # of clicks
               move     BI_SBSTATE(a1),6(a0)    ; return start button state
               sf.b     BI_BEVALID(a1)       ; button event fetched
               bra.s    .evtimer

.nombut:       and      #~MU_BUTTON,(a0)     ; no mouse button event

.evtimer:      subq     #1,d0                ; timer event checked by us?
               beq.s    .nktimer             ; yes ->

               subq     #1,d0                ; 0 ms timer checked by user?
               beq.s    .exit                ; yes ->

.user0:        move.l   tendtime,d1          ; timer event end time
               cmp.l    sysclock,d1          ; end of waiting time reached?
               bls.s    .exit                ; yes ->

.nktimer:      and      #~MU_TIMER,(a0)      ; no timer event did occure

.exit:         move     (a0),d0              ; event mask
               and      #MU_TIMER,d0         ; did a timer event occure?
               beq.s    .exit2               ; no ->

.treset:       moveq.l  #-1,d0               ; yes: reset previous timer
               move.l   d0,prvtime           ;  event value

.exit2:        rts

.lock:         st.b     BI_LOCK(a1)          ; lock mouse button event
               move     #MU_XTIMER,d1        ; lock timer events, too?
               and      _intin,d1            ; check this flag
               beq.s    .evtimer             ; don't lock ->

               bra.s    .nktimer             ; lock ->

.unlock:       sf.b     BI_LOCK(a1)          ; unlock mouse button event
               clr      BI_BMODE(a1)         ; reset mode
               sf.b     BI_BEVALID(a1)       ; reset button event flag
               bra.s    .nombut




****************************************************************************
*
*  nk_mctrl: set control array for multi event
*  #
*
*  In:   -
*  Out:  W  _contrl.W[]:   control parameters configured for evnt_multi() call
*
*  Reg:  D:01234567   A:01234567   CCR
*          ........     *.......    *
*
****************************************************************************

nk_mctrl:      lea      _contrl,a0           ; ^control array
               move     #25,(a0)+            ; opcode
               move     #16,(a0)+            ; size of intin
               move     #7,(a0)+             ; size of intout
               move     #1,(a0)+             ; size of addrin
               clr      (a0)                 ; size of addrout
               rts                           ; bye




****************************************************************************
*
*  nk_msave: save input parameters for evnt_multi() call in buffer
*  #
*
*  In:   -
*  Out:  W  multibuf.W[]:  first 16 words of _intin and first long of _addrin
*
*  Reg:  D:01234567   A:01234567   CCR
*          *******.     **......    *
*
****************************************************************************

nk_msave:      lea      _intin,a0            ; ^_intin
               lea      multibuf,a1          ; ^save buffer
               movem.l  (a0)+,d0-d6          ; get first 14 words of _intin
               movem.l  d0-d6,(a1)           ; save 'em in buffer
               move.l   (a0),7*4(a1)         ; save last two words
               move.l   _addrin,8*4(a1)      ; save contents of _addrin
               rts




****************************************************************************
*
*  nk_mrestore: restore evnt_multi() input parameters corrupted by AES(!)
*  #
*
*  In:   -
*  Out:  W  _contrl.w[]:   restored _contrl array (5 words)
*        W  _intin.w[]:    restored _intin array (16 words)
*        W  _addrin.L[]:   restored _addrin array (1 longword)
*
*  Reg:  D:01234567   A:01234567   CCR
*          *******.     **......    *
*
****************************************************************************

nk_mrestore:   lea      multibuf,a0          ; ^save buffer
               lea      _intin,a1            ; ^_intin
               movem.l  (a0)+,d0-d6          ; first 14 words of _intin
               movem.l  d0-d6,(a1)           ; put them in _intin array
               move.l   (a0)+,7*4(a1)        ; last two words
               move.l   (a0),_addrin         ; restore _addrin
               bra.s    nk_mctrl             ; restore _contrl

               .endif   ; .if !(^^defined NKCNOGEM)




****************************************************************************
*
*  nk_200hz: 200 Hz system clock interrupt
*  # I S
*
*  In:   -
*  Out:  -
*
*  Reg:  D:01234567   A:01234567   CCR
*          ........     ........    *
*
****************************************************************************

               XBRA_HD                       ; XBRA header

nk_200hz:      move.l   _hz_200,sysclock     ; just transfer value
               move.l   nk_200hz-4(pc),-(sp) ; old vector address
               rts                           ; jump to old routine



*START
****************************************************************************
*                            GLOBAL TEXT SECTION                           *
****************************************************************************
*END

               .text

*START
****************************************************************************
*
*  nkc_init: initialize NKCC
*  # G U
*
*  This function initializes NKCC. It must be called once before any other
*  NKCC routine. It performs some tasks that may be important for you to
*  know:
*
*  - the state of bit 3 of the system variable <conterm> ($484.B) is saved,
*    then the bit is set; nkc_exit() restores it
*  - a 200 Hz clock interrupt is installed, using the XBRA method (ID is
*    "NKCC")
*
*  nkc_init() gets some flags which configure NKCC and enable some special
*  functions:
*
*  NKI?_BUTHND       install NKCC's button event handler (see documentation
*                    NKCC.DOC for details)
*  NKI?_BHTOS        additional flag: install only, if TOS has mouse click bug
*  NKI?_NO200HZ      don't install the 200 Hz timer interrupt
*
*  Notes:
*
*  - if NKCC is assembled as TOS-only version (symbol NKCNOGEM was defined),
*    both NKI?_BUTHND and NKI?_BHTOS flags are ignored.
*
*  - if the button event handler is installed, the NKI?_NO200HZ flag is
*    ignored (because the 200 Hz clock is needed by the handler). Besides,
*    the initialization continues:
*
*    - an own GEM trap handler (trap #2) is installed, using the XBRA method
*      (to be up to date when a new double click time is set via the AES
*      function evnt_dclick())
*
*    - a vex_butv() call (VDI) is made to install a mouse button exchange
*      handler
*
*  In:   R  D0.L           miscellaneous flags (NKI?_...); see above
*        R  D1.w           handle of an open VDI workstation
*                          (must only be valid if the button handler is
*                          installed)
*  Out:  W  D0.w           NKCC's version number as 4 digit BCD
*                          (main # in high byte, sub # in low byte)
*
*  Reg:  D:01234567   A:01234567   CCR
*          U**.....     **......    *
*
****************************************************************************
*END

nkc_init:      move.l   a2,-(sp)             ; save a2 for Turbo C

               movem.l  d0-d1,-(sp)          ; save flags and VDI handle
               subq.l   #2,sp                ; one word local stack space


*              switch to Supervisor mode to have access to system variables

               clr.l    -(sp)                ; use usp as ssp
               move     #$20,-(sp)           ; Super
               trap     #1                   ; GEMDOS
               move.l   d0,2(sp)             ; save old sp
               move     #$20,(sp)            ; Super opcode for next call


*              save <conterm> and enable auto return of shift/ctrl/alt status

               lea      conterm.w,a0         ; ^conterm
               move.b   (a0),cntold          ; save old content
               bset.b   #3,(a0)              ; set bit for auto shift return


*              locate shift state system variable

               move.l   _sysbase.w,a0        ; ^OS header
               move     2(a0),d0             ; get TOS version
               move     d0,6(sp)             ; save it in local stack space
               cmp      #$0100,d0            ; TOS 1.0?
               beq.s    .tos10               ; yes ->

               move.l   $24(a0),pshift       ; get ^shift state variable
               bra.s    .user                ; ->

.tos10:        move.l   #$e1b,pshift         ; this is the location in TOS 1.0


*              switch back to user mode

.user:         trap     #1                   ; GEMDOS
               addq     #6,sp                ; clean stack


*              fetch addresses of TOS' key scan code translation tables

               moveq.l  #-1,d0               ; the function is also used to
               move.l   d0,-(sp)             ;  change the addresses; values
               move.l   d0,-(sp)             ;  of -1 as new addresses tell
               move.l   d0,-(sp)             ;  XBIOS not to change them
               move     #$10,-(sp)           ; Keytbl
               trap     #14                  ; XBIOS
               lea      $e(sp),sp            ; clean stack

               move.l   d0,a0                ; ^key table structure
               move.l   (a0)+,pkey_unshift   ; get ^unshifted table
               move.l   (a0)+,pkey_shift     ; get ^shift table
               move.l   (a0),pkey_caps       ; get ^CapsLock table


*              do some other initialization stuff

               clr.l    sflags               ; reset special key flags
               clr.b    prvvalid             ; no previous key code


*              install the mouse button handler

               move     (sp)+,d0             ; TOS version
               movem.l  (sp)+,d1-d2          ; restore flags and VDI handle

               .if      !(^^defined NKCNOGEM)

               clr.b    buthnd               ; default: set flag: not installed
               btst.l   #NKIb_BUTHND,d1      ; install it?
               beq      .timer               ; no ->

               btst.l   #NKIb_BHTOS,d1       ; check TOS version number?
               beq.s    .butev               ; no ->

               cmp      #$0104,d0            ; bug appeared in TOS 1.04!
               blo      .timer               ; TOS is ok ->

               cmp      #$0306,d0            ; bug is solved since TOS 3.06!
               bhs      .timer               ; TOS is ok ->

.butev:        bclr.l   #NKIb_NO200HZ,d1     ; must install timer interrupt
               st.b     buthnd               ; set flag: handler installed
               move     d2,vdihnd            ; save VDI handle

               moveq.l  #0,d0                ; do some initializations
               lea      bvar,a1              ; ^button event info block
               move     d0,BI_BMODE(a1)      ; init interrupt mode
               move.b   d0,BI_BEVALID(a1)    ; mouse click info not valid
               move     d0,BI_PRVBSTATE(a1)  ; init previous button state
               move     d0,BI_BMASK(a1)      ; don't check any button
               move.b   d0,BI_LOCK(a1)       ; mouse button event isn't locked
               subq.l   #1,d0                ; d0.l = -1
               move     d0,BI_REVCOND(a1)    ; reverse button condition
                                             ; -> no mouse click is checked
               move.l   d0,prvtime           ; previous timer event value

               lea      _contrl,a0           ; set parameter array for vex_butv
               move     #125,(a0)+           ; opcode
               clr.l    (a0)+                ; no ptsin/ptsout entries
               clr.l    (a0)+                ; no intin/intout entries
               addq     #2,a0                ; entry #5 is unused
               move     d2,(a0)+             ; handle of workstation
               lea      nk_butvec(pc),a1     ; ^our handler
               move.l   a1,(a0)
               moveq.l  #115,d0              ; opcode: VDI
               move.l   #vdipb,d1            ; ^parameter block
               trap     #2                   ; execute VDI
               move.l   _contrl+18,nk_butvec-4  ; save old handler address

               lea      _contrl,a0           ; set parameter for evnt_dclick
               move     #26,(a0)+            ; opcode
               move     #2,(a0)+             ; # of entries in intin
               move     #1,(a0)+             ; # of entries in intout
               clr.l    (a0)+                ; # of entries in addrin/addrout
               clr      _intin+2             ; get/set flag: get value
               move     #200,d0              ; opcode for AES
               move.l   #aespb,d1            ; ^parameter block
               trap     #2                   ; execute AES
               move     _intout,bvar+BI_DCLTIME ; save double click time

                                             ; install trap #2 handler
               moveq.l  #34,d0               ; vector number
               moveq.l  #NKXM_NUM,d1         ; mode: vector number
               lea      nk_gemtrap(pc),a0    ; ^function to install
               bsr      nkc_vlink            ; install it

               .endif   ; .if !(^^defined NKCNOGEM)


*              install 200 Hz clock interrupt

.timer:        clr.l    sysclock             ; init 200 Hz clock for the case
                                             ; that the timer won't be installed
               btst.l   #NKIb_NO200HZ,d1     ; install interrupt?
               seq.b    timerflag            ; $00 = no, $ff = yes
               bne.s    .exit                ; no ->

               moveq.l  #69,d0               ; vector number
               moveq.l  #NKXM_NUM,d1         ; mode: vector number
               lea      nk_200hz(pc),a0      ; ^function to install
               bsr      nkc_vlink            ; install it


*              restore saved register and exit

.exit:         move.l   (sp)+,a2             ; restore register
               move     #VERSION,d0          ; load version #
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_exit: exit NKCC
*  # G U
*
*  nkc_exit() must be called before the program is quit. It restores
*  <conterm>, which has been modified by nkc_init() and removes all
*  handlers installed in the system.
*
*  In:   -
*  Out:  W  D0.W           status flag:
*                          0     OK
*                          -1    can't remove 200 Hz clock interrupt
*                          -2    can't remove trap #2 handler
*                          -3    can't remove both handlers
*                          An error can happen if somebody corrupted the
*                          XBRA vector list. This is fatal!
*
*  Reg:  D:01234567   A:01234567   CCR
*          ***.....     **......   =D0.W
*
****************************************************************************
*END

nkc_exit:      move.l   a2,-(sp)             ; save register


*              switch to Supervisor mode and restore conterm

               clr.l    -(sp)                ; use usp as ssp
               move     #$20,-(sp)           ; Super
               trap     #1                   ; GEMDOS
               move.l   d0,2(sp)             ; save old sp
               move     #$20,(sp)            ; Super opcode for next call

               move.b   cntold,conterm.w     ; write old content back


*              switch back to user mode

               trap     #1                   ; GEMDOS
               addq     #6,sp                ; clean stack


*              remove 200 Hz system clock interrupt

               clr      -(sp)                ; default remove status for
                                             ;  timer interrupt
               tst.b    timerflag            ; installed?
               beq.s    .buthnd              ; no ->

               moveq.l  #69,d0               ; vector number
               moveq.l  #NKXM_NUM,d1         ; mode: vector number
               lea      nk_200hz(pc),a0      ; ^function to remove
               bsr      nkc_vunlink          ; remove it
               move     d0,(sp)              ; save remove status


*              remove own button event handler

.buthnd:       clr      -(sp)                ; default remove status for
                                             ;  button event handler

               .if      !(^^defined NKCNOGEM)

               tst.b    buthnd               ; handler installed?
               beq.s    .exit                ; no ->

               lea      _contrl,a0           ; set paremeter array for vex_butv
               move     #125,(a0)+           ; opcode
               clr.l    (a0)+                ; no ptsin/ptsout entries
               clr.l    (a0)+                ; no intin/intout entries
               addq     #2,a0                ; entry #5 is unused
               move     vdihnd,(a0)+         ; handle of workstation
               lea      nk_butvec(pc),a1     ; ^our handler
               move.l   -4(a1),(a0)          ; ^old routine
               moveq.l  #115,d0              ; opcode: VDI
               move.l   #vdipb,d1            ; ^parameter block
               trap     #2                   ; execute VDI

                                             ; remove trap #2 handler
               moveq.l  #34,d0               ; vector number
               moveq.l  #NKXM_NUM,d1         ; mode: vector number
               lea      nk_gemtrap(pc),a0    ; ^function to remove
               bsr      nkc_vunlink          ; remove it
               move     d0,(sp)              ; save status

               .endif   ; .if !(^^defined NKCNOGEM)


*              restore saved registers and exit

.exit:         move     (sp)+,d0             ; trap #2 remove error?
               beq.s    .stat0               ; no ->

               move     (sp)+,d0             ; 200 Hz clock remove error?
               beq.s    .stat1               ; no ->

               moveq.l  #-3,d0               ; both failed!
               bra.s    .ret

.stat0:        move     (sp)+,d0             ; 200 Hz clock remove error?
               beq.s    .ret                 ; no ->

               moveq.l  #-1,d0               ; "only" 200 Hz clock failure?
               bra.s    .ret

.stat1:        moveq.l  #-2,d0               ; "only" trap #2 remove failure?

.ret:          move.l   (sp)+,a2             ; restore register
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_set: set special key flags
*  # G
*
*  This function is used to enable/disable special key handling procedures.
*  The features which can be set are:
*
*  - direct input of ASCII codes; when switched on, pressing the Alternate
*    key plus one of the numbers on the numeric keypad will start the input
*    of a decimal ASCII number. The input is finished either when 3 digits
*    are typed in or the Alternate key is released. NKCC will return the
*    corresponding character with the entered ASCII code. This feature makes
*    it possible to use the whole range of the character set (from 0 ... 255).
*
*  - deadkey management; when switched on, NKCC will combine some combi-
*    nations of two key strokes to one character. This is used to generate
*    characters with accents which are not on the keyboard. The supported
*    deadkeys are:
*
*    ^   +   aeiou      =         (NKS?_D_CIRCUM)
*    ~   +   nNaoAo     =        (NKS?_D_TILDE)
*    '   +   eEaiou     =        (NKS?_D_AGUI)
*    `   +   aeiouA     =        (NKS?_D_GRAVE)
*       +   aeiouyAOU  =     (NKS?_D_UMLAUT)
*    "   +   aeiouyAOU  =     (NKS?_D_QUOTE)
*       +   aA         =            (NKS?_D_SMOERE)
*    ,   +   cC         =            (NKS?_D_CEDIL)
*    /   +   oO         =            (NKS?_D_SLASH)
*
*    The quote character as synonym for umlaut is e.g. needed on the Dutch
*    keyboard, where neither umlaut characters nor the umlaut itself are
*    available.
*
*    Each deadkey can be enabled/disabled separately.
*
*  - Control key emulation: Control plus an ASCII code in the range of
*    $40...$5F (characters @, A...Z, [, \, ], ^ and _) is converted
*    to an ASCII code of $00...$1F.
*
*
*  In:   R  D0.L           new key flags (bit set = feature on):
*                          NKS?_ALTNUM       ASCII input
*                          NKS?_D_...        deadkey ...
*                                            (NKSf_DEADKEY: all deadkeys)
*                          NKS?_CTRL         control key emulation
*  Out:  -
*
*  Reg:  D:01234567   A:01234567   CCR
*          ***.....     **......    *
*
****************************************************************************
*END

nkc_set:       move.l   d0,sflags            ; just save flags
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_conin: raw console character input
*  # G
*
*  This routine replaces the Gemdos function Crawcin. However, it returns
*  a WORD with the key code in normalized format rather than a LONG with
*  the key code in the language dependend TOS format.
*
*  In:   -
*  Out:  W  D0.w           key code in normalized format
*                          for details see nkc_tconv()
*        W  CCR            set according content of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          W**.....     **......   =D0.w
*
****************************************************************************
*END

nkc_conin:     tst.b    prvvalid             ; previous character in buffer?
               beq.s    .getkey              ; no->

               clr.b    prvvalid             ; remove key code from buffer
               move     prvkey,d0            ; get key code
               rts                           ; bye


.getkey:       movem.l  d3-d7/a2-a6,-(sp)    ; save registers
               clr      d0                   ; don't check Alternate state
               bsr      nk_gci               ; get key code
               lea      nk_gci(pc),a0        ; ^function to execute
               bsr      nk_spec              ; special key handling
               movem.l  (sp)+,d3-d7/a2-a6    ; restore registers
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_cstat: return console character input status
*  # G
*
*  This function checks, if a key is in the key input buffer or not.
*
*  In:   -
*  Out:  W  D0.W           flag:
*                          0     no key in buffer
*                          -1    at least one key in buffer
*        W  CCR            set according content of D0.W
*
*  Reg:  D:01234567   A:01234567   CCR
*          W**.....     **......   =D0.W
*
****************************************************************************
*END

nkc_cstat:     tst.b    prvvalid             ; previous character in buffer?
               sne.b    d0                   ; $ff = yes, $00 = no
               ext      d0                   ; as word
               bne.s    .exit                ; char available: exit

               move.l   a2,-(sp)             ; save register for Turbo C

               move     #11,-(sp)            ; Cconis
               trap     #1                   ; Gemdos
               addq     #2,sp                ; clean stack

               move.l   (sp)+,a2             ; restore register
               tst      d0                   ; set CCR

.exit:         rts                           ; bye



               .if      !(^^defined NKCNOGEM)

*START
****************************************************************************
*
*  nkc_multi: NKCC multi event
*  # G U
*
*  nkc_multi() is a binding function to the AES multi event handler. The
*  only differences are the keyboard events and the shift key state: the key
*  codes are returned in normalized format (see nkc_tconv()), the shift key
*  state is compatible to the NKF?_... flags. For a detailed description of
*  the whole mass of parameters consult your AES manual or compiler handbook!
*
*  In:   R   4(SP).w       event mask (MU_...)
*        R   6(SP).w       MU_BUTTON: max # of clicks to wait for
*        R   8(SP).w       MU_BUTTON: mask of buttons to check
*        R  10(SP).w       MU_BUTTON: button states to wait for
*        R  12(SP).w       MU_M1: area enter/leave flag
*        R  14(SP).w       MU_M1: x position of area
*        R  16(SP).w       MU_M1: y position of area
*        R  18(SP).w       MU_M1: width of area
*        R  20(SP).w       MU_M1: height of area
*        R  22(SP).w       MU_M2: area enter/leave flag
*        R  24(SP).w       MU_M2: x position of area
*        R  26(SP).w       MU_M2: y position of area
*        R  28(SP).w       MU_M2: width of area
*        R  30(SP).w       MU_M2: height of area
*        R  32(SP).L       MU_MESAG: ^8 words of message buffer
*        R  36(SP).w       MU_TIMER: low word of time to wait
*        R  38(SP).w       MU_TIMER: high word of time to wait
*        ^W 40(SP).L       MU_BUTTON/M1/M2: ^word for mouse x
*        ^W 44(SP).L       MU_BUTTON/M1/M2: ^word for mouse y
*        ^W 48(SP).L       MU_BUTTON/M1/M2: ^word for button state
*        ^W 52(SP).L       MU_BUTTON/M1/M2: ^word for shift state
*        ^W 56(SP).L       MU_KEYBD: ^word for key code in normalized format
*        ^W 60(SP).L       MU_BUTTON: ^word for # of mouse clicks
*  Out:  W  D0.w           mask of occured events (MU_...)
*
*  Reg:  D:01234567   A:01234567   CCR
*          W**.....     **......    *
*
****************************************************************************
*END

nkc_multi:     tst.b    prvvalid             ; previous character in buffer?
               beq.s    .mevent              ; no ->

               clr.b    prvvalid             ; remove key code from buffer
               move.l   56(sp),a0            ; ^buffer for key code
               move     prvkey,(a0)          ; return key code
               moveq.l  #MU_KEYBD,d0         ; a keyboard event occured
               rts                           ; bye

.mevent:       movem.l  d3-d7/a2-a6,-(sp)    ; save registers


*              transfer stack arguments to GEM parameter arrays

               lea      10*4+4(sp),a0        ; ^first parameter
               lea      _intin,a1            ; ^integer in array
               movem.l  (a0)+,d1-d7          ; get the first 14 words
               movem.l  d1-d7,(a1)           ; place them in intin
               move.l   (a0)+,_addrin        ; ^message buffer in addrin
               move.l   (a0),7*4(a1)         ; timer event lo and hi count

               bsr      nk_mctrl             ; setup control array


*              execute multi event

               bsr      nk_bestart           ; start handling for button event
               bsr      nk_msave             ; save multi event parameters

.wait:         cmp      #25,_contrl          ; still evnt_multi() opcode?!?
               beq.s    .aes                 ; yes ->

               bsr      nk_mrestore          ; NO! AES corrupted it! restore

.aes:          lea      _intin,a0            ; ^intin
               move     (a0),-(sp)           ; save event mask
               and      #~MU_XTIMER,(a0)     ; clear x-timer flag
               move     #200,d0              ; opcode for AES
               move.l   #aespb,d1            ; ^AES parameter block
               trap     #2                   ; call GEM
               move     (sp)+,_intin         ; restore event mask

               bsr      nk_beend             ; end handling of button event
               tst      _intout              ; did any event occure?
               beq.s    .wait                ; no ->


*              return intout parameters via pointers

               movem.l  10*4+40(sp),a0-a5    ; get return pointers
               lea      _intout,a6           ; ^intout array
               move     (a6)+,d0             ; mask of occured events
               move     (a6)+,(a0)           ; mouse x
               move     (a6)+,(a1)           ; mouse y
               move     (a6)+,(a2)           ; mouse button state
               move.b   1(a6),(a3)           ; shift key state (NKC flag
                                             ;  compatible!)
               clr.b    1(a3)                ; clear low byte of shift state
               move.l   a6,a3                ; ^shift state in intout
               move     4(a6),(a5)           ; # of mouse clicks
               move     2(a6),(a4)           ; key code

* You wonder why (a4) is written as last one and (a3) points to intout
* instead of the original return location? This is for the case that
* the caller isn't interested in one or some of the return values and
* let them point to ONE dummy variable.


*              check if keyboard event occured and convert key code, if so

               moveq.l  #MU_KEYBD,d1         ; the flag to test
               and      d0,d1                ; is it set?
               beq.s    .exit                ; no-> exit

               move     d0,-(sp)             ; save event mask
               move.l   a4,-(sp)             ; save ^key code

               move     (a4),d0              ; scan code + ASCII code
               moveq.l  #MU_BUTTON|MU_M1|MU_M2,d1  ; any of the events checked
               and      10*4+4+6(a7),d1      ; which return shift state?
               bne.s    .useiout             ; yes->

               move.l   pshift,a3            ; no: get shift state directly
               move.b   (a3),d1              ; get it
               and      #$f,d1               ; isolate valid bits
               bra.s    .conv

.useiout:      move     (a3),d1              ; get shift state from intout

.conv:         bsr      nk_bioscode          ; convert to BIOS format
               bsr      nkc_tconv            ; convert to normalized format
               lea      nk_mci(pc),a0        ; ^function to execute
               bsr      nk_spec              ; special key code handling

               move.l   (sp)+,a0             ; restore ^key code
               move     d0,(a0)              ; return key code

               move     (sp)+,d0             ; restore event mask

.exit:         movem.l  (sp)+,d3-d7/a2-a6    ; restore registers
               rts                           ; bye




*START
****************************************************************************
*
*  nkc_amulti: NKCC multi event called by Assembler  *** added by Gerd Knops
*  # G U
*
*  See description of nkc_multi()!
*
*  usage: instead of    move.l   #aespb,d1
*                       move  #$c8,d0
*                       trap  #2
*
*         do            jsr   nkc_amulti
*
*  In:   all parameters for evnt_multi in the AES parameter arrays
*  Out:  values in intout and message buffer
*
*  Reg:  D:01234567   A:01234567   CCR
*          ***.....     **......    *
*
****************************************************************************
*END

nkc_amulti:    tst.b    prvvalid             ; previous character in buffer?
               beq.s    .mevent              ; no->

               clr.b    prvvalid             ; remove key code from buffer
               clr      _intout+8            ; no SHIFT etc.
               move     prvkey,_intout+10    ; return key code
               move     #MU_KEYBD,_intout    ; a keyboard event occured
               rts                           ; bye

.mevent:       movem.l  d3-d7/a2-a6,-(sp)    ; save registers

               move     _intin,-(sp)         ; events to get


*              execute multi event

               bsr      nk_bestart           ; start handling for button event
               bsr      nk_msave             ; save multi event parameters

.wait:         cmp      #25,_contrl          ; still evnt_multi() opcode?!?
               beq.s    .aes                 ; yes ->

               bsr      nk_mrestore          ; NO! AES corrupted it! restore

.aes:          lea      _intin,a0            ; ^intin
               move     (a0),-(sp)           ; save event mask
               and      #~MU_XTIMER,(a0)     ; clear x-timer flag
               move     #200,d0              ; opcode for AES
               move.l   #aespb,d1            ; ^AES parameter block
               trap     #2                   ; call GEM
               move     (sp)+,_intin         ; restore event mask

               bsr      nk_beend             ; end handling of button event
               tst      _intout              ; did any event occure?
               beq.s    .wait                ; no ->

               move     (sp)+,d2             ; events to get


*              check if keyboard event occured and convert key code, if so

               lea      _intout+8,a3         ; ^shift key state
               move     (a3),d0              ; make in NKF?_... compatible
               move.b   d0,(a3)
               clr.b    1(a3)

               move     _intout,d0           ; get ev_mwhich
               moveq.l  #MU_KEYBD,d1         ; the flag to test
               and      d0,d1                ; is it set?
               beq.s    .exit                ; no-> exit

               move     d0,-(sp)             ; save event mask

               move     _intout+10,d0        ; scan code + ASCII code
               moveq.l  #MU_BUTTON|MU_M1|MU_M2,d1  ; any of the events checked
               and      d2,d1                ; which return shift state?
               bne.s    .useiout             ; yes->

               move.l   pshift,a3            ; no: get shift state directly
               move.b   (a3),d1              ; get it
               and      #$f,d1               ; isolate valid bits
               bra.s    .conv

.useiout:      move     _intout+8,d1         ; get shift state from intout
               lsr      #8,d1                ; in TOS format

.conv:         bsr      nk_bioscode          ; convert to BIOS format
               bsr.s    nkc_tconv            ; convert to normalized format
               lea      nk_mci(pc),a0        ; ^function to execute
               bsr      nk_spec              ; special key code handling

               move     d0,_intout+10        ; save keycode
               move     (sp)+,_intout        ; restore event mask

.exit:         movem.l  (sp)+,d3-d7/a2-a6    ; restore registers
               rts                           ; bye

               .endif   ; .if !(^^defined NKCNOGEM)



*START
****************************************************************************
*
*  nkc_tconv: TOS key code converter
*  # G R
*
*  This is the most important function within NKCC: it takes a key code
*  returned by TOS and converts it to the sophisticated normalized format.
*
*  Note: the raw converter does no deadkey handling, ASCII input or
*        Control key emulation.
*
*  In:   R  D0.L           key code in TOS format:
*                                      0                    1
*                          bit 31:     ignored              ignored
*                          bit 30:     ignored              ignored
*                          bit 29:     ignored              ignored
*                          bit 28:     no CapsLock          CapsLock
*                          bit 27:     no Alternate         Alternate pressed
*                          bit 26:     no Control           Control pressed
*                          bit 25:     no left Shift key    left Shift pressed
*                          bit 24:     no right Shift key   right Shift pressed
*
*                          bits 23...16: scan code
*                          bits 15...08: ignored
*                          bits 07...00: ASCII code (or rubbish in most cases
*                             when Control or Alternate is pressed ...)
*
*  Out:  W  D0.w           normalized key code:
*                          bits 15...08: flags:
*                                      0                    1
*                          NKF?_FUNC   printable char       "function key"
*                          NKF?_RESVD  ignore it            ignore it
*                          NKF?_NUM    main keypad          numeric keypad
*                          NKF?_CAPS   no CapsLock          CapsLock
*                          NKF?_ALT    no Alternate         Alternate pressed
*                          NKF?_CTRL   no Control           Control pressed
*                          NKF?_LSH    no left Shift key    left Shift pressed
*                          NKF?_RSH    no right Shift key   right Shift pressed
*
*                          bits 07...00: key code
*                          function (NKF?_FUNC set):
*                             < 32: special key (NK_...)
*                             >=32: printable char + Control and/or Alternate
*                          no function (NKF?_FUNC not set):
*                             printable character (0...255!!!)
*
*        W  CCR            set according content of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          U**.....     **......   =D0.w
*
****************************************************************************
*END
*
* Usage of the NKF?_RESVD flag:
*
* If the flag is set, the function nkc_cmp(), which evaluates the flag,
* has to check the state of the shift keys when comparing two key codes.
*
* Don't use the flag outside NKCC! Its content and meaning may change
* in future versions without notice!
*
****************************************************************************

nkc_tconv:     movem.l  d3/d4,-(sp)          ; save registers


*              separate TOS key code

               move.l   d0,d1                ; TOS key code
               swap     d1                   ; .W = scan code and flags
               move     d1,d2                ; copy
               move     #$ff,d3              ; and-mask
               and      d3,d0                ; .B = ASCII code
               and      d3,d1                ; .B = scan code
               beq      .tos306              ; scancode=zero (key code created
                                             ;  by ASCII input of TOS 3.06)? ->
               and      #$1f00,d2            ; .W = key flags (in high byte)


*              decide which translation table to use

               move     d2,d3                ; key flags
               and      #NKFf_SHIFT,d3       ; isolate bits for shift keys
               beq.s    .ktab1               ; shift key pressed? no->

               move.l   pkey_shift,a0        ; yes: use shift table
               bra.s    .ktab3               ; ->

.ktab1:        btst.l   #NKFb_CAPS,d2        ; CapsLock?
               beq.s    .ktab2               ; no->

               move.l   pkey_caps,a0         ; yes: use CapsLock table
               bra.s    .ktab3               ; ->

.ktab2:        move.l   pkey_unshift,a0      ; use unshifted table


*              check if Alternate + number: they have simulated scan codes

.ktab3:        cmp.b    #$78,d1              ; scan code of Alt + number?
               blo.s    .scan1               ; no->

               sub.b    #$76,d1              ; yes: calculate REAL scan code
               move.b   (a0,d1),d0           ; fetch ASCII code
               or       #NKFf_ALT,d2         ; set Alternate flag
               bra      .cat_codes           ; -> add flag byte and exit


*              check if exception scan code from cursor keypad

.scan1:        lea      xscantab,a1          ; ^exception scan code table

.search_scan:  move     (a1)+,d3             ; NKC and scan code
               bmi.s    .tabend              ; <0? end of table reached ->

               cmp.b    d1,d3                ; scan code found?
               bne.s    .search_scan         ; no: continue search ->

               lsr      #8,d3                ; .B = NKC
               moveq.l  #0,d0                ; mark: key code found
               bra.s    .scan2               ; ->

.tabend:       moveq.l  #0,d3                ; no NKC found yet


*              check if rubbish ASCII code and erase it, if so

.scan2:        move.b   (a0,d1),d4           ; ASCII code from translation table
               cmp.b    #32,d0               ; ASCII returned by TOS < 32?
               bhs.s    .scan3               ; no -> can't be rubbish

               cmp.b    d4,d0                ; yes: compare with table entry
               beq.s    .scan3               ; equal: that's ok ->

               moveq.l  #0,d0                ; not equal: rubbish! clear it


*              check if ASCII code could only be produced via Alternate key
*              combination

.scan3:        tst.b    d0                   ; ASCII code valid?
               beq.s    .scan4               ; no ->

               cmp.b    d4,d0                ; compare with table entry
               beq.s    .scan4               ; equal: normal key ->

               and      #~NKFf_ALT,d2        ; no: clear Alternate flag


*              check if ASCII code found yet, and set it, if not

.scan4:        tst.b    d0                   ; found?
               bne.s    .scan5               ; yes ->

               move.b   d3,d0                ; no: use code from exception table
               bne.s    .scan5               ; now valid? yes ->

               move.b   d4,d0                ; no: use code from transl. table


*              check special case: delete key

.scan5:        cmp.b    #127,d0              ; ASCII code of Delete?
               bne.s    .scan6               ; no ->

               move.b   #NK_DEL,d0           ; yes: set according NKC


*              check if key is on numeric keypad (via scan code)

.scan6:        cmp.b    #$4a,d1              ; numeric pad scan code range?
               beq.s    .numeric             ; yes ->

               cmp.b    #$4e,d1
               beq.s    .numeric             ; yes ->

               cmp.b    #$63,d1
               blo.s    .scan7               ; no ->

               cmp.b    #$72,d1
               bhi.s    .scan7               ; no ->

.numeric:      or       #NKFf_NUM,d2         ; yes: set numeric bit


*              check if "function key" and set bit accordingly

.scan7:        cmp.b    #32,d0               ; ASCII code less than 32?
               bhs.s    .scan8               ; no ->

               or       #NKFf_FUNC,d2        ; yes: set function bit


*              check special case: Return or Enter key

               cmp.b    #13,d0               ; Return or Enter key?
               bne.s    .scan8               ; no ->

               btst.l   #NKFb_NUM,d2         ; yes: from the numeric pad?
               beq.s    .scan8               ; no -> it's Return, keep code

               moveq.l  #NK_ENTER,d0         ; yes: it's Enter; new code


*              check if function key (F1-F10) via scan code

.scan8:        cmp.b    #$54,d1              ; shift + function key?
               blo.s    .scan9               ; no ->

               cmp.b    #$5d,d1
               bhi.s    .scan9               ; no ->

               sub.b    #$54-$3b,d1          ; yes: scan code for unshifted key
               move     d2,d3                ; shift flags
               and      #NKFf_SHIFT,d3       ; any shift key flag set?
               bne.s    .scan9               ; yes ->
               or       #NKFf_SHIFT,d2       ; no: set both flags

.scan9:        cmp.b    #$3b,d1              ; (unshifted) function key?
               blo.s    .cat_codes           ; no ->

               cmp.b    #$44,d1
               bhi.s    .cat_codes           ; no ->

               move.b   d1,d0                ; yes: calc NKC
               sub.b    #$2b,d0


*              final flag handling; mix key code (low byte) and flag byte

.cat_codes:    move.l   pkey_shift,a0        ; ^shifted table
               move.b   (a0,d1),d3           ; get shifted ASCII code
               or       d2,d0                ; mix flags with key code
               bmi.s    .scan10              ; result is "function key"? ->

               and      #NKFf_CTRL+NKFf_ALT,d2  ; Control or Alternate pressed?
               bne.s    .scan11              ; yes ->

.scan10:       move.l   pkey_unshift,a0      ; ^unshifted table
               cmp.b    (a0,d1),d3           ; shifted ASCII = unshifted ASCII?
               beq.s    .scan12              ; yes ->

               bra.s    .exit                ; no ->

.scan11:       or       #NKFf_FUNC,d0        ; Alt/Ctrl + char: set function bit
               move.l   pkey_caps,a0         ; ^CapsLock table
               cmp.b    (a0,d1),d3           ; shifted ASCII = CapsLocked ASCII?
               bne.s    .exit                ; no ->

               move.b   d3,d0                ; yes: use shifted ASCII code

.scan12:       or       #NKFf_RESVD,d0       ; yes: nkc_cmp() has to check
                                             ;  the Shift keys

*              restore registers and exit

.exit:         tst      d0                   ; set CCR
               movem.l  (sp)+,d3/d4          ; restore registers
               rts                           ; bye


*              special handling for key codes created by TOS' 3.06 ASCII input

.tos306:       and      #NKFf_CAPS,d2        ; isolate CapsLock flag
               or       d2,d0                ; merge with ASCII code
               movem.l  (sp)+,d3/d4          ; restore registers
               rts                           ; bye




*START
****************************************************************************
*
*  nkc_gconv: GEM key code converter
*  # G R
*
*  Why a second key code converter, you ask? Well, in some cases it might
*  happen that the key status byte of the original key code (with states
*  of both Shift keys, Control, Alternate and CapsLock) is lost. Then
*  this converter function must be called, which uses another algorithm
*  to construct the normalized key code.
*
*  Notes:
*  -  the raw converter does no deadkey handling, ASCII input or Control
*     key emulation.
*  -  NKCC does not use this function at all for its own purposes!
*  -  some key combinations cannot be distinguished without the flag byte!
*     For example, "Alternate A" and "Shift Alternate A" produce the same
*     result. Whenever possible, use nkc_tconv()!
*
*  In:   R  D0.w           key code in GEM format:
*                          bits 15...08: scan code
*                          bits 07...00: ASCII code
*
*  Out:  W  D0.w           normalized key code (see nkc_tconv() for details)
*        W  CCR            set according content of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          U**.....     **......   =D0.w
*
****************************************************************************
*END

nkc_gconv:     move.l   d7,-(sp)             ; save register
               swap     d0                   ; move key code to high word
               move.l   pshift,a0            ; ^key state flags
               move.b   (a0),d0              ; get 'em
               and      #NKFf_CAPS>>8,d0     ; isolate CapsLock flag
               swap     d0                   ; move it up and key code down
               move.b   d0,d7                ; copy ASCII code
               lsl.l    #8,d0                ; shift flags and scan code up
               move.b   d7,d0                ; restore ASCII code
               move.l   pkey_shift,a0        ; ^shifted key table
               move.l   d0,d1                ; copy TOS key code
               swap     d1                   ; move flags and scan code down
               and      #$ff,d1              ; isolate scan code
               cmp.b    (a0,d1),d7           ; ASCII came from shifted table?
               bne.s    .conv                ; no ->

               move.l   pkey_unshift,a0      ; ^unshifted key table
               cmp.b    (a0,d1),d7           ; ASCII also in this table?
               beq.s    .conv                ; yes -> can't assume that shift
                                             ;  was pressed

               move.l   pkey_caps,a0         ; ^CapsLock key table
               cmp.b    (a0,d1),d7           ; ASCII also in this table?
               bne.s    .shifted             ; no ->

               btst.l   #NKFb_CAPS+16,d0     ; CapsLock active?
               bne.s    .conv                ; yes -> it COULD be generated
                                             ;  by pressing Shift, but we
                                             ;  assume CapsLock

.shifted:      or.l     #NKFf_SHIFT<<16,d0   ; yes: set both shift flags

.conv:         bsr      nkc_tconv            ; do TOS key code conversion

               cmp.b    #32,d0               ; "normal" ASCII?
               blo.s    .fkey                ; no ->

               cmp.b    d0,d7                ; ASCII code stayed the same?
               beq.s    .exit                ; yes ->

               moveq.l  #0,d1                ; clear for word operation
               lea      nkc_toupper,a0       ; ^upper case translation table
               move.b   d0,d1                ; copy ASCII code
               move.b   (a0,d1),d0           ; get upper-case ASCII code

               tst.b    d7                   ; no: which value had original?
               beq.s    .asc0                ; it was 0 ->

                                             ; non-zero: Control was pressed
.ctrl:         or       #NKFf_FUNC | NKFf_CTRL,d0
               bra.s    .exit                ; ->

                                             ; zero: Alternate MAY be pressed
.asc0:         cmp.b    #' ',d0              ; space?
               beq.s    .ctrl                ; it was Control!

               cmp.b    #'2',d0              ; ASCII "2"?
               bne.s    .alt                 ; no ->

               btst.l   #NKFb_ALT,d0         ; Alternate flag already set?
               beq.s    .ctrl                ; no -> set Control flag

                                             ; set Alternate flag
.alt:          or       #NKFf_FUNC | NKFf_ALT,d0
               bra.s    .exit                ; ->

.fkey:         cmp.b    #$1f,d7              ; Control Delete?
               beq.s    .ctrl

               cmp.b    #$0a,d7              ; Control Return/Enter?
               beq.s    .ctrl

.exit:         move.l   (sp)+,d7             ; restore register
               tst      d0                   ; set CCR
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_kstate: return state of Shift/Control/Alternate/CapsLock in
*              normalized format
*  # G R
*
*  This is a very *FAST* function which returns the state of the Shift/
*  Control/Alternate and CapsLock keys in normalized format.
*
*  In:   -
*  Out:  W  D0.w           normalized key flags:
*                                      0                    1
*                          NKF?_CAPS   no CapsLock          CapsLock
*                          NKF?_ALT    no Alternate         Alternate pressed
*                          NKF?_CTRL   no Control           Control pressed
*                          NKF?_LSH    no left Shift key    left Shift pressed
*                          NKF?_RSH    no right Shift key   right Shift pressed
*
*        W  CCR            set according content of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          w**.....     **......   =D0.w
*
****************************************************************************
*END

nkc_kstate:    move.l   pshift,a0            ; ^shift state variable
               move.b   (a0),d0              ; get its contents
               and      #$1f,d0              ; isolate valid bits
               lsl      #8,d0                ; make it NKC compatible
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_timer: return current value of 200 Hz system clock
*  # G R
*
*  This is a very *FAST* function which returns the content of the 200 Hz
*  system clock.
*
*  In:   -
*  Out:  W  D0.L           current 200 HZ system clock value
*
*  Reg:  D:01234567   A:01234567   CCR
*          w**.....     **......    *
*
****************************************************************************
*END

nkc_timer:     move.l   sysclock,d0          ; just get value
               rts                           ; bye bye



*START
****************************************************************************
*
*  nkc_cmp: compare two key codes
*  # G R
*
*  nkc_cmp() compares key codes. What for, you ask? A simple "if key_code1
*  = key_code2" would also do it? No! This function follows some specific
*  rules, which improve the flexibility of key code comparism.
*
*  One of the key codes passed to nkc_cmp() is called the "reference key
*  code". The other is the "test key code", which is got from nkc_conin()
*  or nkc_multi(). Some flags of the reference code are treated a special
*  way:
*
*  NKF?_IGNUM (same as NKF?_RESVD)
*     if set, the numeric keypad flag doesn't matter
*
*  NKF?_CAPS (CapsLock)
*     if set, the case of the ASCII code doesn't matter
*
*  NKFf_SHIFT (both Shift key flags)
*     if BOTH shift flags are set, the combination of shift key flags in
*     the test key code doesn't matter: only one shift flag has to be set,
*     no matter which one.
*
*  In:   W  D0.w           reference key code
*        W  D1.w           key code to test
*  Out:  W  D0.W           flag: 1 = key codes match
*                                0 = key codes don't match
*        W  CCR            set according content of D0.W
*
*  Reg:  D:01234567   A:01234567   CCR
*          u**.....     **......   =D0.w
*
****************************************************************************
*END

nkc_cmp:       movem.l  d3-d4,-(sp)          ; save registers
               move     d0,d2                ; reference key code
               eor      d1,d2                ; XORed with test key code
               beq      .match               ; identical: key codes match

               bmi.s    .nomatch             ; func flag differs -> don't match

               tst      d0                   ; "function" key?
               bmi.s    .rfunc               ; yes ->


*              printable character

               btst.l   #NKFb_IGNUM,d0       ; ignore numeric keypad flag?
               bne.s    .chkn1               ; yes ->

               btst.l   #NKFb_NUM,d2         ; numeric key flags equal?
               bne.s    .nomatch             ; no ->

.chkn1:        lea      nkc_toupper,a0       ; ^upper case conversion table
               moveq.l  #0,d3                ; clear for word operation
               move.b   d0,d3                ; ASCII code of ref key code
               move.b   (a0,d3),d4           ; get CapsLocked reference key code

               cmp.b    d0,d1                ; ASCII code equal?
               beq.s    .stest               ; yes ->

               btst.l   #NKFb_CAPS,d0        ; no: ignore case?
               beq.s    .nomatch             ; no -> codes don't match

               move.b   d1,d3                ; ASCII of test key code
               cmp.b    (a0,d3),d4           ; CapsLocked comparism
               beq.s    .match               ; equal ->

               bra.s    .nomatch             ; not equal ->

.stest:        btst.l   #NKFb_RESVD,d1       ; check Shift keys?
               beq.s    .match               ; no ->

.stest2:       move     #NKFf_SHIFT,d4       ; Shift key flags
               move     d2,d3                ; XORed key codes
               and      d4,d3                ; shift flags equal?
               beq.s    .match               ; yes ->

               move     d0,d3                ; reference key code
               and      d4,d3                ; isolate Shift flags
               cmp      d3,d4                ; both set?
               bne.s    .nomatch             ; no -> codes don't match

               move     d1,d3                ; check if any pressed in test code
               and      d4,d3
               bne.s    .match               ; yes ->

               bra.s    .nomatch             ; no ->


*              "function key"

.rfunc:        move     d2,d3                ; XORed key codes
               and      #NKFf_ALT|NKFf_CTRL,d3  ; check Alternate and Control
               bne.s    .nomatch             ; different -> codes don't match

               cmp.b    #32,d0               ; special key or character?
               bhs.s    .rchar               ; character->

               cmp.b    d0,d1                ; special key codes equal?
               beq.s    .stest2              ; yes ->

               bra.s    .nomatch             ; no ->

.rchar:        btst.l   #NKFb_IGNUM,d0       ; ignore numeric keypad flag?
               bne.s    .chkn2               ; yes ->

               btst.l   #NKFb_NUM,d2         ; numeric key flags equal?
               bne.s    .nomatch             ; no ->

.chkn2:        cmp.b    d0,d1                ; ASCII codes equal?
               beq.s    .stest               ; yes ->

.nomatch:      movem.l  (sp)+,d3-d4          ; restore registers
               moveq.l  #0,d0                ; codes don't match
               rts                           ; bye

.match:        movem.l  (sp)+,d3-d4          ; restore registers
               moveq.l  #1,d0                ; codes match
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_vlink: link function to XBRA vector list
*  # G U
*
*  This function can be used to change system vectors and let them point
*  to own functions, using a standard method. The vector, which should be
*  changed, is described by the contents of D0.L and D1.W. It can be
*  either a standard vector number, e.g. 2 for the bus error exception
*  vector, or the absolute address of the vector, e.g. $502 for the screen
*  dump vector. The function to install must have the following header:
*
*  .dc.b    "XBRA"         magic longword
*  .dc.b    "myID"         four ASCII character ID of the function
*                          (NKCC uses "NKCC", for example)
*  .dc.l    0              buffer for the old vector content
*  function: ...           start of the function code
*
*  The function should end with:
*
*  move.l   function-4(pc),-(sp)
*  rts
*
*
*  Note: in NKXM_ADR mode, this function automatically switches (temporary)
*        to Supervisor mode to prevent bus errors
*
*
*  In:   R  D0.L           vector descriptor
*        R  D1.W           mode:
*                          NKXM_NUM = D0.L contains a vector number
*                          NKXM_ADR = D0.L contains a vector address
*        R  A0.L           ^function to install (NOT ^XBRA header!)
*  Out:  W  -4(A0.L).L     old content of vector
*
*  Reg:  D:01234567   A:01234567   CCR
*          ***.....     **......    *
*
****************************************************************************
*END


nkc_vlink:     tst      d1                   ; which mode?
               bne.s    .adr                 ; address ->


*              install function by vector number

               move.l   a2,-(sp)             ; save register

               move.l   a0,-(sp)             ; ^function
               move     d0,-(sp)             ; vector number
               move     #5,-(sp)             ; Setexc

               moveq.l  #-1,d1               ; ^function: don't change
               move.l   d1,-(sp)             ;  vector
               move     d0,-(sp)             ; vector number
               move     #5,-(sp)             ; Setexc

               trap     #13                  ; BIOS: get old vector
               addq     #8,sp                ; clean stack

               move.l   4(sp),a0             ; ^function
               move.l   d0,-4(a0)            ; save old vector
               trap     #13                  ; BIOS: install routine
               addq     #8,sp                ; clean stack

               move.l   (sp)+,a2             ; restore register
               rts                           ; bye


*              install function by vector address

.adr:          movem.l  a2-a4,-(sp)          ; save registers

               move.l   a0,a3                ; ^function
               move.l   d0,a4                ; ^vector

               clr.l    -(sp)                ; use usp as ssp
               move     #$20,-(sp)           ; Super
               trap     #1                   ; GEMDOS
               move.l   d0,2(sp)             ; save old sp
               move     #$20,(sp)            ; Super opcode for next call

               move.l   (a4),-4(a3)          ; save old vector content
               move.l   a3,(a4)              ; install function

               trap     #1                   ; GEMDOS (back to User mode)
               addq     #6,sp                ; clean stack

               movem.l  (sp)+,a2-a4          ; restore registers
               rts                           ; bye



*START
****************************************************************************
*
*  nkc_vunlink: unlink function from XBRA vector list
*  # G U
*
*  nkc_vunlink() removes a function which was installed using the XBRA method.
*  For details see nkc_link(). If the XBRA list was corrupted, the function
*  aborts with an error code. This happens, when a non-XBRA routine is
*  installed on the same vector after the nkc_vlink() call of the function
*  to remove.
*
*  Note: the function automatically switches (temporary) to Supervisor mode
*        to prevent bus errors
*
*  In:   R  D0.L           vector descriptor
*        R  D1.W           mode:
*                          NKXM_NUM = D0.L contains a vector number
*                          NKXM_ADR = D0.L contains a vector address
*        R  A0.L           ^function to remove (NOT ^XBRA header!)
*  Out:  W  D0.w           status:
*                          0 = OK
*                          -1 = can't remove (XBRA list corrupted)
*        W  CCR            set according content of D0.w
*
*  Reg:  D:01234567   A:01234567   CCR
*          U**.....     **......   =D0.w
*
****************************************************************************
*END

nkc_vunlink:   movem.l  d6-d7/a2/a6,-(sp)    ; save registers

               move.l   d0,d7                ; save vector descriptor
               move.l   a0,a6                ; save function address
               move     d1,d6                ; save mode
               bne.s    .adr                 ; address mode ->


*              check vector content by number

               moveq.l  #-1,d0               ; get current vector
               move.l   d0,-(sp)
               move     d7,-(sp)             ; vector number
               move     #5,-(sp)             ; Setexc
               trap     #13                  ; BIOS
               addq     #8,sp                ; clean stack

               cmp.l    d0,a6                ; function still on top?
               bne.s    .search              ; no ->

.restore:      move.l   -4(a6),-(sp)         ; restore old vector
               move     d7,-(sp)             ; vector number
               move     #5,-(sp)             ; Setexc
               trap     #13                  ; BIOS
               addq     #8,sp                ; clean stack
               bra.s    .okstat              ; exit


*              check vector content by address

.adr:          clr.l    -(sp)                ; use usp as ssp
               move     #$20,-(sp)           ; Super
               trap     #1                   ; GEMDOS
               move.l   d0,2(sp)             ; save old sp
               move     #$20,(sp)            ; Super opcode for next call

               move.l   d7,a0                ; vector address
               move.l   (a0),d0              ; vector content
               cmp.l    d0,a6                ; function still on top?
               bne.s    .search              ; no ->

               move.l   -4(a6),(a0)          ; yes: remove it
               bra.s    .okstat              ; exit


*              somebody installed a handler (hope he used XBRA ...)

.search:       move.l   d0,a0                ; ^current handler
               move.l   #'XBRA',d0           ; XBRA ID

.find_hnd:     cmp.l    -12(a0),d0           ; XBRA ID before routine?
               bne.s    .error               ; no!!! can't remove function!!

               move.l   -4(a0),a1            ; ^next entry in XBRA list
               cmp.l    a1,a6                ; next entry in list our function?
               beq.s    .found               ; yes ->

               move.l   a1,a0                ; no
               bra.s    .find_hnd            ; continue search

.found:        move.l   -4(a6),-4(a0)        ; yes: remove function

.okstat:       moveq.l  #0,d7                ; no error
               bra.s    .exit

.error:        moveq.l  #-1,d7               ; error! can't unlink function!

.exit:         tst      d6                   ; address mode?
               beq.s    .exit2               ; no ->

               trap     #1                   ; yes: switch to user mode first
               addq     #6,sp                ; with a GEMDOS Super() call

.exit2:        movem.l  (sp)+,d6-d7/a2/a6    ; restore registers
               move     d7,d0                ; status code
               rts                           ; bye




****************************************************************************
*                            LOCAL DATA SECTION                            *
****************************************************************************

               .data

*  exception scan code table for cursor block keys
*
*  first entry:   NKCC key code
*  second entry:  scan code returned by TOS
*
*  the table is terminated with both entries -1

xscantab:      .dc.b    NK_UP       ,  $48   ; cursor up
               .dc.b    NK_DOWN     ,  $50   ; cursor down
               .dc.b    NK_LEFT     ,  $4b   ; cursor left
               .dc.b    NK_LEFT     ,  $73   ; Control cursor left
               .dc.b    NK_RIGHT    ,  $4d   ; cursor right
               .dc.b    NK_RIGHT    ,  $74   ; Control cursor right
               .dc.b    NK_INS      ,  $52   ; Insert
               .dc.b    NK_CLRHOME  ,  $47   ; Clr/Home
               .dc.b    NK_CLRHOME  ,  $77   ; Control Clr/Home
               .dc.b    NK_HELP     ,  $62   ; Help
               .dc.b    NK_UNDO     ,  $61   ; Undo
               .dc.w    -1



*  deadkey table
*
*  first entry:   deadkey option mask (NKSf_D_...), 16 bits shifted down
*  second entry:  ASCII code of deadkey
*  third entry:   ^deadkey match table
*
*  the table is terminated with an option mask of 0

deadtab:       .dc.w    NKSf_D_CIRCUM >> 16
               .dc.w    '^'
               .dc.l    d_circum

               .dc.w    NKSf_D_TILDE >> 16
               .dc.w    '~'
               .dc.l    d_tilde

               .dc.w    NKSf_D_AGUI >> 16
               .dc.w    '\''
               .dc.l    d_agui

               .dc.w    NKSf_D_GRAVE >> 16
               .dc.w    '`'
               .dc.l    d_grave

               .dc.w    NKSf_D_UMLAUT >> 16
               .dc.w    ''
               .dc.l    d_umlaut

               .dc.w    NKSf_D_QUOTE >> 16
               .dc.w    '\"'
               .dc.l    d_umlaut

               .dc.w    NKSf_D_SMOERE >> 16
               .dc.w    ''
               .dc.l    d_smoere

               .dc.w    NKSf_D_CEDIL >> 16
               .dc.w    ','
               .dc.l    d_cedil

               .dc.w    NKSf_D_SLASH >> 16
               .dc.w    '/'
               .dc.l    d_slash

               .dc.w    0



*  deadkey match tables
*
*  first entry:   ASCII code generated by pressing deadkey and key code
*  second entry:  key code (of the key which must be pressed directly after
*                 the deadkey)
*
*  the tables are terminated with both entries 0

d_circum:      .dc.b    '^','^'
               .dc.b    '','a'
               .dc.b    '','e'
               .dc.b    '','i'
               .dc.b    '','o'
               .dc.b    '','u'
               .dc.w    0

d_tilde:       .dc.b    '~','~'
               .dc.b    '','n'
               .dc.b    '','N'
               .dc.b    '','a'
               .dc.b    '','o'
               .dc.b    '','A'
               .dc.b    '','O'
               .dc.w    0

d_agui:        .dc.b    '\'','\''
               .dc.b    '','e'
               .dc.b    '','E'
               .dc.b    '','a'
               .dc.b    '','i'
               .dc.b    '','o'
               .dc.b    '','u'
               .dc.w    0

d_grave:       .dc.b    '`','`'
               .dc.b    '','a'
               .dc.b    '','e'
               .dc.b    '','i'
               .dc.b    '','o'
               .dc.b    '','u'
               .dc.b    '','A'
               .dc.w    0

d_umlaut:      .dc.b    '',''
               .dc.b    '\"','\"'
               .dc.b    '','a'
               .dc.b    '','e'
               .dc.b    '','i'
               .dc.b    '','o'
               .dc.b    '','u'
               .dc.b    '','y'
               .dc.b    '','A'
               .dc.b    '','O'
               .dc.b    '','U'
               .dc.w    0

d_smoere:      .dc.b    '',''
               .dc.b    '','a'
               .dc.b    '','A'
               .dc.w    0

d_cedil:       .dc.b    ',',','
               .dc.b    '','c'
               .dc.b    '','C'
               .dc.w    0

d_slash:       .dc.b    '/','/'
               .dc.b    '','o'
               .dc.b    '','O'
               .dc.w    0



               .if      !(^^defined NKCNOGEM)

*  AES parameter block

aespb:         .dc.l    _contrl
               .dc.l    _global
               .dc.l    _intin
               .dc.l    _intout
               .dc.l    _addrin
               .dc.l    _addrout

*  VDI parameter block

vdipb:         .dc.l    _contrl
               .dc.l    _intin
               .dc.l    _ptsin
               .dc.l    _intout
               .dc.l    _ptsout


*  double click time table (values given in 1/200 s)

timetab:       .dc.l    120                  ; 0.600 s
               .dc.l    90                   ; 0.450 s
               .dc.l    70                   ; 0.350 s
               .dc.l    50                   ; 0.250 s
               .dc.l    40                   ; 0.200 s

               .endif



*START
****************************************************************************
*                           GLOBAL DATA SECTION                            *
****************************************************************************
*END

               .data

*START
*  lower case to upper case conversion table
*  (array of 256 unsigned bytes)

nkc_toupper:
*END
               .dc.b    $00,$01,$02,$03,$04,$05,$06,$07
               .dc.b    $08,$09,$0a,$0b,$0c,$0d,$0e,$0f
               .dc.b    $10,$11,$12,$13,$14,$15,$16,$17
               .dc.b    $18,$19,$1a,$1b,$1c,$1d,$1e,$1f
               .dc.b    " !\"#$%&\'()*+,-./0123456789:;<=>?"
               .dc.b    "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
               .dc.b    "`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~"
               .dc.b    ""
               .dc.b    ""
               .dc.b    ""
               .dc.b    ""


*START
*  upper case to lower case conversion table
*  (array of 256 unsigned bytes)

nkc_tolower:
*END
               .dc.b    $00,$01,$02,$03,$04,$05,$06,$07
               .dc.b    $08,$09,$0a,$0b,$0c,$0d,$0e,$0f
               .dc.b    $10,$11,$12,$13,$14,$15,$16,$17
               .dc.b    $18,$19,$1a,$1b,$1c,$1d,$1e,$1f
               .dc.b    " !\"#$%&\'()*+,-./0123456789:;<=>?"
               .dc.b    "@abcdefghijklmnopqrstuvwxyz[\\]^_"
               .dc.b    "`abcdefghijklmnopqrstuvwxyz{|}~"
               .dc.b    ""
               .dc.b    ""
               .dc.b    ""
               .dc.b    ""



****************************************************************************
*                             LOCAL BSS SECTION                            *
****************************************************************************

               .bss


*              key code handler

pkey_unshift:  .ds.l    1                    ; ^unshifted key table
pkey_shift:    .ds.l    1                    ; ^shift key table
pkey_caps:     .ds.l    1                    ; ^CapsLock table
pshift:        .ds.l    1                    ; ^shift state system variable
sflags:        .ds.l    1                    ; special key flags
prvkey:        .ds.w    1                    ; previous key code
cntold:        .ds.b    1                    ; save buffer for <conterm>
prvvalid:      .ds.b    1                    ; non-zero: prvkey is valid
               .even


*              button event handler

               .if      !(^^defined NKCNOGEM)

bvar:          .ds.b    BI                   ; button event info block
tendtime:      .ds.l    1                    ; timer event end value
prvtime:       .ds.l    1                    ; previous timer event value
vdihnd:        .ds.w    1                    ; handle of VDI workstation
multibuf:      .ds.w    16+2                 ; buffer for evnt_multi parameters
buthnd:        .ds.b    1                    ; non-zero:button handler installed
               .even

               .endif


*              other stuff

sysclock:      .ds.l    1                    ; copy of 200 Hz system clock
timerflag:     .ds.b    1                    ; non-zero:200 Hz timer installed
               .even



*START
****************************************************************************
*                            GLOBAL BSS SECTION                            *
****************************************************************************
*END

               .bss

*START
*  GEM parameter block
*  (structure GEMPARBLK)

               .if      !(^^defined NKCNOGEM)

               .if      !(^^defined NKCOWNPB)
_GemParBlk:                                  ; GEM parameter block
               .endif

_contrl:       .ds.w    11                   ; control array
_global:       .ds.w    80                   ; global array
_intin:        .ds.w    128                  ; integer input array
_ptsin:        .ds.w    128                  ; pointers input array
_intout:       .ds.w    128                  ; integer output array
_ptsout:       .ds.w    128                  ; pointers output array
_addrin:       .ds.l    128                  ; address input array
_addrout:      .ds.l    6                    ; address output array

* note: the original TC declaration of _GemParBlk defined the size of the
*       array _addrout to be 128 longs. I examined the GEM library (module
*       _GEMPARB.O) and found out the real size - only six longs! Maybe
*       this changes in future versions of TC, so you better check it
*       (I tested versions 1.10, 2.0 and 2.05).
*END

               .endif



*EOF

