What key did I press? The task is to write code to identify which key is pressed on the keyboard. You can assume that only one key is pressed at a time and that there is a standard US keyboard layout. That is the layout with the @ over the 2.

Your code should output a unique identifier for any key pressed. This includes PrtScn, Scroll Lock, Pause, left Shift, right Shift, left Ctrl, right Ctrl, Caps Lock, Tab, Enter, Enter on the number pad, Num Lock, Insert, Ins on the number pad, Backspace, Del, F1...F12, Esc, left Windows key, right Windows key, Alt, AltGr, application key (context menu) and so on.  

Your code should carry on  waiting for key presses and outputting their identity until it is killed.  It should output the identifier as soon as a key is released however. It shouldn't carry out any other action from the key presses it receives and it shouldn't output anything apart from the unique identifier.

In your answer, please show what you code outputs for the following key presses: Tab, Pause, Enter, Enter on the number pad, left Windows key, right Windows key, Insert and Ins on the number pad. 

If you have a very different keyboard, the challenge is still to output a different identifier for every single key on your keyboard.
x86 machine code, DOS executable, 29* 28 bytes

FAE464D0E873FAE460D0E073F4D41005212192B402CD2188F2CD21EBE3


This is a COM executable for MS-DOS, it requires a IBM PC compatible hardware.
Particularly an 8042 PS/2 controller or more likely an emulation of it through SMM.
Long story short, it should work out of the box in any mainstream PC.  

The source code is

BITS 16

 ;Disable the interrupts so we don't compete with the IRQ 1 (the 8042 main
 ;device IRQ) handler (the ISR n. 9 by default) for the reading of the codes.
 cli

_wait:

 ;Is 'Output buffer full (OBF)' bit set?

 in al, 64h                ;Read the 8042 status register             
 shr al, 1                 ;Move bit 0 (OBF) into the carry flag

jnc _wait                  ;Keep spinning if CF = OBF not set

 ;Read the scan code S
 in al, 60h

 ;Is S a break code?

 shl al, 1                 ;Bit 7 is set if it is
jnc _wait

 ;PART 2

 ;AL = S mod 10h := y, AH = S / 10h := x
 aam 16
 add ax, 2121h             ;Make both quantities in the printable ASCII range (skip space though)

 ;Print y
 xchg dx, ax
 mov ah, 02h
 int 21h                   ;int 21/ah=02 prints the char in DL

 ;DH is still valid here, it holds x. We print it now
 mov dl, dh
 int 21h

 ;Never terminate
jmp _wait


I've divided the program into two parts.  

The first part deals with the reading of the scancodes. Scancodes are numeric values associated with every key.
Note that these are hardware code, they don't depend on the OS or the charset. They are like an encoded pair (column, row) of the key.
Every key has a scancode, even those non-standard weird function keys found on some keyboard (e.g. the "open calc" key).
Some key has multi-byte scancode, they have prefixes designed to make the stream decodificable by just looking at the sequence of bytes.
So each key gets its unique identifier, even CTRL, SHIFT, WinKeys and so on.

Only the "break codes", sent when a key is released, are processed, the "make codes" are ignored.
The formers have the higher bit (bit 7 for a byte) set, so it's easy to recognise them.  

The second part deals with the printing of a byte.
Printing is always lengthy in assembly, we have no builtins.
To keep it short, and since it was required to write an identifier of the key, I abandoned decimal or hex numerals in favour of a personal encoding.  

A byte xy, where x is the higher nibble and y the lower is printed as two successive chars c0 and c1 defined as:

c0 = 0x21 + y
c1 = 0x21 + x

Note that the lower nibble is printed first (this spared me a swap).
The rationale is to map the each one of the 16 possible values of a nibble into consecutive ASCII characters from '!'.
Simply put this is a hex numeral but with


The nibbles swapped  
!"#$%&amp;'()*+,-./01 as digit(als) instead of 0123456789abcdef


Running it in DOSBox and pressing some random key (some of which is a special key but note that as a Windows process DOSBox can't capture all the keys) yields



Note that this program never terminates (further, it takes complete control of the PC by disabling the interrupts) as I believe was intended by the question (simply there is no killing of processes in DOS).  



* Reduced thank to CodyGray.