; MoreKeys - support module ; ; by Rick Murray ; Version: 0.03 ; Date : Wednesday, 18th April 2012 ; Started: Sunday, 11th March 2012 ; ; ; THIS MODULE *IS* ROMABLE. ; ; ; ; IMPLEMENTATION NOTES: ; ; What we do: ; ; * Allocate ourselves some memory in RMA, for the following purposes: ; !0 = *INTERNAL* code of key pressed, else ZERO. ; !4 = Non-zero if SHIFT held. ; !8 = Value of monotonic time when keypress triggered. ; !12= Non-zero if Ctrl held (module keeps track of this). ; !16= Non-zero if Alt held (module keeps track of this). ; ; * Hook into the keyboard activity event (Event #11). ; ; * When a keypress is detected: ; * Scan for Ctrl-Alt ; If YES, record keypress, then suppress it. ; ; Our inclusions GET ^.h.equx ; EQUB, EQUD, EQUSZ, EQUSZA... GET ^.h.SWINames ; SWI definitions GET ^.h.pushpull ; Push, Pull, PushLR, PullLR, Return, PullRet... GET ^.h.debug ; DebugMsg (only used during actual debugging) ; Version information GBLS CODENAME CODENAME SETS "MoreKeys" GBLS CODEVERS CODEVERS SETS "0.03" GBLA CVERSNUM ; Number is Xxx for X.xx; so 123 (decimal) = v1.23 CVERSNUM SETA 1 GBLS CODEDATE CODEDATE SETS "18 Apr 2012" ; Date format MUST be "DD Mmm CCYY" for RISC OS to recognise it GBLS CODECOPY CODECOPY SETS " 2012 Rick Murray" GBLA SWICHUNK SWICHUNK SETA &59180 ; This SWI chunk has been OFFICIALLY allocated. It's MINE. ; Mine! Mine! Mine! Mine! Mine! ; Allocation given by Ben Avison ( allocate@riscosopen.org ) on 2012/03/15. ; Vector/Event numbers EventV * &10 KeyEvent * 11 ; Alt and Ctrl key internal codes Shift_Left * &4C Shift_Right * &58 Ctrl_Left * &3B Ctrl_Right * &61 Alt_Left * &5E Alt_Right * &60 ; Array offsets KeyCode * 0 Shift * 4 Timer * 8 CtrlStatus * 12 AltStatus * 16 Workspace * 20 ; Module build option DebugCode * 0 ; =========== ; Here we go! ; =========== AREA |MoreKeys$Code|, CODE ENTRY entrypoint ; Standard RISC OS module header EQUD 0 ; Start code EQUD (initialise - entrypoint) ; Initialise code EQUD (finalise - entrypoint) ; Finalise code EQUD 0 ; Service call EQUD (titlestring - entrypoint) ; Module title string EQUD (helpstring - entrypoint) ; Module help string ; EQUD (commands - entrypoint) ; Help/command table EQUD 0 ; help/command stuff to be implemented ##TODO## ;) EQUD SWICHUNK ; SWI chunk EQUD (swicode - entrypoint) ; SWI handler code EQUD (switable - entrypoint) ; SWI decoding table EQUD 0 ; SWI decoding code EQUD 0 ; Messages file EQUD (thirtytwo - entrypoint) ; 32bit flag word titlestring = CODENAME, 0 ALIGN helpstring = CODENAME, 9, CODEVERS, " (", CODEDATE, ") ", CODECOPY ALIGN thirtytwo ; Bit zero set indicates this is a 32bit-compatible module... EQUD 1 [ 0=1 ; ^^^ means it'll NOT be included (at this time) commands ; Only one commands, *MoreKeys = CODENAME, 0 ; "*MoreKeys" ALIGN EQUD (commandcode - entrypoint) ; pointer to code EQUD &00010000 ; No flags, 0-1 parameters, no GSTrans. EQUD (cmndsyntax - entrypoint) ; Syntax message EQUD (cmndhelp - entrypoint) ; Help message EQUD 0 ; [end of list] cmndsyntax = "Syntax: *", CODENAME, " [on|off]", 0 ALIGN cmndhelp EQUSZA "Enable or disable key interception. Call with no parameter to report current state.\r\n" commandcode STR R14, [R13, #-4]! ADR R0, cmndgetstuffed SWI OS_Write0 ; should prolly be GenError, but hey... LDR PC, [R13], #4 cmndgetstuffed EQUSZA "Sorry, this hasn't been done yet!\r\n" ] initialise ; We're in SVC mode, so preserve return address STR R14, [R13, #-4]! ; Check we have no allocated space from RMTidy op LDR R2, [R12] TEQ R2, #0 BNE skip_mem_allocate ; Okay, so claim a cosy little spot in the RMA MOV R0, #6 MOV R3, #32 ; 20 bytes + 12 workspace [debug] SWI XOS_Module LDRVS PC, [R13], #4 ; bomb out if V set ; Okay, remember our workspace pointer STR R2, [R12] ; R12 points to private word; store memblk MOV R12, R2 ; R12 now points directly to our memory skip_mem_allocate ; Now we claim EventV MOV R0, #EventV ADR R1, eventv_handler ; keep R2 as is, it points to our memory block SWI XOS_Claim ; Now we claim the keyboard activity event MOV R0, #14 MOV R1, #KeyEvent SWI XOS_Byte ; OS_Byte? FFS. Needs an OS_ClaimEvent or somesuch! ; Next, clear our memory block MOV R0, #0 ; Nully, Nully STR R0, [R12, #KeyCode] ; Pride of our gully STR R0, [R12, #Shift] ; Nully, Nully STR R0, [R12, #Timer] ; Don't ever wander STR R0, [R12, #CtrlStatus] ; Away from the gulley and me STR R0, [R12, #AltStatus] ; Nully, Nully STR R0, [R12, #Workspace] ; Marry me Nully STR R0, [R12, #(Workspace+4)] ; And happy forever I'll be STR R0, [R12, #(Workspace+8)] ; (ahem!) ; Okay, we're done, let's go home... LDR PC, [R13], #4 finalise ; Preserve return STR R14, [R13, #-4]! ; Determine our true workspace address LDR R12, [R12] ; The One True Workspace ; Undo event claim (undoing in reverse order) MOV R0, #13 MOV R1, #KeyEvent SWI XOS_Byte ; Undo EventV claim MOV R0, #EventV ADR R1, eventv_handler MOV R2, R12 ; address given was memblk, not privword SWI XOS_Release ; And now release the memory block MOV R0, #7 MOV R2, R12 SWI XOS_Module ; That's it. Goodbye and thank you for watching. ; Y'all come back now, y'hear? And goodnight America, wherever you are. LDR PC, [R13], #4 switable = CODENAME, 0 EQUSZ "DataPointer" ; returns pointer to data block EQUSZ "ReadData" ; API to read data from RMA EQUSZ "ClearData" ; API to clear data in RMA [ DebugCode = 1 EQUSZ "DumpStatus" ; for test/debug ] EQUB 0 ; no more SWIs ALIGN swicode ; On entry: ; R0 - R8 as for caller ; R11 = SWI number AND 63 ; R12 = Private word ; R13 = SVC stack ; R14 = Return address ; We're in SVC mode... STMFD R13!, {R9, R10, R14} ; preserve R9, R10, and return address ; check our SWI is within range CMP R11, #3 BGT swicall_invalid ; is it MoreKeys_DataPointer? CMP R11, #0 BEQ swicall_datapointer ; is it MoreKeys_ReadData? CMP R11, #1 BEQ swicall_readdata ; is it MoreKeys_ClearData? CMP R11, #2 BEQ swicall_cleardata [ DebugCode = 1 ; is it MoreKeys_DumpStatus? ##TODO## doesn't exactly need a comparison CMP R11, #3 ; if we know it's the third of three, duh! BEQ swicall_dumpstatus ] swicall_invalid ; just return... can't be arsed to whinge LDMFD R13!, {R9, R10, PC} swicall_datapointer ; SWI &59180 MoreKeys_DataPointer ; ; This returns the value of the module workspace pointer. This ; is intended for use by the main program as both the PollWord ; and also for status (like "Shift held?") and such. ; Also sets module version number in R1. LDR R0, [R12] MOV R1, #CVERSNUM MOV R2, #0 LDMFD R13!, {R9, R10, PC} swicall_readdata ; SWI &59181 MoreKeys_ReadData ; ; This provides a nice official way to read from the data block ; without having an application read from module space. LDR R12, [R12] LDR R0, [R12, #KeyCode] LDR R1, [R12, #Shift] LDR R2, [R12, #Timer] LDMFD R13!, {R9, R10, PC} swicall_cleardata ; SWI &59182 MoreKeys_ClearData ; ; This provides a nice official way to reset the data block ; without having an application WRITE into module space. LDR R12, [R12] MOV R0, #0 STR R0, [R12, #KeyCode] STR R0, [R12, #Shift] STR R0, [R12, #Timer] STR R0, [R12, #CtrlStatus] STR R0, [R12, #AltStatus] STR R0, [R12, #Workspace] STR R0, [R12, #(Workspace+4)] STR R0, [R12, #(Workspace+8)] LDMFD R13!, {R9, R10, PC} [ DebugCode = 1 swicall_dumpstatus ; SWI &59183 MoreKeys_DumpStatus ; ; This spits out the current MoreKeys module status as stored in ; the memory block. It writes as character output; and as such ; is expected to be called from a single-tasking program that ; just calls this over and over and over... ; Yeah, it's for debugging. ;-) LDR R12, [R12] ; resolve pointer ; Key code? ADR R0, msg_keycode SWI OS_Write0 LDR R0, [R12, #KeyCode] ADD R1, R12, #Workspace ; pointer to workspace at buffer%!20 MOV R2, #12 SWI OS_ConvertHex2 ; 0 - FF SWI OS_Write0 ; Shift held? ADR R0, msg_shift SWI OS_Write0 LDR R0, [R12, #Shift] CMP R0, #0 ADREQ R0, msg_no ADRNE R0, msg_yes SWI OS_Write0 ; Timer ADR R0, msg_timer SWI OS_Write0 LDR R0, [R12, #Timer] ADD R1, R12, #Workspace ; pointer to workspace at buffer%!20 MOV R2, #12 SWI OS_ConvertCardinal4 ; 0 - 4294967295 SWI OS_Write0 ; Ctrl held? ADR R0, msg_ctrl SWI OS_Write0 LDR R0, [R12, #CtrlStatus] CMP R0, #0 ADREQ R0, msg_no ADRNE R0, msg_yes SWI OS_Write0 ; Alt held? ADR R0, msg_alt SWI OS_Write0 LDR R0, [R12, #AltStatus] CMP R0, #0 ADREQ R0, msg_no ADRNE R0, msg_yes SWI OS_Write0 ; Tidy up the display ADR R0, msg_done SWI OS_Write0 ; Finished here LDMFD R13!, {R9, R10, PC} msg_keycode = CODENAME, " v", CODEVERS, " (", CODEDATE, ") status:\r\n\r\n Internal key code = " EQUB 0 ALIGN msg_shift EQUSZA " (hex)\r\n Shift key held = " msg_timer EQUSZA "\r\n Timer value = " msg_ctrl EQUSZA "\r\n Ctrl key held = " msg_alt EQUSZA "\r\n Alt key held = " msg_done EQUSZA "\r\n\r\nMore info: http://www.heyrick.co.uk/software/morekeys/\r\n\r\n" msg_yes EQUSZA "Yes" msg_no EQUSZA "No" ;msg_maybe ; EQUSZA "Maybe" ] eventv_handler ; Called for keyboard press/release on EventV CMP R0, #KeyEvent ; verify event MOVNE PC, R14 ; We don't touch 'special' keys CMP R2, #&10 ; &00+ = Esc, F0 - F12, Print, ScrLock, Break MOVLT PC, R14 ; pass it on ; Nor mouse buttons CMP R2, #&6F ; &70+ = Select, Menu, Adjust MOVGT PC, R14 ; pass it on ; We don't intercept Tab as Ctrl-Alt-Tab is used by another module to ; switch windows a la (Microsoft) Windows. CMP R2, #&26 ; &26 = Tab MOVEQ PC, R14 ; Check state of Shift/Ctrl/Alt keys CMP R2, #Shift_Left BEQ shift_handler CMP R2, #Shift_Right BEQ shift_handler CMP R2, #Ctrl_Left BEQ ctrl_handler CMP R2, #Ctrl_Right BEQ ctrl_handler CMP R2, #Alt_Left BEQ alt_handler CMP R2, #Alt_Right BEQ alt_handler ; Any further processing will corrupt registers, so we stack 'em STMFD R13!, {R0-R3, R14} ; Are Ctrl and Alt both held down at this point? LDR R3, [R12, #CtrlStatus] CMP R3, #1 BNE handler_dropout LDR R3, [R12, #AltStatus] CMP R3, #1 BNE handler_dropout ; Beyond this point, we have a keypress with Ctrl and Alt held ; ; Our key mapping is anticipated to be as follows: ; a -> ; c -> ; e -> ; f -> ; i -> ; m -> ['m' for "maths"] ; n -> ; o -> ; q -> ['q' for "quotes"] ; s -> ['s' for "symbols"] ; u -> ; w -> ; y -> ; # -> [other "stuff"] ; ; ; However, in order to future support alternative encodings (Latin2 etc) we ; will simply trap *ANY* keypress that happens when Ctrl and Alt is held down. STR R2, [R12, #KeyCode] ; !0 is key code SWI OS_ReadMonotonicTime ; !4 is shift (set elsewhere) STR R0, [R12, #Timer] ; !8 is centisecond timer value ; Now we intercept this event to swallow the keypress LDMFD R13!, {R0-R3, R14} LDMFD R13!, {PC} handler_dropout LDMFD R13!, {R0-R3, PC} ; exit to pass on the call shift_handler ; called when Shift pressed; we just record its status STR R1, [R12, #Shift] MOV PC, R14 ; now pass this call on ctrl_handler ; called when Ctrl pressed STR R1, [R12, #CtrlStatus] MOV PC, R14 ; ditto alt_handler ; called when Alt pressed STR R1, [R12, #AltStatus] MOV PC, R14 ; mme ; ; ALL DONE! ; (see? that wasn't so hard now, was it?) ; END