Detect/receive multiple key-presses at the same time in DOS?

前端 未结 1 1544
醉酒成梦
醉酒成梦 2020-12-12 06:20

I\'m in the middle of writing air hockey in tasm and I have encounter a problem which is how I get two keys/clicks at once because I need to get both click at once to move b

相关标签:
1条回答
  • 2020-12-12 06:43

    Do you mind working with scancodes?

    I know this is not the simple, drop-in, solution you were looking for but I'm afraid there is none.
    So I'm writing this in the hope that, if not you, some other troubled coder can find something useful.

    I also know you write for TASM but I forgot that and started with NASM, converting however should be very easy (just add segments declarations, take out the segment register from the brackets and add PTR).


    It seems that from an hardware perspective the keyboard repeats only the last pressed key1, so if two players are pressing two keys, only one is actually sent by the keyboard.
    However the software handle multiple keys at once everywhere, how do they do it?

    The trick is that the keyboard sends two codes (scancodes) when a key is pressed: one when the key goes down (and many others while it is kept down) and one when it is released.
    The software can thus tell when a key is down without further notice from the keyboard2.

    I cannot find any interrupt service that handle key up and down events.

    The only solution is handling scancode directly.

    While the 8042 chip is very simple, there is not need to get dirty with IO instruction.
    Without explaining how the 8259A and the IRQ mapping works, it's enough to say that the interrupt 15h/AH=4fh is called with the scancode in AL when a key is pressed/released.

    We can intercept that interrupt and check if the scancode is for a key down or a key up.
    Release scancodes have bit7 set.
    We can have an array of 128 bytes, each for any possible scancode value (bit0-6)3 and store in each element 0ffh if the scancode indicates a press or 00h if it indicates a release.

    It is also useful keep a count of the processed scancodes, so a program can wait for new scancodes with trivial arithmetic.


    Now, I imagine you are still confused.
    I have written a demo.

    The program below, for NASM, waits for you to press both a and d keys to exit.
    Warning Since we are using scancodes, this is keyboard layout dependent!

    BITS 16
    ORG 100h        ;COM
    
     ;Setup ISR for the scancode 
     call init 
    
     ;Clear screen
     mov ax, 03h
     int 10h 
    
     ;Print command
     mov ah, 09h 
     mov dx, strCommand
     int 21h 
    
    _main:
     ;Wait for a change in the scancode tables
     call wait_for_scancode
     ;Remove unused keystrokes
     call remove_keystrokes
    
     ;Check if a is pressed 
     mov al, 1eh           ;a
     call is_scancode_pressed
     jz _main 
    
     ;Check if 'd' is pressed 
     mov al, 20h           ;d 
     call is_scancode_pressed
     jz _main 
    
     ;Both are pressed, print bye and ...
     mov ah, 09h 
     mov dx, strDone
     int 21h 
    
     ;... restore the ISR and ...
     call dispose 
    
     ;... exit
     mov ax, 4c00h
     int 21h
    
     strCommand db "Press 'a' and 'd' to exit", 13, 10, 24h 
     strDone    db "Bye",13,10,13,10,24h 
    
     ;Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  L  
     ;  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll
     ;Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  Ll  L
    
     ;S C A N C O D E   F U N C T I O N S
    
     ;Set the ISR 
    init:
      push ax  
    
      mov ax, cs
      mov WORD [old_isr_15 + 02h], ax          
      ;old_isr_15 is now a far pointer to new_isr_15
    
      call swap_isr_15          ;Swap the current isr15 with the one in old_isr_15
    
      pop ax 
      ret
    
      ;Restore the original ISR 
    dispose:
      call swap_isr_15          ;Swap the current isr15 with the one in old_isr_15                  
    
      ret  
    
      ;Swap the pointer in the IVT for int 15h with the pointer in old_isr_15
    swap_isr_15:
      push eax
      push es 
    
      xor ax, ax 
      mov es, ax 
    
      cli
    
      mov eax, DWORD [es: 15h*4]
      xchg eax, DWORD [old_isr_15]
      mov DWORD [es: 15h*4], eax 
    
      sti
    
      pop es 
      pop eax
    
      ret  
    
      ;Wait for a change in the scancode table
    wait_for_scancode:
     cli                           ;Prevent the ISR from messing things up 
    
     ;At least one scancode processed?
     cmp WORD [new_scancode], 0 
     jne _wfs_found                ;Yes
    
     ;No, restore interrupt so the CPU can process the prending ones
     sti
    jmp wait_for_scancode
    
     ;New scancode, decrement the count and restore interrupts
    _wfs_found:
     dec WORD [new_scancode]
     sti 
    
     ret
    
      ;THe BIOS is still saving keystrokes, we need to remove them or they 
      ;will fill the buffer up (should not be a big deal in theory).
    remove_keystrokes:
     push ax
    
     ;Check if there are keystrokes to read.
     ;Release scancodes don't generate keystrokes 
    _rk_try:
     mov ah, 01h 
     int 16h
     jz _rk_end      ;No keystrokes present, done 
    
     ;Some keystroke present, read it (won't block)
     xor ah, ah 
     int 16h
    jmp _rk_try
    
    _rk_end:
     pop ax 
     ret 
    
     ;Tell if a scancode is pressed
     ;
     ;al = scancode  
     ;ZF clear is pressed 
    is_scancode_pressed:
      push bx
    
      movzx bx, al 
      cmp BYTE [scancode_status + bx], 0 
    
      pop bx 
      ret 
    
     ;AL = scancode 
    new_isr_15:
     ;Check for right function
     cmp ah, 4fh
     jne _ni15_legacy
    
     ;Save used regs
     push bx
     push ax
    
    
     movzx bx, al            ;BX = scancode 
     and bl, 7fh             ;BX = scancode value
    
     sar al, 07h             ;AL = 0ffh if scancode has bit7 set (release), 00h otherwise
     not al                  ;AL = 00h if scancode has bit7 set (release), 0ffh otherwise
    
     ;Save the scancode status
     mov BYTE [cs:bx + scancode_status], al 
     ;Increment the count
     inc WORD [cs:new_scancode]
    
     pop ax
     pop bx 
    
    _ni15_legacy:   
     ;This is a far jump, in NASM is simply jmp FAR [cs:old_isr_15]
     ;Ended up this way for debug
     push WORD [cs: old_isr_15 + 02h] 
     push WORD [cs: old_isr_15] 
     retf
    
     ;Original ISR
    old_isr_15                      dw new_isr_15, 0  
    
     ;Scan code status table
    scancode_status     TIMES 128   db 0
     ;Scan code count 
    new_scancode                    dw 0
    

    All you need to use is:

    • init to set up the scancode snooping.
    • dispose to tear down the scancode snooping.
    • is_scancode_pressed to know if a key is being held down.

    Everything else, including wait_for_scancode and remove_keystrokes is accessory and is there just to make your program behave very nice.

    If you want to find the scancode associated with a key, you can use this other program that show you a table with the pressed scancodes (press ESC to exit).
    For example, if I press a and d I get the values used in the demo

    It is just a variant of the demo above.


    1I create this boot program (intended for NASM) to test my hypothesis, at least in my hardware.

    2 Effectively speaking the repeat feature is useful only in typing software, every other application, like games, that explicitly check for keys status don't need it.

    3 There are actually extended (multi byte scancode) but they are ignored here. Stick with plain keys!

    0 讨论(0)
提交回复
热议问题