;
; akbd.s
;
; Keyboard handling (MDW)
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Sapphire library.
;
; Sapphire is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; Sapphire is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with Sapphire.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

;----- External dependencies ------------------------------------------------

		GET	sapphire:keyMap
		GET	sapphire:intKeys

;----- Macros ---------------------------------------------------------------

		MACRO
$label		KEYCMP	$reg,$key

 [ $key > &100
		CMP	$reg,# ( $key - &100 ) << 8
 |
		CMP	$reg,# $key
 ]

		MEND

		MACRO
$label		KEYRNG	$reg,$low,$high

 [ $low > &100
 		SUB	R14,$reg,# ( $low - &100 ) << 8
 		CMP	R14,# ( $high - $low - &100 ) << 8
 |
		SUB	R14,$reg,# $low
		CMP	R14,# $high-$low
 ]

		MEND

;----- Main code ------------------------------------------------------------

		AREA	|Sapphire$$Code|,CODE,READONLY

; --- akbd_test ---
;
; On entry:	R0 == internal key number to test
;
; On exit:	CS if key was pressed, CC otherwise
;
; Use:		Informs you whether a given key is currently being pressed.

		EXPORT	akbd_test
akbd_test	ROUT

		STMFD	R13!,{R0-R2,R14}	;Save some registers
		MOV	R2,#&FF			;Test internal key number
		EOR	R1,R0,#&FF		;Wierd translation for key
		MOV	R0,#&81			;Test for key pressed
		SWI	OS_Byte			;Do the test nicely
		CMP	R1,#0			;Is it pressed down?
		LDMFD	R13!,{R0-R2,R14}	;Restore registers anyhow
		ORRNES	PC,R14,#C_flag		;Yes -- set C on exit
		BICEQS	PC,R14,#C_flag		;No -- clear C on exit

		LTORG

; --- akbd_translate ---
;
; On entry:	R0 == Wimp key number
;
; On exit:	R0 == Straylight extended keyset key number
;
; Use:		Translates a Wimp key number into one of the more specific
;		Straylight key numbers.

akbd__s		EQU	(1<<0)
akbd__c		EQU	(1<<1)

		EXPORT	akbd_translate
akbd_translate	ROUT

		; --- Deep magic warning ---
		;
		; This routine is really not very pleasant.  If you can,
		; avoid messing about with it.  It contains several `clever'
		; tricks to try and reduce the code size and time taken,
		; which will probably end up being utterly incomprehensible.

		STMFD	R13!,{R6-R10,R14}	;Save some registers away
		MOV	R10,R0			;Look after the keynumber
		MOV	R9,R10			;Keep another copy

		; --- Do a clever translation on the Wimp key ---
		;
		; The idea is that instead of distinguishing the two keymap
		; halves by whether but 8 is set, we distinuguish by which
		; byte the value is in -- byte 0 for bottom-half values and
		; byte 1 for top-half values.  This has an advantage in that
		; *all* key values are now available as immediate constants.
		;
		; There is in fact a slight problem with this: &100 and &000
		; become indistinguishable.  This is OK, though, since the
		; value &100 cannot be returned by the Wimp.

		CMP	R10,#&100		;Is it in the top half?
		MOVGE	R10,R10,LSL #8		;Yes -- move it into byte 2
		ANDGE	R10,R10,#&FF00		;And clear the top bit

		; --- Handle PageUp/PageDown nicely ---

		BIC	R14,R10,#&3100		;Clear Shift and Control bits
		CMP	R14,#&8E00		;Is it PageUp/PageDown?
		BEQ	%50			;Yes -- handle it specially

		; --- Remove some immediate no-hopers ---

		KEYCMP	R10,&180		;Is it really big?
		BGE	%99			;Yes -- skip everything
		KEYRNG	R10,'a','z'		;Is it a simple character?
		BLS	%99			;Yes -- skip everything
		KEYRNG	R10,'A','Z'		;Check upper case too
		BLS	%99			;Yes -- skip everything
		KEYRNG	R10,'[',']'		;Go for the brackets too
		BLS	%99			;Skip if in there

		; --- Work out the modifiers now ---

		MOV	R8,#0			;No modifiers found yet
		MOV	R0,#intk_Shift		;Check for shift held down
		BL	akbd_test		;Is Shift being pressed?
		ORRCS	R8,R8,#akbd__s		;Yes -- set the bit then
		MOV	R0,#intk_Ctrl		;Check for ctrl too
		BL	akbd_test		;Is Ctrl being pressed?
		ORRCS	R8,R8,#akbd__c		;Yes -- set that bit

		; --- Various key checks ---

		KEYCMP	R10,key_Delete		;Check for Delete key
		BEQ	akbd__del		;Yes -- handle it

		KEYCMP	R10,' '			;Check for Space key
		BEQ	akbd__space		;Yes -- handle it

		; --- Handle the mappings to `Return' ---

		KEYCMP	R10,&0D			;Check for Return key
		BNE	%10			;No -- skip ahead a little
		MOV	R0,#intk_kEnter		;Check for Enter key
		BL	akbd_test		;Scan the keyboard again
		BCS	akbd__enter		;Yes -- handle it
		MOV	R0,#intk_M		;Check for the M key
		BL	akbd_test		;Scan the keyboard again
		BCS	%10			;If so, skip to ctrl keys

		ADD	R9,PC,#0		;Point to the table
		B	%90			;And sort that out nicely
		DCW	key_Return,key_sReturn
		DCW	key_cReturn,key_scReturn

akbd__enter	ADD	R9,PC,#0		;Point to the table
		B	%90			;And sort that out nicely
		DCW	key_kEnter,key_skEnter
		DCW	key_ckEnter,key_sckEnter

akbd__space	ADD	R9,PC,#0		;Point to the table
		B	%90			;And sort that out nicely
		DCW	key_Space,key_sSpace
		DCW	key_cSpace,key_scSpace

akbd__del	ADD	R9,PC,#0		;Point to the table
		B	%90			;And sort that out nicely
		DCW	key_Delete,key_sDelete
		DCW	key_cDelete,key_scDelete

		; --- Handle control keys now (at last!) ---

10		CMP	R10,#&1B		;Is it a control key?
		BGE	%20			;No -- skip forwards then

		MOV	R14,#&00BB		;Part of the weird key mask
		ORR	R14,R14,#&0300		;Rest of the mask
		MOV	R7,#1			;Will be shifted about
		TST	R14,R7,LSL R10		;Check the weirdness bit then
		BEQ	%15			;If not *very* strange, skip

		CMP	R10,#8			;Is it a backspace?
		BNE	%11			;No -- skip ahead
		MOV	R0,#intk_H		;Could be a ctrl-H
		BL	akbd_test		;So check for that then
		MOVCC	R0,#intk_8		;Or a ctrl-8
		BLCC	akbd_test		;So check for that too
		BCS	%11			;Either -- skip ahead

		ADD	R9,PC,#0		;Point to table
		B	%90			;Handle table nicely
		DCW	key_Backspace,key_sBackspace
		DCW	key_cBackspace,key_scBackspace

		; --- Now handle keys which could be numbers ---

akbd__numKeys	DCB	0,intk_1,0,intk_3,intk_4
		DCB	intk_5,0,intk_7,intk_8,intk_9

11		CMP	R10,#0			;Is it a null code?
		BEQ	%13			;Yes -- it could be anything!

		ADR	R14,akbd__numKeys	;Point to internal key table
		LDRB	R0,[R14,R10]		;Load the correct number
		BL	akbd_test		;Test the key's state
		BCC	%15			;It's not that weird then
12		TST	R8,#akbd__s		;Was Shift being pressed?
		ADDNE	R9,R9,#&150		;Yes -- add in offset
		ADDEQ	R9,R9,#&130		;No -- add different offset
		B	%99			;And come right out of this

		; --- Handle a 0 key number ---

13		MOV	R0,#intk_0		;Is it control-0?
		BL	akbd_test		;Check the keyboard
		MOVCC	R9,#2			;No -- then it's really 2
		B	%12			;Now handle Shift status

		; --- It's a sensible control key ---

15		TST	R8,#akbd__s		;Is shift held down?
		ADDNE	R9,R9,#&100		;Yes -- add the offset nicely
		B	%99			;And return the key number

		; --- Try to handle number key presses ---

akbd__padTab	DCB	intk_k0,intk_k1,intk_k2,intk_k3,intk_k4
		DCB	intk_k5,intk_k6,intk_k7,intk_k8,intk_k9

20		SUB	R0,R10,#'0'		;Chop off bottom limit
		CMP	R0,#9			;Is it a numeric key?
		BHI	%30			;No -- skip ahead then
		ADR	R14,akbd__padTab	;Point to key number table
		LDRB	R0,[R14,R0]		;Load the correct key number
		BL	akbd_test		;Check the key number nicely
		ADDCS	R9,R9,#&1C0 - '0'	;If on keypad, add offset
		ADDCS	R9,R9,R8,LSL #4		;And add in the modifiers
		B	%99			;Return this value to caller

		; --- Handle other keypad bits ---
		;
		; This is somewhat clever in places, so watch out.  Rather
		; than branching on a match, or having an unconditional
		; CMP for each case, and a further CMP at the end, we
		; drop through to *all* subsequent comparisons, and take
		; this into account.
		;
		; Skeptics may say that this actually wastes an instruction.
		; Not true -- the setting up requires one value to be
		; initialised to an invalid value spotted at the end anyway,
		; and the base values are not valid immediate constants
		; anyway.  Oddly enough, &168 == &5A >> 2, which is valid!

30		MOV	R7,#&168		;Initial value for base
		MOV	R0,#0			;Initial value for icode

		CMP	R10,#'/'
		SUBEQ	R7,R7,#1
		ADDEQ	R0,R0,#intk_kSlash-intk_kStar
		CMPNE	R10,#'*'
		SUBEQ	R7,R7,#1
		ADDEQ	R0,R0,#intk_kStar-intk_kHash
		CMPNE	R10,#'#'
		SUBEQ	R7,R7,#1
		ADDEQ	R0,R0,#intk_kHash-intk_kMinus
		CMPNE	R10,#'-'
		SUBEQ	R7,R7,#1
		ADDEQ	R0,R0,#intk_kMinus-intk_kPlus
		CMPNE	R10,#'+'
		SUBEQ	R7,R7,#2
		ADDEQ	R0,R0,#intk_kPlus-intk_kDot
		CMPNE	R10,#'.'
		SUBEQ	R7,R7,#1
		ADDEQ	R0,R0,#intk_kDot

		BNE	%40			;If no match above, skip

		BL	akbd_test		;Test the key's state
		BCC	%40			;If not pressed, skip

		; --- Now return the correct value ---
		;
		; We use the barrel shifter to copy the modifier key states
		; to the ARM flags.

		MOV	R9,R7
		MOVS	R14,R8,LSR #1		;Copy ctrl to !Z, sh to C
		SUBNE	R9,R9,#64		;If ctrl, subtract 64
		ADDCS	R9,R9,#16		;If shift, add 16
		ADDHI	R9,R9,#16		;If both, subtract only 32
		B	%99			;Return this value

		; --- Now tidy up any other weirdnesses ---

40		CMP	R10,#&01E		;Is it a home lookalike?
		BEQ	akbd__home		;Yes -- handle it nicely
		CMP	R10,#&01B		;Is it an escape press?
		BEQ	akbd__esc		;Yes -- handle that
		KEYRNG	R10,&1C,&1F		;Is it one of the others?
		BHI	%99			;No -- nothing else for it

		TST	R8,#akbd__s		;Is Shift being pressed?
		ADDNE	R9,R9,#&130		;Yes -- offset correctly
		ADDEQ	R9,R9,#&110		;No -- use different offset
		B	%99			;Return this value

akbd__home	MOV	R0,#intk_6		;It could be control-6
		BL	akbd_test		;Check this possibility
		ADRCC	R9,akbd__homeMods	;No -- point to home table
		BCC	%90			;And process it as normal
		TST	R8,#akbd__s		;Is Shift being pressed?
		ADDNE	R9,R9,#key_sc6-&1E	;Yes -- handle this nicely
		ADDEQ	R9,R9,#key_c6-&1E	;No -- return other value
		B	%99			;And return to caller

akbd__homeMods	DCW	key_Home,key_sHome
		DCW	key_cHome,key_scHome

akbd__esc	MOV	R0,#intk_LSquare	;It could be control-[
		BL	akbd_test		;Check this possibility
		ADRCC	R9,akbd__escMods	;No -- point to escape table
		BCC	%90			;And process it as normal
		TST	R8,#akbd__s		;Is Shift being pressed?
		ADDNE	R9,R9,#key_scLSquare-&1B ;Yes -- handle this nicely
		ADDEQ	R9,R9,#key_cLSquare-&1B	;No -- return other value
		B	%99			;And return to caller

akbd__escMods	DCW	key_Esc,key_sEsc
		DCW	key_cEsc,key_scEsc

		; --- Distinguish Page keys from Shift+Up/Down ---

50		TST	R10,#&0100		;Is it up or down?
		MOVNE	R0,#intk_PageUp		;If up, check PageUp key
		MOVEQ	R0,#intk_PageDown	;Otherwise, check PageDown
		BL	akbd_test		;Check the key's pressedness
		MOVNE	R0,#intk_k9		;Can use keypad with NumLock!
		MOVEQ	R0,#intk_k3		;So check for this as well
		BLCC	akbd_test		;Check the key's pressedness
		EORCS	R9,R9,#&050		;If it is, mangle correctly
		B	%99			;Return this value to caller

		; --- R9 points to a modifier table ---
		;
		; Entries in the table are held in halfwords, to save space

90		TST	R8,#akbd__s		;Is shift set?
		BIC	R8,R8,#akbd__s		;Clear shift bit anyway
		LDR	R9,[R9,R8,LSL #1]	;Load word from address
		MOVNE	R9,R9,LSR #16		;If set, shift word down
		BIC	R9,R9,#&ff000000	;Clear top byte of key value
		BIC	R9,R9,#&00ff0000	;Clear next byte of key too

		; --- Standard return-to-caller code ---

99		MOV	R0,R9			;Return the key number
		LDMFD	R13!,{R6-R10,PC}^	;Return all registers

		LTORG

; --- akbd_pollKey ---
;
; On entry:	--
;
; On exit:	CC if a key was in the buffer and
;		  R0 == wimp translated key code
;		else CS and
;		  R0 corrupted
;
; Use:		Reports whether the user has typed ahead, and if so what the ;		keypress. Note that the keypresses returned are WIMP-type,
;		not Straylight extended ones so you will have to use
;		akbd_translate if you need the extended type.
;
;		This call could be used to allow buffering of keypresses:
;		on a Key_Pressed event you would call this routine until
;		it returns FALSE and store the codes it returns in a buffer
;		along with the code from the event.

		EXPORT	akbd_pollKey
akbd_pollKey	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stack some registers
		
		; --- Find out if there is a key waiting ---
		
		MOV	R0,#129			;Scan for a key
		MOV	R1,#0			;No time limit
		MOV	R2,#0
		SWI	OS_Byte			;Read it then
		CMP	R2,#&FF			;Was there a time out
		BEQ	%95akbd_pollKey		;Yes -- return
		
		; --- Check for extended key codes ---
		
		CMP	R1,#0			;Was 0 returned?
		BNE	%90akbd_pollKey		;No -- return happily
			
		MOV	R0,#129			;Scan for a key
		MOV	R1,#0			;No time limit
		MOV	R2,#0
		SWI	OS_Byte			;Read it then
		
		CMP	R1,#0			;Is it still 0?
		ADDNE	R1,R1,#&100		;No -- return extended no.
		
90akbd_pollKey	MOV	R0,R1			;No -- get the character code
		LDMFD	R13!,{R1,R2,R14}	;Load back registers
		BICS	PC,R14,#C_flag		;Return with C clear

95akbd_pollKey	LDMFD	R13!,{R1,R2,R14}	;Load back registers
		ORRS	PC,R14,#C_flag		;Return with C set
		
		LTORG

;----- That's all, folks ----------------------------------------------------

		END
