Detect/receive multiple key-presses at the same time in DOS?
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 both players in one time and Im trying a lot but I don't think I have a way to doing it.
I heard that I need to read from the buffer directory and see what keys are there and read each one individually but I don't really know how to do this.
assembly x86 game-engine tasm dosbox
add a comment |
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 both players in one time and Im trying a lot but I don't think I have a way to doing it.
I heard that I need to read from the buffer directory and see what keys are there and read each one individually but I don't really know how to do this.
assembly x86 game-engine tasm dosbox
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55
add a comment |
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 both players in one time and Im trying a lot but I don't think I have a way to doing it.
I heard that I need to read from the buffer directory and see what keys are there and read each one individually but I don't really know how to do this.
assembly x86 game-engine tasm dosbox
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 both players in one time and Im trying a lot but I don't think I have a way to doing it.
I heard that I need to read from the buffer directory and see what keys are there and read each one individually but I don't really know how to do this.
assembly x86 game-engine tasm dosbox
assembly x86 game-engine tasm dosbox
edited May 6 '16 at 2:13
Peter Cordes
133k18203341
133k18203341
asked May 4 '16 at 6:06
Ori MosheOri Moshe
1
1
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55
add a comment |
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55
add a comment |
1 Answer
1
active
oldest
votes
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!
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f37019711%2fdetect-receive-multiple-key-presses-at-the-same-time-in-dos%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
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!
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
add a comment |
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!
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
add a comment |
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!
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!
answered May 4 '16 at 16:49
Margaret BloomMargaret Bloom
22.8k53270
22.8k53270
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
add a comment |
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
sometimes the terms make (button down makes a connection) and break (button released, breaks connection) are used. and absolutely this is the only way to do it you have to watch for makes and breaks for the keys you are interested and keep your own local state table, and then run your program off of that state table for keys that can be pressed "at the same time", for other keys, quitting the game or whatever, just the make should do.
– old_timer
May 6 '16 at 2:35
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f37019711%2fdetect-receive-multiple-key-presses-at-the-same-time-in-dos%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Are you able to get one key at a time?
– Buddy
May 4 '16 at 6:28
Yes, it's it 16h or 21. But I need to get 2 keys at once - not happening, get only the first, and then I need to call two procedures to move two players at almost once
– Ori Moshe
May 4 '16 at 7:00
You might want to add information about the OS, platform etc.
– Sami Kuhmonen
May 4 '16 at 7:03
I'm writing in TASM8086 in windows, I'm using dosbox
– Ori Moshe
May 4 '16 at 7:54
Post the code you have so others may give you opinion and suggestions.
– Frank C.
May 4 '16 at 7:55