Capture repeated keystrokes due to a key kept pressed

illera88

Member
Hi,

I've been trying to capture the repeated keys prints that a keyboard produces when a key kept being pressed for a while before releasing. When you do that just one letter gets printed for a second and then a bunch of them forward and get printed.

Example:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

I know there are the attachPress() and attachRelease() callbacks and theoretically they could be used to calculate the number of letters that should be printed by measuring the time that passed between both callbacks. But it sounds very complicated and I bet there is a easier and more precise way to know it.

Any idea?

Thank you
 
Please don't create N different threads for the same conversation.

I believe a lot of this is done at the host level. That is for example on PC keyboard controller. You can change the Repeat delay and the repeat rate.
 
I see.
For reference for others that want to do the same, you should measure the time passed between `attachRawPress` and `attachRawRelease` and do some calculations based on that. By default in Windows:

[KeyboardSpeed](https://docs.microsoft.com/en-us/pr...ro/windows-2000-server/cc978659(v=technet.10)) is 31

[KeyboardDelay](https://docs.microsoft.com/en-us/pr...r/cc978658(v=technet.10)?redirectedfrom=MSDN) is 1

I have done a stupid test and keeping pressed a letter in my PC for 10 seconds generates 289 letters (almost 29 per second but take into consideration that the second letter does not get printed until after a second so you can use the value of 32 letters per second after the first second which is similar to what the windows documentation says(30 per second))

Thank you!!
 
@illera88 - A few years ago I created a small library to do repeating key strokes. See attached zip below. It uses elapsedMillis and IntervalTimer to create the repeat rate and delay before starting to repeat a keypress. This works with USBHost_t36.

You can change the repeat rate and delay time by setting two items in:
Code:
struct usbKeyMsg_struct {
	uint8_t keypressed;
	uint8_t keyCode;
	uint8_t modifiers;
	uint8_t keyOEM;
	uint8_t repeatchar;
	uint8_t repeatkeyCode;
	uint8_t	repeatmodifiers;
	uint8_t repeatkeyOEM;
	uint16_t last_keyCode;
[COLOR="#FF0000"]	uint32_t keyRepeatDelay;
	uint32_t keyRepeatRate;[/COLOR]
};

The default rates are set in the init routine:
Code:
void USBKeyboardInit(void) {
	keyboard1.attachPress(OnPress);
	keyboard1.attachRelease(OnRelease);
	head = 0;
	tail = 0;
	ctrl_c_flag = 0;
[COLOR="#FF0000"]	keyboard_msg_p->keyRepeatDelay = 800; // Default repeat delay
	keyboard_msg_p->keyRepeatRate = 500; // Default repeat rate[/COLOR] 
	keyrepeat.begin(checkKeyRepeat,(unsigned long)(keyboard_msg_p->keyRepeatRate * 100));
}

Hope this example is what you are looking for:)
 

Attachments

  • USBKeyBoardRepeatTest.zip
    2.6 KB · Views: 27
Last edited:
Hi @wwatson - maybe at some point we should merge some of that functionality into USBHost_t36 library.

It would probably not use an IntervalTimer, but instead would probably use USBTimerDriver builtin to the USBHost library to do so.
Would probably then also need to add method to KeyboardController function that would allow the initial delay and the repeat delay.

My guess is, if someone did a good job of this, Paul would take in a Pull Request for the functionality.

@illera88 - however this may or may not be exactly the same as what values your PC may have for these delays, as I believe in most all systems, the user can change
these values and as far as I know there is no query function that a keyboard could do to query these values, as the keyboard does not need to know this as it
simply sends messages when a scan code state changes and typically this state will contain up to 6 keys being pressed at once...

It is then up to the host to decide what to do with these states...

Note: some keyboards support a larger number of keys being pressed at once, but those are not fully supported in the keyboardController... For most of these you can get them to work by telling the keyboard to go into BOOT mode, which again has a standard message format.
 
Hi @wwatson - maybe at some point we should merge some of that functionality into USBHost_t36 library.

It would probably not use an IntervalTimer, but instead would probably use USBTimerDriver builtin to the USBHost library to do so.
Would probably then also need to add method to KeyboardController function that would allow the initial delay and the repeat delay.

My guess is, if someone did a good job of this, Paul would take in a Pull Request for the functionality.

@illera88 - however this may or may not be exactly the same as what values your PC may have for these delays, as I believe in most all systems, the user can change
these values and as far as I know there is no query function that a keyboard could do to query these values, as the keyboard does not need to know this as it
simply sends messages when a scan code state changes and typically this state will contain up to 6 keys being pressed at once...

It is then up to the host to decide what to do with these states...

Note: some keyboards support a larger number of keys being pressed at once, but those are not fully supported in the keyboardController... For most of these you can get them to work by telling the keyboard to go into BOOT mode, which again has a standard message format.

Thanks for the input:) I have not used the USBTimerDriver before but since it is available that would be a better option. When I get some time I will play with it. Still buried in Teensyext4 which is coming along nicely. But that is another subject:)
 
@illera88 - however this may or may not be exactly the same as what values your PC may have for these delays, as I believe in most all systems, the user can change
these values and as far as I know there is no query function that a keyboard could do to query these values, as the keyboard does not need to know this as it
simply sends messages when a scan code state changes and typically this state will contain up to 6 keys being pressed at once...
In case anybody is interested, here's some further info.

We are talking about devices using the USB Human Interface Device specification; the current being Device Class Definition for Human Interface Devices 1.11 (PDF) (especially Appendix C: Keyboard implementation onwards) and HID Usage tables 1.3 (PDF) (page 88 onwards).

HID subclass 1 defines two boot devices: boot keyboards and boot mice, with specific report formats, that should be compatible with all USB devices that support keyboards and mice. See Device Class Definition for Human Interface Devices 1.11 (PDF), Appendix B. Essentially, a boot keyboard reports keypresses and key releases using 8-byte reports, and accepts 1-byte output reports to set keyboard LEDs:
Code:
struct boot_keyboard_event {
    unsigned char  modifiers;
    unsigned char  reserved;  /* Zero */
    unsigned char  keycodes[6];
};

struct boot_keyboard_output_event {
    unsigned char  state;  /* Bit 0: NumLock, 1: CapsLock, 2: ScrollLock, 3: Compose, 4: Kana, 5-7: reserved. */
};
Each entry in the keycodes[] array is either 0, or specifies a non-modifier key that is currently pressed; see HID Usages and Descriptions, section 10: Keyboard/Keypad Page (0x07), usage type "SEL". Their order in the keycodes[] array in struct boot_keyboard_event should be irrelevant. Because of this limitation, boot protocol keyboards can support at most 6 simultaneous non-modifier keys. The modifier keys are from bit 0 (LSB) to bit 7 (MSB): Left Control, Left Shift, Left Alt, Left GUI, Right Control, Right Shift, Right Alt, and Right GUI. See the previous link, pages 93-94, codes 224-231 (0xE0-0xE7, marked DV in the table, as they comprise the modifier bits).

Similarly, a boot mouse reports events using 3-byte reports:
Code:
struct boot_mouse {
    unsigned char  buttons;
    signed char    deltaX;
    signed char    deltaY;
};

The USB device itself specifies what kinds of reports it produces using essentially a bit-packing format, various operating systems have certain expectations as to what they actually support. Values and bitmaps need to be byte-aligned, for example. Linux tends to be quite easy and forgiving, except for nonstandard workarounds that Windows may need, so ones best bet for maximum OS support is boot protocols, and device reports similar to commonly used joysticks and other devices.


I'm personally most familiar with the Linux Input subsystem Event interface, i.e. /dev/input/event* device interface. Essentially, you just open the device for reading, and read (complete) structures having format
Code:
struct input_event {
    struct timeval  time;
    unsigned short  type;
    unsigned short  code;
    unsigned int   value;
};
where time is the timestamp; type is one of the EV_type constants defined in <linux/input-event-codes.h>, for example EV_KEY for a keyboard event, or EV_SYN for report separator (which always follows a related set of events, so that multiple events like different axes on a joystick are treated as a single action instead of separate actions); code identifies the key, button, axis, etc., for example KEY_ENTER or KEY_A (defined in the same file; for type EV_FOO, look for constants FOO_name); and value specifies the action on that code, for example 0 for key release, 1 for key press, and 2 for autorepeat.

Autorepeat is indeed handled by the Linux kernel for keyboard devices. It internally detects how long a key has been pressed, and issues the above autorepeat events if needed.
The autorepeat timing is controlled by using an ioctl() on the device; specifically, EVIOCGREP (get) or EVIOCSREP (set) ioctls that take an array of REP_CNT (2) ints, with the value at index REP_DELAY (0) specifying the initial delay in milliseconds, and REP_PERIOD (1) the repeat interval in milliseconds. The ioctl names are defined in <linux/input.h>, and the values in <linux/input-event-codes.h>.

In general, when more than one process has the same event device open, each of them receives a copy of the events. One can use the EVIOCGRAB ioctl() to "grab" (and EVIOCREVOKE to revoke) access to the events, so that only this (file description) receives the events.

The way Arduino et cetera without native USB generate these events, is that they have a simple daemon in Linux userspace, that opens and reads from a serial port, and then uses the uinput (/dev/uinput) device interface (link contains example code at Linux kernel device interface to emit events using the same interface as described above. Additional ioctls defined in <linux/uinput.h> are used to set up the new virtual device (which will appear exactly as a physical input event device would!); for example, the UI_DEV_SETUP ioctl is used to specify the USB Vendor and Product numbers and device name via the struct uinput_setup structure. The UI_DEV_CREATE ioctl is finally used to actually create the device, but note that although the kernel creates the device instantly, it takes a little while for the udev daemon to create the character device nodes with configured owner, group, access mode, and symbolic links to. Whenever the uinput device file descriptor is closed, the virtual device is automatically removed by the kernel (again, it can take a fraction of a second for the udev daemon to delete the device node and symlinks).

In normal use, the udev delay does not matter, since either applications use a symlink (to identify a specific device, defined using SYMLINK+="symlink-name"[/URL] in a suitable udev rule), they detect the new device node appearing (which means udev's work is complete), or they use libudev to receive events informing them of a new device appearing or being removed. It is really only when a side channel (like a pipe or socket between applications) is used to directly notify another application that the device is now available, when the udev delay actually matters.

(Also, if you happen to stumble on my comment to a 2016 StackOverflow question, ioctl(fd, EVIOCGRAB, 1) is invalid for /dev/uinput, because it is essentially "grabbed" automatically: each open description (and thus file descriptor unless you duplicate the descriptor instead of reopening the device) refers to an independent interface to the input subsystem.)
 
Back
Top