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...

; 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.

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 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!

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 
 
CMP PC, #0 ; Ensure V-flag clear 
LDMFD R13!, {R0-R2, PC} ; Leave 

Add a comment (v0.11) [help?] . . . try the comment feed!
Your name
Your email (optional)
Validation Are you real? Please type 09019 backwards.
Your comment
French flagSpanish flagJapanese flag
Calendar
«   October 2017   »
MonTueWedThuFriSatSun
      1
2578
91011121415
17181921
232425262829
3031     

(Felicity? Marte? Find out!)

Last 5 entries

List all b.log entries

Return to the site index

Geekery

Search

Search Rick's b.log!

PS: Don't try to be clever.
It's a simple substring match.

Etc...

Last read at 11:18 on 2024/04/19.

QR code


Valid HTML 4.01 Transitional
Valid CSS
Valid RSS 2.0

 

© 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.

 

Have you noticed the watermarks on pictures?
Next entry - 2017/10/04
Return to top of page