Wednesday, September 01, 2021

Typematic Rates

Okay, let's talk a bit about typematic rates.

The idea of a typematic rate is simple: when a key is pressed and held, there exists some amount of delay after registering the first key-down event before the operating system decides that the key is considered to be held and should emit the same key code repeatedly at a given rate. The most obvious manners in which this shows up is from using the arrow keys to navigate through a document, or to move the cursor/caret back and forth horizontally through a line of text, or to delete/backspace through a whole bunch of text.

Or if one uses the keyboard to play computer games, any form of keyboard-related navigation will reveal this behaviour as well.

With that out of the way, let's talk about how these two parameters (delay, repeat-rate) can be adjusted.

In the old days of DOS, one would use the mode command in the form of
mode con rate=32 delay=1
to set the keyboard repeat rate to the highest value [of 32] with the lowest delay [of 1]. The rate is given in units of characters-per-second, or just Hertz (Hz), while the delay is provided in units of 0.25 s. So the most rapid that the keyboard can go is 32 Hz (31.25 ms) with an initial delay of 0.25 s (250 ms).

When Windows came about, there was a new keyboard repeat rate/delay control mechanism. This can be easily reached through Windows+R, followed by typing ``control keyboard'' in the ensuing box. This will bring up the ``Keyboard Properties'' dialog box, where the typematic rate is to be set under the ``Speed'' tab. Instead of hard numbers like the mode command, we get sliders that state delays from ``Long'' to ``Short'', and then ``Slow'' to ``Fast''. A cursory search did not reveal any information about any exact numbers here, but considering how the values seem to mimic the range of the old mode command, I will assume that they are similar. I will get to why I believe so in a bit.

Anyway, eventually though, even the fastest settings that were available felt sluggish to me, and I don't mean it just in the ``repeated key hold'' sort of way. Even regular typing was starting to feel sluggish, and this is after applying the ``performance tweak'' (deselecting ``Animate controls and elements inside windows'' under the ``Visual Effects'' tab from the ``Performance Options'' window that is called from the ``Settings'' button under the ``Advanced'' tab of the ``System Properties'' window) to force the newest versions of Microsoft Office to not fuck around with the apparent cursor speed with their ``smooth animation'' bullshit that completely deconstructed the responsiveness of cursor movement. Maybe it is because my typematic rate has increased, or maybe it is due to the higher refresh rate of the screens that I am working on, but no matter the reason, the 32 Hz (31.25 ms) repeat rate with 250 ms delay was no longer cutting it.

I had to go faster. But how?

Salvation came from a completely unexpected source: FilterKeys. Traditionally, this is one of those options that any person who games will want to look at immediately because of the triggering shortcut: holding the shift key for 8 s, an action that will often happen when one is trying to run for long periods of time in an FPS. FilterKeys when activated normally are bad for the reason that they tend to drop extra keystrokes as part of their accessibility slant---it allows people who have unsteady typing to have longer delays in between the emission of key code events to allow their slower finger movements to not generate spurious characters especially in typing.

But FilterKeys have a dark secret: they actually provide a means to fine-tune keyboard-related settings down to millisecond precision. And because of that, I have been using the following registry tweak to obtain my favoured typing rate:
Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Control Panel\Accessibility\Keyboard Response]
"AutoRepeatDelay"="188"
"AutoRepeatRate"="10"
"BounceTime"="0"
"DelayBeforeAcceptance"="0"
"Flags"="59"
"Last BounceKey Setting"=dword:00000000
"Last Valid Delay"=dword:00000000
"Last Valid Repeat"=dword:00000000
"Last Valid Wait"=dword:000003e8
The delay is now set to 188 ms instead of the 250 ms lowest value available, while the repeat rate is set to being every 10 ms (100 Hz) as opposed to being every 31.25 ms (32 Hz); a 25% shorter delay with slightly more than 3× the repeat rate---this is extremely noticeable. It really makes my typing much more comfortable than before.

That would have been the end of the story for today's post, except for one thing: there were some games on Steam that mysteriously destroy my keyboard settings after I exit out of the game. I could restore the typematic rate, but it would require me to log out and back in again to effect the changes that were in the Windows Registry. The notorious game that kept giving me this problem was N++. It was very problematic because I liked to play N++ in short bursts instead of having a long marathon session as a means of relaxing in between through a paradoxical hyper-concentration process on something completely unrelated. It started to get old each time this happened, and eventually I was annoyed enough that I uninstalled it from Edythe-III and never installed it on Eileen-II at all.

Unfortunately, my favourite game, Jupiter Hell started showing similar symptoms recently. I didn't want to have it go the way N++ did, and wondered if there was a more direct way of making the change happen without having to log out and back in again. It led me to the SystemParametersInfoA() Windows API using SPI_GETFILTERKEYS/SPI_SETFILTERKEYS with associated information set in the FILTERKEYS C-struct. I wrote a simple bit of C++ code and compiled it with g++ in Cygwin, where I had my Win32 API headers installed. The first thing I did was to use SPI_GETFILTERKEYS to probe and output the information for FilterKeys both before running Jupiter Hell and after to see if my hypothesis was true.
And yes, the probe program proved that the FilterKeys settings were nuked after running.

I brought it up to Kornel, and we had a discussion about it. Turns out that the upstream library used to support the changing of keyboard typematic rate from within Jupiter Hell did not actually preserve the original settings due to various technical reasons, and there were some strange Windows-based fuckery that would randomly trigger off the keyboard-related accessibility options. There was no obviously elegant way of solving this in a portable fashion in time for the full release, and a quick fix which nuked all accessibility options was done to ensure that most people would not be affected by it.

Converting the probe program into one that would set the values that I wanted was quite straightforward---the only thing left was to see if the changes would stick. I mean, it's so straightforward that I can put the code (with my personal settings as talked about earlier) here:
#include <windows.h>
#include <winuser.h>

#include <iostream>

int main(void) {
  FILTERKEYS filterKeys;
  BOOL fResult;

  filterKeys.cbSize = sizeof(FILTERKEYS);
  filterKeys.dwFlags = 59;
  filterKeys.iWaitMSec = 0;
  filterKeys.iDelayMSec = 188;
  filterKeys.iRepeatMSec = 10;
  filterKeys.iBounceMSec = 0;

  fResult = SystemParametersInfoA(SPI_SETFILTERKEYS, 
      sizeof(FILTERKEYS), &filterKeys, SPIF_SENDCHANGE);
  if (fResult) {
    std::cout << "Success." << std::endl;
  } else {
    std::cout << "Failed." << std::endl;
  }

  return 0;
}
I compiled it, ran it, and double checked with my probe program. It works perfectly.

So now I have a perfectly working workaround to handle this issue without having to do the log out/in dance. And so, N++ has been installed on Eileen-II now.

I think that's all I have for today. Till the next update.

No comments: