Rick's b.log - 2017/10/03 |
|
It is the 21st of November 2024 You are 3.16.75.156, pleased to meet you! |
|
mailto:
blog -at- heyrick -dot- eu
What this is, is a little module to trap when Left Ctrl and Left Alt are pressed together, and if they are, to kick in the screen blanker. This can be useful for blanking the screen to stop unwanted people reading it, or how I use it - to force blank the screen when going to make tea or feed the cat (etc) because I know I'll be away for several minutes so can invoke the blanker right away instead of waiting for the usual timeout.
This project is designed to be assembled with the DDE, however it is fairly simple so shouldn't be hard to port to GCCSDK or BASIC as required.
Note that this code is released under the Common Development and Distribution Licence (CDDL). This means the source is "open", you can hack around with it, modify it, and so on with the usual proviso - if you release an updated/modified executable, you must also make the source to it available. That way, everybody benefits.
Why did I pick CDDL instead of my usual EUPL? Two simple reasons. The first is that CDDL is quite happy to coexist with other differently licenced code in order to form a larger whole (there is CDDL code within RISC OS). The EUPL doesn't actually appear to make any mention regarding combining EUPL code within a larger project containing code licensed differently. It may be that the EUPL's approach is to allow licence alteration under the Compatibility Clause, which may or may not be useful. The CDDL specifically says that CDDL only applies to CDDL code and that it's happy to co-exist with other-licenced code that doesn't infringe upon CDDL rights.
What we then do is check our first key - is it a match? If so, we jump over to a routine to perform a keyboard scan to see if the second key is also pressed. If it is, we can blank.
We don't stack any registers until we come to scan the keyboard for the other key. This is because we can work entirely with data available in registers up until that point, but once we start making OS calls, that's when we need to preserve registers.
This method might seem a bit contrived. It is being done like this so that we can rest entirely stateless - we don't record if our hotkeys are being pressed, we have no memory claim whatsoever. Furthermore, by detecting either keypress and scanning for the other key, we can handle the hotkeys being pressed in any order (or in the case of how I do it, just mashing both keys at the same time, though one will make contact a millisecond before the other, and a millisecond is a thousandth of a second which is an eternity to a processor clocking a gigahertz...).
If not - take a look at eventv_checksecondkey. The last two instructions are a BEQ (branch if result is equal) to the do_blank function, followed by an LDMFD to load the registers and exit in the case that the EQ branch is not taken (in other words, the result was not equal). The do_blank code then follows.
A more efficient way to write this would be to remove the BEQ line entirely, and change the
Don't be afraid to move your functions around to make use of fall-through. Just remember to add a
QuickBlank
A little bit of geekery for you.
Initial definitions
There's nothing surprising here. Some comments to identify the project and version, and some string definitions that will be pulled in later on to identify the module.
Which brings me to my second reason. The CDDL is incompatible with the GPL. For my point of view (that being that the GPL is the most rubbish licence on the planet with its "all your base are belong to us" attitude and ill-conceived vague terms "to be decided by the courts" (according to the FAQ)), that the CDDL is directly incompatible with the GPL but magically compatible with the entire RISC OS codebase (and its assorted licences) means CDDL is a good choice...
; QuickBlank
;
; by Rick Murray
; Version: 0.01
; Date : Tuesday, 26th September 2017
; Started: Tuesday, 26th September 2017
;
; Copyright 2017 Richard Murray. All rights reserved.
; Use is subject to license terms.
;
;
; CDDL HEADER START
;
; The contents of this file are subject to the terms of the
; Common Development and Distribution License (the "Licence").
; You may not use this file except in compliance with the Licence.
;
; You can obtain a copy of the licence in the LICENCE file that is
; part of the distribution archive, or Google for "CDDL licence".
; See the Licence for the specific language governing permissions
; and limitations under the Licence.
;
; When distributing Covered Code, include this CDDL HEADER in each
; file and include the Licence file. If applicable, add the
; following below this CDDL HEADER, with the fields enclosed by
; brackets "[]" replaced with your own identifying information:
; Portions Copyright [yyyy] [name of copyright owner]
;
; CDDL HEADER END
;
;
; Version information
GBLS CODENAME
CODENAME SETS "QuickBlank" ; NAME *NOT* REGISTERED!
GBLS CODEVERS
CODEVERS SETS "0.01"
GBLS CODEDATE
CODEDATE SETS "26 Sep 2017" ; Date format MUST be "DD Mmm CCYY" for RISC OS to recognise it
GBLS CODECOPY
CODECOPY SETS "© 2017 Rick Murray"
Definitions
Now for integer definitions used in the code. The only complication here is the duality of the key codes. This is because the KeyEvent vector uses low level key codes, which the "is a key pressed?" command uses internal key codes.
This is where you will want to fiddle around should you prefer a different key combination than Left Ctrl and Left Alt key.
; Vector/Event numbers
EventV * &10
KeyEvent * 11
; Alt and Ctrl key internal codes
Key_1_Low * &3B ; Left Ctrl key - low level
Key_2_Low * &5E ; Left Alt key - low level
Key_1_High * 4 ; Left Ctrl key - INKEY style number
Key_2_High * 5 ; Left Alt key - INKEY style number
; SWIs
XOS_Byte * &20006
XOS_Claim * &2001F
XOS_Release * &20020
XScreenBlanker_Control * &63100
Module header
After the APCS area definition, we just straight into the standard module header. It's quite simple, mostly a list of words that are offsets to the relative data (which is why it is something minus entrypoint), or zero if not applicable.
As modules run in SVC mode, they must be flagged as being 32 bit compatible as serious errors in modules have the potential to bring down the system as SVC mode is the same level of privilege as the OS itself; hence running 26 bit code (and all the freaky stuff that implies what with messing with PC's PSR bits) would be a Really BAD Idea.
; ===========
; Here we go!
; ===========
AREA |QuickBlank$Code|, CODE
ENTRY
entrypoint
; Standard RISC OS module header
DCD 0 ; Start code
DCD (initialise - entrypoint) ; Initialise code
DCD (finalise - entrypoint) ; Finalise code
DCD 0 ; Service call
DCD (titlestring - entrypoint) ; Module title string
DCD (helpstring - entrypoint) ; Module help string
DCD 0 ; Help/Command table
DCD 0 ; SWI chunk
DCD 0 ; SWI handler code
DCD 0 ; SWI decoding table
DCD 0 ; SWI decoding code
DCD 0 ; Messages file
DCD (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...
DCD 1
Initialisation routine
This is quite simple. Claim EventV, the "random event" vector, then nable the key transition event.
The only two complications worth pointing out are: firstly, as we are a module we must preserve R14. Why? Because we are running in SVC mode, and when a SWI is called, the return address is automatically pushed into R14_svc. As our R14 is also R14_svc, you can see where that would go wrong.
The second wrinkle is the seemingly odd way of stacking/unstacking R14. The stack is STR R14, [R13, #-4]!
which means to write R14 at the address of R13 minus 4, with writeback of the new address into R13. Does that behaviour sound familiar? I've just described the operation of a Fully Descending stack (where the address points to the last used word, and the addresses count downwards in memory). It's functionally equivalent to STMFD R13!, {R14}
, only it is more efficient on contemporary processors than using a multiple register store instruction to write a single register.
Likewise, LDR PC, [R13], #4
is functionally equivalent to LDMFD R13!, {PC}
.
initialise
; ENTRY: R0-R6 trashable; as is R12 and R14.
; We're in SVC mode, so preserve return address
STR R14, [R13, #-4]!
; Now we claim EventV
MOV R0, #EventV
ADR R1, eventv_handler
MOV R2, #0 ; We have no memory claim
SWI XOS_Claim
LDRVS PC, [R13], #4
; Now we claim the keyboard activity event
MOV R0, #14
MOV R1, #KeyEvent
SWI XOS_Byte ; OS_Byte? Needs OS_ClaimEvent or somesuch!
; Okay, we're done, let's go home...
LDR PC, [R13], #4
Finalisation routine
This is the tidy up routine run before the module is terminated. It "disables" the key transition event, and removes our claim on EventV. I put "disables" in quotes there as RISC OS keeps a count of the number of times an event request is enabled and disabled, and it will only actually disable the event if the count is zero. This means we do not have to worry about messing up anything else that might be watching the key transition event - RISC OS will only actually disable the event when the final claimant disables it.
finalise
; ENTRY: R0-R6 trashable; as is R12 and R14.
; Preserve return
STR R14, [R13, #-4]!
; Undo event claim
MOV R0, #13
MOV R1, #KeyEvent
SWI XOS_Byte
; Undo EventV claim
MOV R0, #EventV
ADR R1, eventv_handler
MOV R2, #0
SWI XOS_Release
; That's it. Goodbye and thank you for watching.
LDR PC, [R13], #4
Event handler routine
Okay, this routine is likely entered in IRQ mode with interrupts disabled, so we must be small and fast.
Our first jobs are to check that we're being called for a key transition event (as EventV is a mishmash of random events), and if we are, we then check that we're only being called for when a key is PRESSED - we don't care about released keys.
If the first key is not pressed, we look to see if the second key is the one that we're being notified as having been pressed. If it is, we immediately check if the first key is also pressed. If it is, we can blank.
eventv_handler
; Called for keyboard press/release on EventV
;
; ENTRY: R0 is Event number (11 for key transition)
; R1 is transition value (1 if pressed, 0 if released)
; R2 is internal key number
; R3 is keyboard ID
; We come here for all EventV, so check it's a Key event
CMP R0, #KeyEvent
MOVNE PC, R14
; Now check it's a key PRESS
; StrongHelp OS v3.37 is WRONG when it says "Transition value is 0 when
; released and 1 when pressed".
CMP R1, #0 ; 0 means pressed!
MOVNE PC, R14
; Is the key currently pressed the first key we want to look out for?
CMP R2, #Key_1_Low
BEQ eventv_checksecondkey
; Is the key currently pressed the second key we want to look out for?
CMP R2, #Key_2_Low
MOVNE PC, R14
; Fall through to check FIRST key
STMFD R13!, {R0-R2, R14}
MOV R0, #121 ; Keyboard scan
MOV R1, #(Key_1_High :EOR: &80) ; &80 to scan a particular key
SWI XOS_Byte
CMP R1, #255 ; Is it pressed?
BEQ do_blank
LDMFD R13!, {R0-R2, PC} ; Leave if not pressed
eventv_checksecondkey
STMFD R13!, {R0-R2, R14}
MOV R0, #121 ; Keyboard scan
MOV R1, #(Key_2_High :EOR: &80) ; &80 to scan a particular key
SWI XOS_Byte
CMP R1, #255 ; Is it pressed?
BEQ do_blank
LDMFD R13!, {R0-R2, PC} ; Leave if not pressed
The blanking routine
A simple one this. We already have R0-R2 and R14 stacked, so we simply perform the OS call to blank the screen, then exit.
do_blank
; We enter with R0-R2, R14 on the stack.
MOV R0, #0 ; Blank screen immediately
SWI XScreenBlanker_Control
LDMFD R13!, {R0-R2, PC} ; Leave
;
; ALL DONE!
;
END
The one-shot version of the source code
Here it is all together:
; QuickBlank
;
; by Rick Murray
; Version: 0.01
; Date : Tuesday, 26th September 2017
; Started: Tuesday, 26th September 2017
;
; Copyright 2017 Richard Murray. All rights reserved.
; Use is subject to license terms.
;
;
; CDDL HEADER START
;
; The contents of this file are subject to the terms of the
; Common Development and Distribution License (the "Licence").
; You may not use this file except in compliance with the Licence.
;
; You can obtain a copy of the licence in the LICENCE file that is
; part of the distribution archive, or Google for "CDDL licence".
; See the Licence for the specific language governing permissions
; and limitations under the Licence.
;
; When distributing Covered Code, include this CDDL HEADER in each
; file and include the Licence file. If applicable, add the
; following below this CDDL HEADER, with the fields enclosed by
; brackets "[]" replaced with your own identifying information:
; Portions Copyright [yyyy] [name of copyright owner]
;
; CDDL HEADER END
;
;
; Version information
GBLS CODENAME
CODENAME SETS "QuickBlank" ; NAME *NOT* REGISTERED!
GBLS CODEVERS
CODEVERS SETS "0.01"
GBLS CODEDATE
CODEDATE SETS "26 Sep 2017" ; Date format MUST be "DD Mmm CCYY" for RISC OS to recognise it
GBLS CODECOPY
CODECOPY SETS "© 2017 Rick Murray"
; Vector/Event numbers
EventV * &10
KeyEvent * 11
; Alt and Ctrl key internal codes
Key_1_Low * &3B ; Left Ctrl key - low level
Key_2_Low * &5E ; Left Alt key - low level
Key_1_High * 4 ; Left Ctrl key - INKEY style number
Key_2_High * 5 ; Left Alt key - INKEY style number
; SWIs
XOS_Byte * &20006
XOS_Claim * &2001F
XOS_Release * &20020
XScreenBlanker_Control * &63100
; ===========
; Here we go!
; ===========
AREA |QuickBlank$Code|, CODE
ENTRY
entrypoint
; Standard RISC OS module header
DCD 0 ; Start code
DCD (initialise - entrypoint) ; Initialise code
DCD (finalise - entrypoint) ; Finalise code
DCD 0 ; Service call
DCD (titlestring - entrypoint) ; Module title string
DCD (helpstring - entrypoint) ; Module help string
DCD 0 ; Help/Command table
DCD 0 ; SWI chunk
DCD 0 ; SWI handler code
DCD 0 ; SWI decoding table
DCD 0 ; SWI decoding code
DCD 0 ; Messages file
DCD (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...
DCD 1
initialise
; ENTRY: R0-R6 trashable; as is R12 and R14.
; We're in SVC mode, so preserve return address
STR R14, [R13, #-4]!
; Now we claim EventV
MOV R0, #EventV
ADR R1, eventv_handler
MOV R2, #0 ; We have no memory claim
SWI XOS_Claim
LDRVS PC, [R13], #4
; Now we claim the keyboard activity event
MOV R0, #14
MOV R1, #KeyEvent
SWI XOS_Byte ; OS_Byte? Needs OS_ClaimEvent or somesuch!
; Okay, we're done, let's go home...
LDR PC, [R13], #4
finalise
; ENTRY: R0-R6 trashable; as is R12 and R14.
; Preserve return
STR R14, [R13, #-4]!
; Undo event claim
MOV R0, #13
MOV R1, #KeyEvent
SWI XOS_Byte
; Undo EventV claim
MOV R0, #EventV
ADR R1, eventv_handler
MOV R2, #0
SWI XOS_Release
; That's it. Goodbye and thank you for watching.
LDR PC, [R13], #4
eventv_handler
; Called for keyboard press/release on EventV
;
; ENTRY: R0 is Event number (11 for key transition)
; R1 is transition value (1 if pressed, 0 if released)
; R2 is internal key number
; R3 is keyboard ID
; We come here for all EventV, so check it's a Key event
CMP R0, #KeyEvent
MOVNE PC, R14
; Now check it's a key PRESS
; StrongHelp OS v3.37 is WRONG when it says "Transition value is 0 when
; released and 1 when pressed".
CMP R1, #0 ; 0 means pressed!
MOVNE PC, R14
; Is the key currently pressed the first key we want to look out for?
CMP R2, #Key_1_Low
BEQ eventv_checksecondkey
; Is the key currently pressed the second key we want to look out for?
CMP R2, #Key_2_Low
MOVNE PC, R14
; Fall through to check FIRST key
STMFD R13!, {R0-R2, R14}
MOV R0, #121 ; Keyboard scan
MOV R1, #(Key_1_High :EOR: &80) ; &80 to scan a particular key
SWI XOS_Byte
CMP R1, #255 ; Is it pressed?
BEQ do_blank
LDMFD R13!, {R0-R2, PC} ; Leave if not pressed
eventv_checksecondkey
STMFD R13!, {R0-R2, R14}
MOV R0, #121 ; Keyboard scan
MOV R1, #(Key_2_High :EOR: &80) ; &80 to scan a particular key
SWI XOS_Byte
CMP R1, #255 ; Is it pressed?
BEQ do_blank
LDMFD R13!, {R0-R2, PC} ; Leave if not pressed
do_blank
; We enter with R0-R2, R14 on the stack.
MOV R0, #0 ; Blank screen immediately
SWI XScreenBlanker_Control
LDMFD R13!, {R0-R2, PC} ; Leave
;
; ALL DONE!
;
END
Did you spot the optimisation potential?
I left a little inefficiency in the code to see if you spotted it.
If you did, you're on the way to being an expert assembler coder!
LDMFD
instruction to be an LDMNEFD
instruction. What this means is that the registers would be loaded (and the routine exited) if the result was not equal.
And if the result was equal? We do nothing, as we'll fall directly into the do_blank function. So by thinking carefully about our code and the relationship of functions to each other, we can optimise out an unnecessary word and (more importantly) save the pipeline disturbances that would arise from taking an unnecessary branch.
; Falls through
comment so that it is clear to everybody that this is intentional behaviour and not a forgotten function return.
And, of course...
Nobody expects you to copy-paste a program's source from a browser to your editor. Not in 2017. Here's a nice zip file (16.6KiB) containing the source, a MakeFile, the licence text, and a pre-built module. The optimisation discussed above has not been applied, that's your job. ☺
Jeff Doggett, 4th October 2017, 09:24 Ok, so here's mine optimised to the point of unreadability.
Of course, given my track record it may not even work.
; We come here for all EventV, so check it's a Key event TEQ R0, #KeyEvent ; Now check it's a key PRESS ; StrongHelp OS v3.37 is WRONG when it says "Transition value is 0 when ; released and 1 when pressed". TEQEQ R1, #0 ; 0 means pressed! MOVNE PC, R14 ; Is the key currently pressed the first key we want to look out for? TEQ R2, #Key_1_Low STMEQFD R13!, {R0-R2, R14} MOVEQ R1, #(Key_2_High :EOR: &80) ; &80 to scan a particular key BEQ eventv_checksecondkey ; Is the key currently pressed the second key we want to look out for? TEQ R2, #Key_2_Low MOVNE PC, R14 ; Fall through to check FIRST key STMFD R13!, {R0-R2, R14} MOV R1, #(Key_1_High :EOR: &80) ; &80 to scan a particular key eventv_checksecondkey MOV R0, #121 ; Keyboard scan SWI XOS_Byte TEQ R1, #255 ; Is it pressed? MOVEQ R0, #0 ; Blank screen immediately SWIEQ XScreenBlanker_Control LDMFD R13!, {R0-R2, PC} ; LeaveRick, 4th October 2017, 09:38 If I turn the PC on, I'll wrap your code in pre tags so it reads better (done!).Jeff Doggett, 4th October 2017, 09:38 And there are other sneaky ways to save an instruction:TEQ R1, #255 ; Is it pressed? MOVEQ R0, #0 ; Blank screen immediately SWIEQ XScreenBlanker_ControlCan be optimised to:SUBS R0, R1, #255 ; Is it pressed? SWIEQ XScreenBlanker_ControlRick, 4th October 2017, 23:49 The use of conditionals and fall-through in the first example is pretty nice. I had thought about rolling the first two rejection tests together like you have, but didn't for the tutorial in order to keep each part separate.
Your second idea, the SUBS. Now you're playing hardball. It beautifully demonstrates the lateral thinking that can perform the return value test and set the desired (different) register to zero at the same time. I really like that one. :-) :-) :-)David Pilling, 8th October 2017, 02:37 Cars have a sensor under the drivers seat which lets them do various things - maybe computers should - driver departs, blank the screen.Fred Graute, 2nd September 2018, 16:24 Chanced upon this when looking for some info for ToggleBD (which also sits on EventV,11). The StrongHelp OS v3.37 manual _is_ correct, a transition value of 1 does mean 'key pressed'.
The code presented in this blog actually works on key up. You can see this by pressing and holding down one key and then press and hold the other. Nothing happens until you release one of the keys.
Trying to blank the screen on a key down doesn't work. Why? Because ScreenBlanker also looks for key down events so it can unblank the screen when the user hits the keyboard.
Normally ScreenBlanker's handler will be called after QuickBlanker's (handlers are called in reverse order of installation). So the blanking done by QuickBlanker will be immediately undone by ScreenBlanker.
Hence blanking the screen using the keyboard can only be done on key up (took me a while to figure this out :-).
Anyway here's my version of the code, including Jeff's clever trick:
; We come here for all EventV, so check it's a Key up event
TEQ R0, #KeyEvent ; is it a key event?
TEQEQ R1, #0 ; if so, is key released?
MOVNE PC, R14 ; if not, ignore this event
; Is the key released one of the two we're interested in?
TEQ R2, #Key_1_Low ; is it first key?
TEQNE R2, #Key_2_Low ; or is it second key?
MOVNE PC, R14 ; if not, ignore this key
; One key is released, check if the other key is still down
STMFD R13!, {R0-R2, LR} ;
TEQ R2, #Key_1_Low ;
MOV R0, #121 ; 121 => Keyboard scan
MOVEQ R1, #(Key_2_High :EOR: &80) ; &80 to scan a particular key
MOVNE R1, #(Key_1_High :EOR: &80) ; &80 to scan a particular key
SWI XOS_Byte ;
SUBS R0, R1, #255 ; Is it pressed?
SWIEQ XScreenBlanker_Control ; If so, blank immediately
CMP PC, #0 ; Ensure V-flag clear
LDMFD R13!, {R0-R2, PC} ; Leave
© 2017 Rick Murray |
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted. RIPA notice: No consent is given for interception of page transmission. |