Author Topic: A new Keyhit  (Read 5413 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
A new Keyhit
« on: December 26, 2019, 04:30:09 pm »
For a long time now, I realized that there were a few keys which mapped wrongly with _KEYHIT, but they were never really much of an issue in my code, so I didn't bother to dig extensively into them.  A few times, I've dug into the qb64 source in libqb.cpp to try and see if I could sort out what was wrong with a few of them (CTRL-TAB is generally the one which affects me the most, as I use it for a reverse tab process normally.), but I've never been able to decipher exactly what the heck is going on in there to correct things.

So, now I'm working on a new project (or at least trying to, though it seems I have to keep rebuilding basic development tools before I can actually go anywhere with it like I want), and once again, I need some of those "lost" or "misplaced" keys.  This morning, I sorted out how to get the values of every key with GetAsyncKeyState (you can look at it here: https://www.qb64.org/forum/index.php?topic=2030.0), but it returns values and codes which I'm not that used to using.  Normally I use _KEYHIT and _KEYDOWN for my programs, and I really don't want to have to swap everything over to the virtual key codes...

So, I sat down and started to write a patch to fix and fill in those missing codes in _KEYHIT for me.  The work is definitely a work-in-progress, as I'm still finding and patching issues, but I wanted to go ahead and share what I've came up with so far.

Code: QB64: [Select]
  1. DECLARE LIBRARY 'function is already used by QB64 so "User32" is not required
  2.     FUNCTION GetAsyncKeyState% (BYVAL vkey AS LONG)
  3.  
  4.  
  5.  
  6.     k = Keyhit(oldkeyhit&)
  7.     IF k <> 0 THEN PRINT k, oldkeyhit&
  8.     _LIMIT 30
  9.  
  10. FUNCTION Keyhit& (oldkeyhit&)
  11.     STATIC MenuDelay AS _FLOAT, MenuDown AS _BYTE
  12.     STATIC TabDelay AS _FLOAT, TabDown AS _BYTE
  13.     STATIC BackspaceDelay AS _FLOAT, BackspaceDown AS _BYTE
  14.     STATIC TildeDelay AS _FLOAT, BackQuoteDelay AS _FLOAT
  15.     STATIC NumDelay(0 TO 19) AS _FLOAT
  16.     STATIC JDelay AS _FLOAT, JDown AS _BYTE
  17.     STATIC SysDelay AS _FLOAT, SysDown AS _BYTE
  18.     STATIC ScrollDelay AS _FLOAT, ScrollDown AS _BYTE
  19.     STATIC PauseDelay AS _FLOAT, PauseDown AS _BYTE
  20.     STATIC NumDelay AS _FLOAT, NumDown AS _BYTE 'num lock
  21.     STATIC Num5Delay AS _FLOAT, Num5Down AS _BYTE 'number 5 key on the num pad
  22.     DIM RepeatRate AS _FLOAT: RepeatRate = 0.2 'necessary so we don't spam key returns with GetAsyncKeyState
  23.  
  24.     k = _KEYHIT: oldkeyhit& = k
  25.  
  26.         'Fix for number 5 on the num pad, when numlock is active
  27.         IF GetAsyncKeyState(12) THEN 'pause key, which has no value (and probably still won't, as QB64 uses it as a command code for pausing program execution
  28.             'and gives a false result on a keyup
  29.             IF ExtendedTimer > Num5Delay THEN k = 19456: Num5Down = -1: Num5Delay = ExtendedTimer + RepeatRate ELSE k = 0
  30.         ELSE
  31.             IF Num5Down THEN k = -19456: Num5Down = 0
  32.         END IF
  33.  
  34.         'Fix for Pause Key
  35.         IF GetAsyncKeyState(19) THEN 'pause key, which has no value (and probably still won't, as QB64 uses it as a command code for pausing program execution
  36.             'and gives a false result on a keyup
  37.             IF ExtendedTimer > PauseDelay THEN k = 100019: PauseDown = -1: PauseDelay = ExtendedTimer + RepeatRate ELSE k = 0
  38.         END IF
  39.  
  40.         'Fix for the Menu Key
  41.         IF GetAsyncKeyState(93) THEN 'menu key, which doesn't work at all on keydown
  42.             'and gives a false result on a keyup
  43.             IF ExtendedTimer > MenuDelay THEN k = 100319: MenuDown = -1: MenuDelay = ExtendedTimer + RepeatRate ELSE k = 0
  44.         END IF
  45.  
  46.         'Fix for SYS Key
  47.         IF GetAsyncKeyState(44) THEN 'SYS key, which currently thinks it's a comma
  48.             'and gives a false result on a keyup
  49.             IF ExtendedTimer > SysDelay THEN k = 100316: SysDown = -1: SysDelay = ExtendedTimer + RepeatRate ELSE k = 0
  50.         END IF
  51.         'Fix for NUM LOCK Key
  52.         IF GetAsyncKeyState(144) THEN 'num lock key, which currently toggles itself with the down value
  53.             'changing from a up code to a down code, depending on numlock status
  54.             'and gives a false result on keyup
  55.             IF ExtendedTimer > NumDelay THEN k = 100300: NumDelay = ExtendedTimer + RepeatRate ELSE k = 0
  56.         END IF
  57.  
  58.         'FIX for SCROLL LOCK Key
  59.         IF GetAsyncKeyState(145) THEN 'scroll lock key, which has no down value
  60.             'and gives a false result on keyup
  61.             IF ExtendedTimer > ScrollDelay THEN k = 100302: ScrollDelay = ExtendedTimer + RepeatRate ELSE k = 0
  62.         END IF
  63.  
  64.  
  65.  
  66.         IF _KEYDOWN(100305) OR _KEYDOWN(100306) THEN 'control is down
  67.             'Fix for CTRL-Tab
  68.             IF GetAsyncKeyState(9) THEN 'ctrl-tab; return tab, not CTRL-I
  69.                 IF ExtendedTimer > TabDelay THEN k = 9: TabDown = -1: TabDelay = ExtendedTimer + RepeatRate ELSE k = 0
  70.             END IF
  71.             'Fix for CTRL-Backspace
  72.             IF GetAsyncKeyState(8) THEN 'ctrl-backspace; return backspace, not CTRL-Delete
  73.                 IF ExtendedTimer > BackspaceDelay THEN k = 8: BackspaceDown = -1: BackspaceDelay = ExtendedTimer + RepeatRate ELSE k = 0
  74.             END IF
  75.             'Fix for CTRL-J
  76.             IF GetAsyncKeyState(74) THEN 'CTRL-J; return ctrl-j, not ctrl-return
  77.                 IF ExtendedTimer > JDelay THEN k = 106: JDown = -1: JDelay = ExtendedTimer + RepeatRate ELSE k = 0
  78.             END IF
  79.  
  80.             'Fix for CTRL-Tilde, CTRL-Backquote
  81.             IF GetAsyncKeyState(192) THEN 'ctrl-tilde, ctrl-backquote -- neither are supported
  82.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  83.                     IF ExtendedTimer > TildeDelay THEN k = 126: TildeDelay = ExtendedTimer + RepeatRate ELSE k = 0
  84.                 ELSE
  85.                     IF ExtendedTimer > BackQuoteDelay THEN k = 96: BackQuoteDelay = ExtendedTimer + RepeatRate ELSE k = 0
  86.                 END IF
  87.             END IF
  88.             'Fix for CTRL-0 to CTRL-9
  89.             FOR i = 0 TO 9
  90.                 IF GetAsyncKeyState(i + 48) THEN 'ctrl-0 to ctrl-9
  91.                     IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  92.                         IF ExtendedTimer > NumDelay(i) THEN
  93.                             SELECT CASE i 'WTF are these key codes all over the damn place??
  94.                                 CASE 0: k = 41
  95.                                 CASE 1: k = 33
  96.                                 CASE 2: k = 64
  97.                                 CASE 3: k = 35
  98.                                 CASE 4: k = 36
  99.                                 CASE 5: k = 37
  100.                                 CASE 6: k = 94
  101.                                 CASE 7: k = 38
  102.                                 CASE 8: k = 42
  103.                                 CASE 9: k = 40
  104.                             END SELECT
  105.                             NumDelay(i) = ExtendedTimer + RepeatRate
  106.                         END IF
  107.                     ELSE
  108.                         IF ExtendedTimer > NumDelay(i) THEN k = 48 + i: NumDelay(i) = ExtendedTimer + RepeatRate ELSE k = 0
  109.                     END IF
  110.                 END IF
  111.             NEXT
  112.         END IF
  113.  
  114.  
  115.         'Key up releases for the keys which we've been remapping
  116.  
  117.         SELECT CASE k
  118.             CASE 0 'We didn't read a keypress at all
  119.                 IF PauseDown THEN k = -100019: PauseDown = 0
  120.             CASE 13: JDown = 0 'Enter was hit, not CTRL-J
  121.             CASE 93: MenuDown = 0 '] was hit after menu
  122.             CASE 105: TabDown = 0 'CTRL-I was hit, rather than CTRL-Tab
  123.             CASE 21248: BackspaceDown = 0 'Delete was hit, rather than CTRL-Backspace
  124.             CASE -13 '-13 is the release code for both ENTER and CTRL-J
  125.                 IF JDown THEN k = -106: MenuDown = 0
  126.             CASE -16, -17, -18 'shift release, ctrl release, alt release
  127.                 k = 0 'no need to report these as release codes, if we don't report them as down codes.
  128.                 'Instead, we report these as LEFT/RIGHT shift, ctrl, alt key releases and not just a generic "SHIFT WAS RELEASED" code.
  129.             CASE -44 '-44 is the release code for both SYS and comma
  130.                 IF SysDown THEN k = -100316: SysDown = 0
  131.             CASE -48 TO -57 '0 to 9 shares the same release codes, regardless of if shift is pressed
  132.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'if shift is down
  133.                     SELECT CASE k
  134.                         CASE -48: k = -41 'it's ), not 0
  135.                         CASE -49: k = -33 '!, not 1
  136.                         CASE -50: k = -64 '@, not 2
  137.                         CASE -51: k = -35 '#, not 3
  138.                         CASE -52: k = -36 '$, not 4
  139.                         CASE -53: k = -37 '%, not 5
  140.                         CASE -54: k = -94 '^, not 6
  141.                         CASE -55: k = -38 '&, not 7
  142.                         CASE -56: k = -42 '*, not 8
  143.                         CASE -57: k = -40 '(, not 9
  144.                     END SELECT
  145.                 END IF
  146.             CASE -93 '-93 is the release code for both MENU and ]
  147.                 IF MenuDown THEN k = -100319: MenuDown = 0
  148.             CASE -105 '-105 is the release code for both CTRL-TAB and CTRL-I
  149.                 IF TabDown THEN k = -9: TabDown = 0
  150.             CASE -106 '-106 is the release code for both CTRL-J and ENTER
  151.                 IF JDown THEN k = -106: TabDown = 0
  152.             CASE -144: k = -100300 'Num lock key currently returns virtual code release values/
  153.             CASE -145: k = -100302 'Scroll lock key currently returns virtual code release values
  154.             CASE -192 'CTRL-` and CTRL-~ both  map to this key, but only up a key up event
  155.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  156.                     k = -126
  157.                 ELSE
  158.                     k = -96
  159.                 END IF
  160.             CASE -21248 '-21248 is the release code for both CTRL-Backspace and Delete
  161.                 IF BackspaceDown THEN k = -8: BackspaceDown = 0
  162.         END SELECT
  163.     END IF
  164.     Keyhit = k
  165.  
  166. FUNCTION ExtendedTimer##
  167.     'modified extendedtimer to store the old day's count, and not have to recalculate it every time the routine is called.
  168.  
  169.     STATIC olds AS _FLOAT, old_day AS _FLOAT
  170.     DIM m AS INTEGER, d AS INTEGER, y AS INTEGER
  171.     DIM s AS _FLOAT, day AS STRING
  172.     IF olds = 0 THEN 'calculate the day the first time the extended timer runs
  173.         day = DATE$
  174.         m = VAL(LEFT$(day, 2))
  175.         d = VAL(MID$(day, 4, 2))
  176.         y = VAL(RIGHT$(day, 4)) - 1970
  177.         SELECT CASE m 'Add the number of days for each previous month passed
  178.             CASE 2: d = d + 31
  179.             CASE 3: d = d + 59
  180.             CASE 4: d = d + 90
  181.             CASE 5: d = d + 120
  182.             CASE 6: d = d + 151
  183.             CASE 7: d = d + 181
  184.             CASE 8: d = d + 212
  185.             CASE 9: d = d + 243
  186.             CASE 10: d = d + 273
  187.             CASE 11: d = d + 304
  188.             CASE 12: d = d + 334
  189.         END SELECT
  190.         IF (y MOD 4) = 2 AND m > 2 THEN d = d + 1 'add a day if this is leap year and we're past february
  191.         d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
  192.         d = d + (y + 2) \ 4 'add in days for leap years passed
  193.         s = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
  194.         old_day = s
  195.     END IF
  196.     IF TIMER < oldt THEN 'we went from 23:59:59 (a second before midnight) to 0:0:0 (midnight)
  197.         old_day = s + 83400 'add another worth of seconds to our counter
  198.     END IF
  199.     oldt = TIMER
  200.     olds = old_day + oldt
  201.     ExtendedTimer## = olds
  202.  

The list of issues which I've found so far boggles my mind!  I thought this was just a small issue, limited to a very few number of select keys which was affected.  From what I've actually discovered, I'm now wondering why the heck _KEYHIT and _KEYDOWN are used by anyone!!  Things are broken BAD, and I had no idea...

The Menu key is completely broken.
CTRL-Tab reports itself as CTRL-I.
CTRL-Backspace reports itself as Delete.
CTRL-Tilde and CTRL-Backquote aren't supported at all.
CTRL- 0 to CTRL-9 have no keydown codes, only keyup codes.
CTRL-) to CTRL-( have no codes whatsoever.  They just report themselves as CTRL-0 to CTRL-9 codes.

And that's only the glitches which I've sorted out this afternoon, while playing around with the values.  Some of these return no values whatsoever, others return values which are completely unfathomable (CTRL-@ returns a value of 200000, or so.), and others return duplicate values on things...

Try the program above and test it out with some of the CTRL-key presses as mentioned in the comments in the code, and you can see the differences for yourself.  Who knew the command was broken so badly?!!

If anyone finds code combinations which don't work as they should, kindly report them to me and I'll add them to this little routine for now, so it can be used as a patch until someone can actually sort out what the heck is so wrong with QB64 itself.  :)



EDIT: Code assembled into Library format and attached to the bottom of this post.

Just $INCLUDE the file below, at the bottom of your desired QB64 program, and then make use of Keyhit instead of _KEYHIT.  Only works for Windows.  Linux/Mac users will need to find a different way to patch the incorrect return values, if they need to.
* Keyhit Patch.BM (Filesize: 10.7 KB, Downloads: 194)
« Last Edit: December 27, 2019, 06:49:59 am by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: A new Keyhit
« Reply #1 on: December 26, 2019, 05:54:05 pm »
... Things are broken BAD, and I had no idea ...
... Who knew the command was broken so badly?!! ...

Oh holy Steve, finally....
...finally somebody else recognized it, it's not only _KEYHIT, it's all keyboard based  input, INKEY$, INPUT$, (LINE) INPUT.

I was complaining about this at least a dozens times over at the old qb64(.)net forum since 2013 when Galleon switched from SDL to GL. Never anything happend until I got really wiry about, then most told me I shall forget about, as SDL behavior will never come back and I should better adopt myself to GL or go away.

Even here https://qb64forum.alephc.xyz/index.php?topic=304.msg104079#msg104079 I mentioned it again when v1.3 was released, but nobody picked it up (again), because it was more important how _INSTRREV works and I was so evil to be completely on Fellippes side here.

Then, later in the year I came up with my own INKEY$ replacement (https://qb64forum.alephc.xyz/index.php?topic=1385.msg105704#msg105704) to be at least able to work in german with CP437 again, as I knew & loved it from the old SDL version. Unfortunately this approch does not work international, as every country has other KB layouts and returns different values.

So, yes Steve, I know how severly this is broken since the birth of QB64 GL...
« Last Edit: January 31, 2022, 06:57:45 pm by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #2 on: December 26, 2019, 06:30:50 pm »
Test the other input routine out, Rho; see if it’ll work for your needs.  https://www.qb64.org/forum/index.php?topic=2030.0

The issue with the keys seems to be things just returning cross-values.  A lot of the faulty keyhit codes are actually the proper return codes for the virtual keyboard codes...  (like the 192 for tilde and backquote). 

I’ve dug into libqb a few times in the past, but can’t decipher how it decides which value to return back to us (or why it skips over several of them completely).  For now, the best I can do is Fraken-patch two different input methods together to get them to return the proper codes for us — which is what the code in the topic here is all about.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: A new Keyhit
« Reply #3 on: December 26, 2019, 07:27:27 pm »
I've tested the routine from this topic. It works until it comes to the international chars, which require an accent key or AltGr, or in short extended ASCII 128-255. In general I don't need _KEYHIT so much, INKEY$ is much more important for me, that's why I made my mentioned german keyboard to CP437 INKEY$ replacement.

As far as I could figure out the differences in the input behavior between SDL vs. GL it seems to me as in the old SDL version raw key codes were collected from keyboard, which then were mapped by SDL to the CP437 or whatever CP was selected in the IDE.

For GL it seems that not raw key codes are collected, but the codes already mapped to the systems default CP and then without further mapping by GL these codes are simply returned to the IDE, hence it works only if your selected IDE codepage does match the systems default codepage.

Another thinkable scenario is, that still raw key codes are collected, but GL does always map these to the systems default codepage, instead of the selected IDE codepage.

However, you guys out there, who not have an AltGr key on their keyboards should still keep the possibility of such an AltGr in mind when checking for the ALT modifier, cause AltGr is a complete other (4th) modifier. For correct behavior you should always use the following logic:

Code: QB64: [Select]
  1. '--- get modifiers ---
  2. shift% = _KEYDOWN(100303) OR _KEYDOWN(100304)
  3. ctrl% = _KEYDOWN(100305) OR _KEYDOWN(100306)
  4. altgr% = _KEYDOWN(100307) AND _KEYDOWN(100306)
  5. alt% = (_KEYDOWN(100307) OR _KEYDOWN(100308)) AND NOT altgr%
  6.  
« Last Edit: December 30, 2019, 09:15:05 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #4 on: December 26, 2019, 08:02:39 pm »
Isn’t the AltGr key just a combination CTRL-ALT key?  At least, I think Ctrl-Alt is the method used to generate AltGr keystrokes on a keyboard without that key.  Or it used to be.  Or else my memory is crap anymore and it was just some program I used that substituted Alt-Ctrl for AltGr.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: A new Keyhit
« Reply #5 on: December 26, 2019, 08:31:31 pm »
To be exact AltGr internally is a combination of right ALT + left CTRL, and used to generate chars, it should  otherwise not trigger activation of the menu bar, which is unfortunately the case in many programs (even in big comercial apps sometimes).

E.g. InForm has this problem too. As the backslash is an AltGr char on german keyboards, I can't do such simple things like writing a path\file into an input field, because I need to press the AltGr key to get the \, but that's immediately activating the menu and more worse deletes the already written path, because it wasn't confirmed yet with enter. The only way to enter such a path\file for me is to write it in a text editor, copy it and then paste it into InForm.

Same is for instance the alternative ALT + number input method. It should not work with AltGr, because here on my german keyboard I've lots of AltGr chars on the number keys (²³{[]}).

So the easiest is to treat it as a complete different modifier as I show in my code snippet above.
« Last Edit: December 30, 2019, 09:14:17 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #6 on: December 26, 2019, 09:09:52 pm »
Do you have a list of the keys and scan codes for the AltGr keys?  I’ll add them to my work here while I’m at it.  If not, I might be able to get them from the QB64 SDL version.  As long as I know what values they should be, I can tweak the output, as above, to produce suitable results. 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #7 on: December 27, 2019, 03:42:21 am »
Updated the original post to make several more corrections.  So far, the list of bugs here are:

  • Numpad key 5 doesn't return down presses, repeat presses, or correct values if numlock is active.
  • Pause key has no interaction at all.  (And probably still won't, as QB64 reserves it for internal use to pause a running program.  Maybe with $CHECKING:OFF, it will now.)
  • Menu key which doesn't work at all on keydown and gives a false result on keyup.
  • System key, which currently thinks it's a comma.
  • Num lock key, whose value toggles itself from up to down with numlock state and returns a false code for num lock release.
  • Scroll lock key, which has no down value, and returns a false value for keyup.
  • CTRL-Tab reports itself as CTRL-I.
  • CTRL-Backspace reports itself as Delete.
  • CTRL-Tilde and CTRL-Backquote aren't supported at all.
  • CTRL-J thinks it's CTRL-ENTER.
  • CTRL- 0 to CTRL-9 have no keydown codes, only keyup codes.
  • Alt, Shift, Ctrl return two keyup codes, but only one keydown code.
  • CTRL-) to CTRL-( have no codes whatsoever.  They just report themselves as CTRL-0 to CTRL-9 codes.

Are there more glitches than those in _KEYHIT?  I'm willing to bet that there sure are!  I don't have a copy of the AltGr codes which Rho was talking about above, so I can't code those into the routine.  I wouldn't swear that every change here will work with all languages or keyboard layouts (not without a lot of people testing things).  I've probably missed various other keystroke combinations like CTRL-ALT-SHIFT-W or some odd tweak like that.

If anyone ever comes up with a missing code/incorrect combo/wrong value, then let me know and I'll do my best to correct the issue and map the values back to what they should be for us.



One thing I want to point out for all users of this patched _KEYHIT routine:

Code: [Select]
    DIM RepeatRate AS _FLOAT: RepeatRate = 0.2 'necessary so we don't spam key returns with GetAsyncKeyState
Since a lot of these codes don't have any sort of keydown code associated with them, I had to use GetAsyncKeyState to find their status.  GetAsyncKeyState works a lot like a _KEYDOWN event, moreso than a _KEYHIT event, so it'd endlessly spam up with keyhit codes.  To stop this, I've added in a RepeatRate where it won't return a second keystroke until the proper amount of time has passed.  For me, 0.2 seconds works perfect, or at least close enough to perfect, so that it mimics my keyboard's natural repeat rate.

If you have a slower repeat rate for your keyboard then you might increase that pause.  If you want faster duplicates, decrease the pause between them.  It's a simple variable, for ease of user alteration.

Note 2: These changes only work on Window's Systems, since this uses a window library call.  Sorry Linux/Mac folks; you'll need to work out a different patchwork system for the changes.  At least with this, you can have a list of what's broken, to make fixing it on your own system work better.  (I'm certain Linux/Mac has a substitute GetAsyncKeyState% command.  I just don't know what it is.  Sorry -- I'm a Window's guy!)

https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
    • View Profile
Re: A new Keyhit
« Reply #8 on: December 27, 2019, 08:21:32 am »
I think you go to far with this Steve, according to the CTRL combos, as long as I code (~30yrs) I know the CTRL synonymes for those keys (or better chars):

Esc = CHR$(27) = CTRL+[
Tab = CHR$(9) = CTRL-I
Enter (as CR (CHR$(13))) = CTRL-J
Enter (as LF (CHR$(10))) = CTRL-M

and there were some more for vertical tab, form feed, and backspace which I don't remember. I guess this comes from the ancient DOS days and was probably also used to control printers. How much sense it still makes these days is on another piece of paper.

As to the AltGr chars, I don't know if its a good idea to hardcode those into your routine, as they are probably very depenend on language or even regions of the same language. E.g. German KBs are also different between Germany, Switzerland and Luxemburg, its all german, but different :) Usually you find the AltGr triggered chars in the bottom-right corner of the Keys you have to press while holding down AltGr.

However, do you know how the brake on your car works? Maybe yes, maybe no, and it doesn't matter. What's important is, that you slow down immediately when you kick the pedal.
Same here, I'm not interrested in scancodes, or if a key is currently pressed or released, what's really important is, that an @ appears on the screen at my INPUT prompt, when I press the key(s) to get an @, dosen't matter if this @ got its own key on the KB, is an AltGr combo or even a impossible multiple key combo which would break your fingers. And also it shouldn't matter which codepage is selected in the IDE or your program. If the @ is available on that specific CP (and it should in all), then print it, done. And this must work with all chars, doesn't matter how many keys you've to press to get them.

That's, as far as I experience it from my daily work with QB64, works in the SDL version, but not in GL. There must be checked what's broken with the transition from SDL to GL, and if required the complete keyboard input system might need to rewritten from scratch in the GL version. To plug in a custom input routine in every program and for every country and every codepage is no solution.

I'm afraid, this will never change, as Galleons coding style and formatting is simply to horrible to get through to the deeply hidden truth.
« Last Edit: December 31, 2019, 09:57:26 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #9 on: December 27, 2019, 08:56:07 am »
Quote
Esc = CHR$(27) = CTRL+[
Tab = CHR$(9) = CTRL-I
Enter (as CR (CHR$(13))) = CTRL-J
Enter (as LF (CHR$(10))) = CTRL-M

Those, and all the A-Z keys, were part of the terminal control code set back in the day.

CTRL-0 gave a CHR$(0) value.
CTRL-A gave CHR$(1).
CTRL-Z gave CHR$(26).

The problem with the way KEYHIT implements the scancodes though, is that it only works for a limited few of them and not others.  Why use CTRL-I for Tab (CHR$(9)), when you don’t use CTRL-A for CHR$(1)?  Or CTRL-H for backspace (CHR$(8))?

Keyhit should map a code for each key hit, and then a user can interpret if those codes hold special meaning or not.  As it currently exists, CTRL-I is a duplicate of TAB, so what is CTRL-Tab?  A CTRL-CTRL-I keystroke — which breaks down to a CTRL-I keystroke...

There’s no way to map those as independent process in our code, as they currently exist in the GL version.  CTRL-TAB = CTRL-I.

Going back to the SDL version, as you mentioned, the keys return different values.  TAB is 9, I is 103(?; I’m guessing from my poor memory), and CTRL is a modifier key.  You can tell one from the other, at all times, and that’s the behavior I’ve reimplemented here.  ;)

If one likes CTRL-@ to CTRL ] for CHR$(0) to CHR$(31), they should code those combinations into their program’s input handling/processing routines, rather than have _KEYHIT map multiple buttons to the same code. 

If we keep them separate, we can code CTRL-J for a CHR$(13) key, and CTRL-ENTER can be something different than just a CTRL-CTRL-J keypress.

(And, btw, the return values are even reversed for CTRL-J and CTRL-M, if you compare them to the old control code values.  A to Z represented CHR$(1) to CHR$(26)...  CTRL-J should be CHR$(10), while CTRL-M is CHR$(13) — or ENTER.)



Quote
I'm afraid, this will never change, as Galleons coding style and formatting is simply to horrible to get through to the deeply hidden truth.

I’ve tried a few times, but I just can’t sort it out at all.  Honestly, I think at some point, we should consider going back and add SDL 2 into our library mix, and let it handle the font and keyboard routines for us.  Eventually, I might try and go that route for my own repo sometime in the future.


Edit:  CTRL-@ to CTRL-_ for 0 to 31.  https://www.windmill.co.uk/ascii-control-codes.html

13 was ENTER (CTRL-M), so CTRL-J is still returning a wrong value, no matter how you look at it.  ;)
« Last Edit: December 27, 2019, 09:09:58 am by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A new Keyhit
« Reply #10 on: December 27, 2019, 05:13:45 pm »
Code: QB64: [Select]
  1.     k = Keyhit
  2.     IF k <> 0 THEN PRINT k, oldkeyhit&
  3.     _LIMIT 30
  4.  
  5.  
  6. $IF WIN THEN
  7.     FUNCTION Keyhit&
  8.         DECLARE LIBRARY 'function is already used by QB64 so "User32" is not required
  9.             FUNCTION GetAsyncKeyState% (BYVAL vkey AS LONG)
  10.         END DECLARE
  11.  
  12.         CONST AcceptMouseKeys = -1, AcceptMusicKeys = -1
  13.  
  14.  
  15.         STATIC MenuDelay AS _FLOAT, MenuDown AS _BYTE
  16.         STATIC TabDelay AS _FLOAT, TabDown AS _BYTE
  17.         STATIC BackspaceDelay AS _FLOAT, BackspaceDown AS _BYTE
  18.         STATIC TildeDelay AS _FLOAT, BackQuoteDelay AS _FLOAT
  19.         STATIC NumDelay(0 TO 19) AS _FLOAT
  20.         STATIC JDelay AS _FLOAT, JDown AS _BYTE
  21.         STATIC SysDelay AS _FLOAT, SysDown AS _BYTE
  22.         STATIC ScrollDelay AS _FLOAT, ScrollDown AS _BYTE
  23.         STATIC PauseDelay AS _FLOAT, PauseDown AS _BYTE
  24.         STATIC NumDelay AS _FLOAT, NumDown AS _BYTE 'num lock
  25.         STATIC Num5Delay AS _FLOAT, Num5Down AS _BYTE 'number 5 key on the num pad
  26.         STATIC RepeatRate AS _FLOAT: RepeatRate = 0.2 'necessary so we don't spam key returns with GetAsyncKeyState
  27.         STATIC MouseDelay(1 TO 6) AS _FLOAT, MouseDown(1 TO 6) AS _BYTE
  28.         STATIC MusicDelay(173 TO 179) AS _FLOAT, MusicDown(173 TO 179) AS _BYTE
  29.  
  30.         k = _KEYHIT
  31.  
  32.         IF _WINDOWHASFOCUS THEN
  33.             'Fix for number 5 on the num pad, when numlock is active
  34.             IF GetAsyncKeyState(12) THEN 'pause key, which has no value (and probably still won't, as QB64 uses it as a command code for pausing program execution
  35.                 'and gives a false result on a keyup
  36.                 IF ExtendedTimer > Num5Delay THEN k = 19456: Num5Down = -1: Num5Delay = ExtendedTimer + RepeatRate ELSE k = 0
  37.             ELSE
  38.                 IF Num5Down THEN k = -19456: Num5Down = 0
  39.             END IF
  40.  
  41.             'Fix for Pause Key
  42.             IF GetAsyncKeyState(19) THEN 'pause key, which has no value (and probably still won't, as QB64 uses it as a command code for pausing program execution
  43.                 'and gives a false result on a keyup
  44.                 IF ExtendedTimer > PauseDelay THEN k = 100019: PauseDown = -1: PauseDelay = ExtendedTimer + RepeatRate ELSE k = 0
  45.             END IF
  46.  
  47.             'Fix for the Menu Key
  48.             IF GetAsyncKeyState(93) THEN 'menu key, which doesn't work at all on keydown
  49.                 'and gives a false result on a keyup
  50.                 IF ExtendedTimer > MenuDelay THEN k = 100319: MenuDown = -1: MenuDelay = ExtendedTimer + RepeatRate ELSE k = 0
  51.             END IF
  52.  
  53.             'Fix for SYS Key
  54.             IF GetAsyncKeyState(44) THEN 'SYS key, which currently thinks it's a comma
  55.                 'and gives a false result on a keyup
  56.                 IF ExtendedTimer > SysDelay THEN k = 100316: SysDown = -1: SysDelay = ExtendedTimer + RepeatRate ELSE k = 0
  57.             END IF
  58.             'Fix for NUM LOCK Key
  59.             IF GetAsyncKeyState(144) THEN 'num lock key, which currently toggles itself with the down value
  60.                 'changing from a up code to a down code, depending on numlock status
  61.                 'and gives a false result on keyup
  62.                 IF ExtendedTimer > NumDelay THEN k = 100300: NumDelay = ExtendedTimer + RepeatRate ELSE k = 0
  63.             END IF
  64.  
  65.             'FIX for SCROLL LOCK Key
  66.             IF GetAsyncKeyState(145) THEN 'scroll lock key, which has no down value
  67.                 'and gives a false result on keyup
  68.                 IF ExtendedTimer > ScrollDelay THEN k = 100302: ScrollDelay = ExtendedTimer + RepeatRate ELSE k = 0
  69.             END IF
  70.  
  71.  
  72.  
  73.             IF _KEYDOWN(100305) OR _KEYDOWN(100306) THEN 'control is down
  74.                 'Fix for CTRL-Tab
  75.                 IF GetAsyncKeyState(9) THEN 'ctrl-tab; return tab, not CTRL-I
  76.                     IF ExtendedTimer > TabDelay THEN k = 9: TabDown = -1: TabDelay = ExtendedTimer + RepeatRate ELSE k = 0
  77.                 END IF
  78.                 'Fix for CTRL-Backspace
  79.                 IF GetAsyncKeyState(8) THEN 'ctrl-backspace; return backspace, not CTRL-Delete
  80.                     IF ExtendedTimer > BackspaceDelay THEN k = 8: BackspaceDown = -1: BackspaceDelay = ExtendedTimer + RepeatRate ELSE k = 0
  81.                 END IF
  82.                 'Fix for CTRL-J
  83.                 IF GetAsyncKeyState(74) THEN 'CTRL-J; return ctrl-j, not ctrl-return
  84.                     IF ExtendedTimer > JDelay THEN k = 106: JDown = -1: JDelay = ExtendedTimer + RepeatRate ELSE k = 0
  85.                 END IF
  86.  
  87.                 'Fix for CTRL-Tilde, CTRL-Backquote
  88.                 IF GetAsyncKeyState(192) THEN 'ctrl-tilde, ctrl-backquote -- neither are supported
  89.                     IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  90.                         IF ExtendedTimer > TildeDelay THEN k = 126: TildeDelay = ExtendedTimer + RepeatRate ELSE k = 0
  91.                     ELSE
  92.                         IF ExtendedTimer > BackQuoteDelay THEN k = 96: BackQuoteDelay = ExtendedTimer + RepeatRate ELSE k = 0
  93.                     END IF
  94.                 END IF
  95.                 'Fix for CTRL-0 to CTRL-9
  96.                 FOR i = 0 TO 9
  97.                     IF GetAsyncKeyState(i + 48) THEN 'ctrl-0 to ctrl-9
  98.                         IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  99.                             IF ExtendedTimer > NumDelay(i) THEN
  100.                                 SELECT CASE i 'WTF are these key codes all over the damn place??
  101.                                     CASE 0: k = 41
  102.                                     CASE 1: k = 33
  103.                                     CASE 2: k = 64
  104.                                     CASE 3: k = 35
  105.                                     CASE 4: k = 36
  106.                                     CASE 5: k = 37
  107.                                     CASE 6: k = 94
  108.                                     CASE 7: k = 38
  109.                                     CASE 8: k = 42
  110.                                     CASE 9: k = 40
  111.                                 END SELECT
  112.                                 NumDelay(i) = ExtendedTimer + RepeatRate
  113.                             END IF
  114.                         ELSE
  115.                             IF ExtendedTimer > NumDelay(i) THEN k = 48 + i: NumDelay(i) = ExtendedTimer + RepeatRate ELSE k = 0
  116.                         END IF
  117.                     END IF
  118.                 NEXT
  119.             END IF
  120.             IF AcceptMouseKeys = -1 THEN
  121.                 FOR i = 1 TO 6
  122.                     IF GetAsyncKeyState(i) THEN 'mouse left, right, nothing, center, button4, button5
  123.                         IF ExtendedTimer > MouseDelay(i) THEN
  124.                             k = i
  125.                             MouseDelay(i) = ExtendedTimer + RepeatRate
  126.                             MouseDown(i) = True
  127.                         ELSE
  128.                             k = 0
  129.                         END IF
  130.                     ELSE
  131.                         IF MouseDown(i) THEN k = -i: MouseDown(i) = 0
  132.                     END IF
  133.                 NEXT
  134.             END IF
  135.             IF AcceptMusicKeys = -1 THEN
  136.                 FOR i = 173 TO 179
  137.                     IF GetAsyncKeyState(i) THEN 'mouse left, right, nothing, center, button4, button5
  138.                         IF ExtendedTimer > MusicDelay(i) THEN
  139.                             k = i
  140.                             MusicDelay(i) = ExtendedTimer + RepeatRate
  141.                             MusicDown(i) = True
  142.                         ELSE
  143.                             k = 0
  144.                         END IF
  145.                     ELSE
  146.                         IF MusicDown(i) THEN k = -i: MusicDown(i) = 0
  147.                     END IF
  148.                 NEXT
  149.             END IF
  150.  
  151.             'Key up releases for the keys which we've been remapping
  152.  
  153.             SELECT CASE k
  154.                 CASE 0 'We didn't read a keypress at all
  155.                     IF PauseDown THEN k = -100019: PauseDown = 0
  156.                 CASE 13: JDown = 0 'Enter was hit, not CTRL-J
  157.                 CASE 93: MenuDown = 0 '] was hit after menu
  158.                 CASE 105: TabDown = 0 'CTRL-I was hit, rather than CTRL-Tab
  159.                 CASE 21248: BackspaceDown = 0 'Delete was hit, rather than CTRL-Backspace
  160.                 CASE -13 '-13 is the release code for both ENTER and CTRL-J
  161.                     IF JDown THEN k = -106: MenuDown = 0
  162.                 CASE -16, -17, -18 'shift release, ctrl release, alt release
  163.                     k = 0 'no need to report these as release codes, if we don't report them as down codes.
  164.                     'Instead, we report these as LEFT/RIGHT shift, ctrl, alt key releases and not just a generic "SHIFT WAS RELEASED" code.
  165.                 CASE -44 '-44 is the release code for both SYS and comma
  166.                     IF SysDown THEN k = -100316: SysDown = 0
  167.                 CASE -48 TO -57 '0 to 9 shares the same release codes, regardless of if shift is pressed
  168.                     IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'if shift is down
  169.                         SELECT CASE k
  170.                             CASE -48: k = -41 'it's ), not 0
  171.                             CASE -49: k = -33 '!, not 1
  172.                             CASE -50: k = -64 '@, not 2
  173.                             CASE -51: k = -35 '#, not 3
  174.                             CASE -52: k = -36 '$, not 4
  175.                             CASE -53: k = -37 '%, not 5
  176.                             CASE -54: k = -94 '^, not 6
  177.                             CASE -55: k = -38 '&, not 7
  178.                             CASE -56: k = -42 '*, not 8
  179.                             CASE -57: k = -40 '(, not 9
  180.                         END SELECT
  181.                     END IF
  182.                 CASE -93 '-93 is the release code for both MENU and ]
  183.                     IF MenuDown THEN k = -100319: MenuDown = 0
  184.                 CASE -105 '-105 is the release code for both CTRL-TAB and CTRL-I
  185.                     IF TabDown THEN k = -9: TabDown = 0
  186.                 CASE -106 '-106 is the release code for both CTRL-J and ENTER
  187.                     IF JDown THEN k = -106: TabDown = 0
  188.                 CASE -144: k = -100300 'Num lock key currently returns virtual code release values/
  189.                 CASE -145: k = -100302 'Scroll lock key currently returns virtual code release values
  190.                 CASE -192 'CTRL-` and CTRL-~ both  map to this key, but only up a key up event
  191.                     IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'determine which it is, via if shift is down
  192.                         k = -126
  193.                     ELSE
  194.                         k = -96
  195.                     END IF
  196.                 CASE -21248 '-21248 is the release code for both CTRL-Backspace and Delete
  197.                     IF BackspaceDown THEN k = -8: BackspaceDown = 0
  198.             END SELECT
  199.         END IF
  200.         Keyhit = k
  201.  
  202. FUNCTION ExtendedTimer##
  203.     'modified extendedtimer to store the old day's count, and not have to recalculate it every time the routine is called.
  204.  
  205.     STATIC olds AS _FLOAT, old_day AS _FLOAT
  206.     DIM m AS INTEGER, d AS INTEGER, y AS INTEGER
  207.     DIM s AS _FLOAT, day AS STRING
  208.     IF olds = 0 THEN 'calculate the day the first time the extended timer runs
  209.         day = DATE$
  210.         m = VAL(LEFT$(day, 2))
  211.         d = VAL(MID$(day, 4, 2))
  212.         y = VAL(RIGHT$(day, 4)) - 1970
  213.         SELECT CASE m 'Add the number of days for each previous month passed
  214.             CASE 2: d = d + 31
  215.             CASE 3: d = d + 59
  216.             CASE 4: d = d + 90
  217.             CASE 5: d = d + 120
  218.             CASE 6: d = d + 151
  219.             CASE 7: d = d + 181
  220.             CASE 8: d = d + 212
  221.             CASE 9: d = d + 243
  222.             CASE 10: d = d + 273
  223.             CASE 11: d = d + 304
  224.             CASE 12: d = d + 334
  225.         END SELECT
  226.         IF (y MOD 4) = 2 AND m > 2 THEN d = d + 1 'add a day if this is leap year and we're past february
  227.         d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
  228.         d = d + (y + 2) \ 4 'add in days for leap years passed
  229.         s = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
  230.         old_day = s
  231.     END IF
  232.     IF TIMER < oldt THEN 'we went from 23:59:59 (a second before midnight) to 0:0:0 (midnight)
  233.         old_day = s + 83400 'add another worth of seconds to our counter
  234.     END IF
  235.     oldt = TIMER
  236.     olds = old_day + oldt
  237.     ExtendedTimer## = olds
  238.  

Added support for NEW STUFF!!

This now returns us a keyhit code of:

1) Left Mouse
2) Right Mouse
4) Middle Mouse
5) Extra Mouse Button 1
6) Extra Mouse Button 2
173) Mute
174) Vol Down
175) Vol Up
176) Fast Forward/Next
177) Rewind/Back
178) Stop
179) Play/Pause

+ value is key down, - value is key up.

These aren't normally key returns that are available to us via _KEYHIT, but they're all keys on my keyboard, and values which I can read and test for with GetAsyncKeyState, so I added them into the patch for my own personal use.  If somebody else doesn't care for the extended button checking, just go in and change the following two CONST values to 0 and you can remove them from the process:

Code: [Select]
        CONST AcceptMouseKeys = -1, AcceptMusicKeys = -1 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Re: A new Keyhit
« Reply #11 on: December 28, 2019, 07:26:09 am »
Hi
it is an huge and interesting work to decode for _Keyhit

In my old posts on .net I have noted that I can get different keycode depending if I use the original notebook keyboard or an USB keyboard the same are IT keyboards.

So the question that I pass you to let think... How much does _Keyhit recognize the keycode indipendently from hardware keyboard?
My fear is that you are going to do a very hard work that I can use only if you sell me your keyboard!
Thanks to read
Programming isn't difficult, only it's  consuming time and coffee