Using the raw keyboard mode under Linux

by Karsten Scheibler

Remark (a):
Save this page as rawkb.html and use the following command to extract the source code with a sample Makefile:
sh -c "( mkdir rawkb && cd rawkb && awk '/^<!--eof/{d=0}{if(d)print>f}/^<!--sof/{f=\$2;d=1}' ) < rawkb.html"
Remark (b):
Some browsers try to be super smart and reformat the html code so you may have trouble to extract the files. This is the right time to learn more about wget, a tool to download files via http or ftp ;-)
Remark (c):
Some versions of nasm are known to generate broken code. I have used nasm-0.98 for this example which worked for me.

If you read something from STDIN, the read bytes are normally in ASCII format. In some cases it is useful to get the keyboard input as raw as possible. Imagine: Your program should react on the Alt or Ctrl Key with menu highlightning. In raw keyboard mode such things are possible. In this mode you get directly the untranslated keycodes coming from your keyboard. It doesn't generate ASCII like codes it is far away from that. If you press a key on your keyboard two codes are send to the controller in your PC a so called make code if you press a key and a break code if you release a key. The most generated codes are 1 byte codes. It is easy to recognize which sort of code you have received: bit 7 clear = make, bit 7 set = break.

Examples:

But there are also codes longer than 1 byte, such codes are escaped with a leading 0e0h. Example: RIGHT CTRL: 0e0h 01dh (make), 0e0h 09eh (break). Three byte codes are escaped with 0e1h (there should be only one such key on your keyboard: PAUSE. This key is a little bit special because it generates make and break codes without any delay if you press it).

The example code below will simply dump the hex code of every key to the terminal. Use ESC to exit the program. It will only work on local console.

The translation to ASCII Codes in the kernel is done via tables (the same tables are changed if you run the command loadkeys, which loads the correct keytable for your country). You can reach this tables with the ioctl's KDGKBENT and KDSKBENT. They expect a pointer to a structure of the type kbentry (see /usr/src/linux/include/linux/kd.h). If you want to get an entry you have to set the values kb_table and kb_index, if you want to set an entry kb_value must also be set. kb_index is the raw keycode, kb_table contains bits for the keys Shift, Alt, AltGr and Ctrl (it is some kind of table selector, for making things like Shift+A easier). kb_value contains the ASCII Code. For simple keyhandling this should be enough, the real key translation in the kernel seems to be a little bit more complex (think of escape sequences if you press a cursor key), in such cases kb_value contains some magic values.

Like all low level accesses you should try as hard as you can to restore the original state, because if you fail the keyboard stays in raw mode and this means the local console is unusable (Ctrl+Alt+Del doesn't work, no terminal switching etc.). Therefore you should catch all signals, which can terminate your program (including SIGSEGV) with an exit handler.

Note:
The ioctl KDGKBMODE expects like the most ioctl's an address as third argument, but KDSKBMODE expects directly the mode (K_RAW for the raw keyboard mode K_XLATE for the normal mode). There is also a K_MEDIUMRAW available but i let it up to you to figure out the differences between K_RAW and K_MEDIUMRAW ;-). To find out more about available ioctls you may consult the console_ioctls manpage. Because i have already mentioned a manpage i will also mention another one: console_codes. There you will find escape codes for changing terminal colors etc.

short procedure for switching to raw keyboard mode:

  1. use the TIOCLINUX or VT_GETMODE ioctl's to determine if you your program was called from local console.
  2. save the current terminal state (ioctl's TCGETS and KDGKBMODE and friends)
  3. set signal handlers
  4. set the terminal values:
    1. switch off input flags: ISTRIP, INLCR, ICRNL, IGNCR, IXON, IXOFF.
    2. switch off local flags: ECHO, ICANON, ISIG.
    3. Disable kernel keyboard translation.

In this example we use STDIN as file descriptor for sys_ioctl. This is ok as long as nobody redirects it to a file or pipe. If you want that the program even works in this cases: open /dev/tty and use that file descriptor instead.

If you find mistakes or have suggestions, mail me.


;****************************************************************************
;****************************************************************************
;*
;* USING THE RAW KEYBOARD MODE UNDER LINUX
;*
;* written by Karsten Scheibler, 2003-DEC-08
;*
;****************************************************************************
;****************************************************************************





global rawkb_start



;****************************************************************************
;* some assign's
;****************************************************************************
%assign SYS_READ			3
%assign SYS_WRITE			4
%assign SYS_OPEN			5
%assign SYS_CLOSE			6
%assign SYS_IOCTL			54

%assign TCGETS				0x00005401
%assign TCSETSW				0x00005403
%assign KDGKBMODE			0x00004b44
%assign KDSKBMODE			0x00004b45
%assign K_RAW				0

%assign ISTRIP				000400q
%assign INLCR				000100q
%assign IGNCR				000200q
%assign ICRNL				000400q
%assign IXON				002000q
%assign IXOFF				010000q

%assign ISIG				1
%assign ICANON				2
%assign ECHO				8

%assign STDIN				0
%assign STDOUT				1

%assign O_RDWR				2

%assign STATE_TERMIOS_SAVED		0x01
%assign STATE_KBMODE_SAVED		0x02

					struc	ttermios
					alignb	4
termios_input_flags:			resd	1
termios_output_flags:			resd	1
termios_control_flags:			resd	1
termios_local_flags:			resd	1
termios_line_discipline:		resb	1
termios_control_characters:		resb	64
					endstruc



;****************************************************************************
;* data
;****************************************************************************
section .data
					align	4
tty_state:				dd	0

section .bss
	align	4
tty_termios_saved:			resb	ttermios_size
tty_kbmode_saved:			resd	1
tty_termios:				resb	ttermios_size
hex_number:				resd	1
character:				resb	1



;****************************************************************************
;* rawkb_start
;****************************************************************************
section .text
rawkb_start:

	;save terminal state

	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, KDGKBMODE
	mov	dword edx, tty_kbmode_saved
	int	byte  0x80
	test	dword eax, eax
	js	near  rawkb_error
	or	dword [tty_state], STATE_KBMODE_SAVED

	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, TCGETS
	mov	dword edx, tty_termios_saved
	int	byte  0x80
	test	dword eax, eax
	js	near  rawkb_error
	or	dword [tty_state], STATE_TERMIOS_SAVED

	;set terminal values

	cld
	mov	dword ecx, ttermios_size
	mov	dword esi, tty_termios_saved
	mov	dword edi, tty_termios
	rep movsb

	and	dword [tty_termios + termios_input_flags], (~(ISTRIP | INLCR | ICRNL | IGNCR | IXON | IXOFF))
	and	dword [tty_termios + termios_local_flags], (~(ECHO | ICANON | ISIG))

	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, TCSETSW
	mov	dword edx, tty_termios
	int	byte  0x80
	test	dword eax, eax
	js	near  rawkb_error

	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, KDSKBMODE
	mov	dword edx, K_RAW
	int	byte  0x80
	test	dword eax, eax
	js	near  rawkb_error

	;display the hex codes of all pressed keys, ESC exits

.display:
	mov	dword eax, SYS_READ
	mov	dword ebx, STDIN
	mov	dword ecx, character
	mov	dword edx, 1
	int	byte  0x80
	xor	dword ebx, ebx
	xor	dword ecx, ecx
	mov	byte  bl, [character]
	mov	byte  cl, bl
	cmp	byte  bl, 0x01
	je	near  rawkb_end
	shr	dword ecx, 4
	and	byte  bl, 0x0f
	mov	dword eax, 0x0a680000
	mov	byte  ah, [.hex_table + ebx]
	mov	byte  al, [.hex_table + ecx]
	mov	dword [hex_number], eax
	mov	dword eax, SYS_WRITE
	mov	dword ebx, STDOUT
	mov	dword ecx, hex_number
	mov	dword edx, 4
	int	byte  0x80
	jmp	short .display

.hex_table:				db	"0123456789abcdef"



;****************************************************************************
;* rawkb_restore ************************************************************
;****************************************************************************
section .text
rawkb_restore:
	test	dword [tty_state], STATE_KBMODE_SAVED
	jz	short .no_kbmode
	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, KDSKBMODE
	mov	dword edx, [tty_kbmode_saved]
	int	byte  0x80
.no_kbmode:
	test	dword [tty_state], STATE_TERMIOS_SAVED
	jz	short .no_termios
	mov	dword eax, SYS_IOCTL
	mov	dword ebx, STDIN
	mov	dword ecx, TCSETSW
	mov	dword edx, tty_termios_saved
	int	byte  0x80
.no_termios:
	ret



;****************************************************************************
;* rawkb_error **************************************************************
;****************************************************************************
section .text
rawkb_error:
	call	near  rawkb_restore
	xor	dword eax, eax
	inc	dword eax
	mov	dword ebx, eax
	int	byte  0x80



;****************************************************************************
;* rawkb_end
;****************************************************************************
section .text
rawkb_end:
	call	near  rawkb_restore
	xor	dword eax, eax
	mov	dword ebx, eax
	inc	dword eax
	int	byte  0x80
;*********************************************** linuxassembly@unusedino.de *