[RFC] Listening to keyboard in background, RawInput and DInput

Rémi Bernon rbernon at codeweavers.com
Fri Aug 23 04:16:55 CDT 2019


Hi everyone,

I'm trying to solve a problem that looked simple at first but ended up 
being much more complicated than I expected. I would like to expose the 
issue and the solutions I'm thinking about here so that you can give me 
your opinion on it.


The issue:

On Linux/X11, when an application that uses DInput for keyboard input 
loses focus - with Alt-Tab - then gets focus back, it sees the Alt key 
as still being pressed until you press and release it again.

The reason is that X11 sends a KeyPress event to the application for the 
Alt while it still has foreground, but as soon as you press Tab the 
window manager takes a keyboard grab and intercepts all other key events 
until you release the Alt key. And the release event is not received 
either - so even if you don't actually change to another window, this 
event will not be seen.

There are two ways that I saw to workaround this missing release event. 
The first one is a KeymapNotify event that is received upon gaining 
focus, which could be used to send missing input. And the other is 
XInput2 RawKeyPress/RawKeyRelease events that would allow to listen to 
keyboard events while in background.


Regarding the input stack:

On the Windows side, AFAIU there're two different ways to get keyboard 
(or mouse) input. One is the WM_KEYPRESS/WM_KEYRELEASE messages, and 
their corresponding low-level hooks. And there's the WM_INPUT messages 
for raw input. I could see that both the low-level hooks and the raw 
input allow an application to get keyboard input while in background.

(I understand that internally the WM_KEYPRESS/WM_KEYRELEASE messages are 
probably implemented on top of the raw input, then provided to the 
low-level hooks and then discarded if the application does not have 
foreground, but in Wine it's all on the same level.)

Then there's DInput. In Wine it is implemented using low-level hooks, 
but I believe that it is using raw input on Windows - it is mutually 
exclusive with WM_INPUT events, and you can even see the raw input 
devices that DInput registered by calling GetRegisteredRawInputDevices 
after activating the DInput keyboard / mouse devices.


The problem:

When trying either of the two ways to get the missing release events, 
while keeping the current input stack unmodified, I faced some 
difficulties. The main reason AFAIU is because of duplicate key input 
being sometimes sent to wineserver, which can make the keystate 
inconsistent.

As each window sends input event separately, we rely on the host to only 
send unique KeyPress/KeyRelease events. This is correct in general as 
only the window with the input focus receives such events, but as soon 
as we add the KeymapNotify or the raw input events, it is not anymore.

It could be possible to add some de-duplication of the keyboard events 
in wineserver as it is done for some mouse motion, but that doesn't seem 
quite right.


The proposal:

I believe the right way to do would be to change DInput to be 
implemented on top of raw input, and listen to the host raw input events 
from the desktop window thread to receive them in background if possible.

It would make the desktop window the unique source of raw input 
messages, which would ensure their uniqueness, and they would be 
received whether wine windows are in foreground or not. In this case, 
normal input events would only translate to WM_KEY* messages and 
low-level hooks.

If not possible, then we keep the current way where normal input events 
generate both raw input messages and the normal messages and hooks.

This would also fix the values reported by DInput, that are currently 
modified by cursor speed and acceleration although they aren't on Windows.

This would also make wine windows capable of listening to keyboard (and 
mouse) input while in background, and make windows keylogger software 
work better. Not sure if this is an improvement or not.

However it doesn't change the fact that the low-level hooks aren't 
called when wine is in background. If we want that they it would require 
to translate the raw input to normal input ourselves instead of relying 
on the host events, but it would also be possible.


tl;dr: I would like to change DInput keyboard/mouse to be implemented on 
top of raw input and listen to raw input while wine is in background. A 
lot of work for a small issue, what do you think?
-- 
Rémi Bernon <rbernon at codeweavers.com>



More information about the wine-devel mailing list