It is the 1730th of March 2020 (aka the 24th of November 2024)
You are 3.135.190.244,
pleased to meet you!
mailto:blog-at-heyrick-dot-eu
QuickBlank
A little bit of geekery for you.
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.
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.
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.
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...
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 numbersEventV * &10KeyEvent * 11; Alt and Ctrl key internal codesKey_1_Low * &3B; Left Ctrl key - low levelKey_2_Low * &5E; Left Alt key - low levelKey_1_High * 4; Left Ctrl key - INKEY style numberKey_2_High * 5; Left Alt key - INKEY style number; SWIsXOS_Byte * &20006XOS_Claim * &2001FXOS_Release * &20020XScreenBlanker_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|, CODEENTRYentrypoint; Standard RISC OS module headerDCD 0; Start codeDCD (initialise - entrypoint) ; Initialise codeDCD (finalise - entrypoint) ; Finalise codeDCD 0; Service callDCD (titlestring - entrypoint) ; Module title stringDCD (helpstring - entrypoint) ; Module help stringDCD 0; Help/Command tableDCD 0; SWI chunkDCD 0; SWI handler codeDCD 0; SWI decoding tableDCD 0; SWI decoding codeDCD 0; Messages fileDCD (thirtytwo - entrypoint) ; 32bit flag wordtitlestring= CODENAME, 0ALIGNhelpstring= CODENAME, 9, CODEVERS, " (", CODEDATE, ") ", CODECOPYALIGNthirtytwo; 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 addressSTR R14, [R13, #-4]!
; Now we claim EventVMOV R0, #EventVADR R1, eventv_handlerMOV R2, #0; We have no memory claimSWI XOS_ClaimLDRVS PC, [R13], #4; Now we claim the keyboard activity eventMOV R0, #14MOV R1, #KeyEventSWI 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 returnSTR R14, [R13, #-4]!
; Undo event claimMOV R0, #13MOV R1, #KeyEventSWI XOS_Byte; Undo EventV claimMOV R0, #EventVADR R1, eventv_handlerMOV R2, #0SWI 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.
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.
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.
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...).
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 eventCMP R0, #KeyEventMOVNE 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_LowBEQ eventv_checksecondkey; Is the key currently pressed the second key we want to look out for?CMP R2, #Key_2_LowMOVNE PC, R14; Fall through to check FIRST keySTMFD R13!, {R0-R2, R14}
MOV R0, #121; Keyboard scanMOV R1, #(Key_1_High:EOR: &80) ; &80 to scan a particular keySWI XOS_ByteCMP R1, #255; Is it pressed?BEQ do_blankLDMFD R13!, {R0-R2, PC} ; Leave if not pressedeventv_checksecondkeySTMFD R13!, {R0-R2, R14}
MOV R0, #121; Keyboard scanMOV R1, #(Key_2_High:EOR: &80) ; &80 to scan a particular keySWI XOS_ByteCMP R1, #255; Is it pressed?BEQ do_blankLDMFD 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 immediatelySWI XScreenBlanker_ControlLDMFD R13!, {R0-R2, PC} ; Leave;; ALL DONE!;END
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!
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 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.
Don't be afraid to move your functions around to make use of fall-through. Just remember to add a ; 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. ☺
Your comments:
Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.
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} ; Leave
Rick, 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_Control
Can be optimised to:
SUBS R0, R1, #255 ; Is it pressed?
SWIEQ XScreenBlanker_Control
Rick, 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
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.