moses-palmer / pynput

Sends virtual input commands
GNU Lesser General Public License v3.0
1.81k stars 251 forks source link

xorg: Cannot type combination Super + letter #59

Open ddast opened 6 years ago

ddast commented 6 years ago

Setup: Arch Linux xorg-server 1.19.5 Window Manager: i3-wm

I recently started using pynput and I'm trying to type the combination Super+k with the following code

import pynput
keyboard = pynput.keyboard.Controller()
with keyboard.pressed(pynput.keyboard.Key.cmd):
    keyboard.press('k')
    keyboard.release('k')

but it does not show the same behaviour as typing it.

To see the difference compared with typing Super+k on the keyboard I used xev:

#XEV OUTPUT WHEN TYPING ON KEYBOARD
KeyPress event, serial 33, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33497941, (514,-283), root:(518,275),
    state 0x10, keycode 133 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 33, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33498629, (514,-283), root:(518,275),
    state 0x50, keycode 45 (keysym 0x6b, k), same_screen YES,
    XLookupString gives 1 bytes: (6b) "k"
    XmbLookupString gives 1 bytes: (6b) "k"
    XFilterEvent returns: False

KeyRelease event, serial 33, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33498717, (514,-283), root:(518,275),
    state 0x50, keycode 45 (keysym 0x6b, k), same_screen YES,
    XLookupString gives 1 bytes: (6b) "k"
    XFilterEvent returns: False

KeyRelease event, serial 33, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33499237, (514,-283), root:(518,275),
    state 0x50, keycode 133 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False
#XEV OUTPUT WHEN USING PYNPUT
MappingNotify event, serial 33, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 248

KeyPress event, serial 33, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33793611, (813,165), root:(817,723),
    state 0x10, keycode 133 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 34, synthetic YES, window 0x3a00001,
    root 0x4a3, subw 0x0, time 0, (0,0), root:(0,0),
    state 0x0, keycode 45 (keysym 0x6b, k), same_screen NO,
    XLookupString gives 1 bytes: (6b) "k"
    XmbLookupString gives 1 bytes: (6b) "k"
    XFilterEvent returns: False

KeyRelease event, serial 34, synthetic YES, window 0x3a00001,
    root 0x4a3, subw 0x0, time 0, (0,0), root:(0,0),
    state 0x0, keycode 45 (keysym 0x6b, k), same_screen NO,
    XLookupString gives 1 bytes: (6b) "k"
    XFilterEvent returns: False

KeyRelease event, serial 34, synthetic NO, window 0x3a00001,
    root 0x4a3, subw 0x0, time 33793614, (813,165), root:(817,723),
    state 0x50, keycode 133 (keysym 0xffeb, Super_L), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

Furthermore, I noticed that, e.g., using pynput.keyboard.Key.up instead of 'k' works as expected and other modifiers like pynput.keyboard.Key.ctrl + 'a' do also work.

If you need more information please let me know.

moses-palmer commented 6 years ago

Thank you for your report.

When diffing the xev output, I notice that the main difference is that the K sent by pynput is SYNTHETIC, does not have a TIME, is not on the same screen as the ROOT window, and has an unset TIME.

pynput is able to pass all keys defined in Keys directly to XTest using fake_input. For all other keys, this unfortunately does not work when supporting arbitrary keys.

Keyboard events are passed to XLib here. The event parameter is one of the event classes listed here.

Perhaps you could try modifying the arguments? same_screen sounds like it possibly should be set to a non-zero value?

ddast commented 6 years ago

Thank you for the pointers!

I played around a little bit with the arguments but no luck so far. same_screen=1 does change the property reported by xev but does not change the behaviour. Also I tried setting time=Xlib.X.CurrentTime but this value is just zero, so this does not change anything.

I will probably have no time the next days but I will look into this at the weekend.

Note: The C library libxdo has the desired behaviour, so I will try to compare what is done there. Code for libxdo for reference:

#include <xdo.h>
int main()
{
  xdo_t* xdo = xdo_new(NULL);
  xdo_send_keysequence_window(xdo, CURRENTWINDOW, "Super+k", 12000);
  xdo_free(xdo);
}
ddast commented 6 years ago

I did not succeed to fix this by changing the properties of the event.

I think the property SYNTHETIC is connected to using send_event since this is mentioned in the documentation of this function.

Setting TIME and the coordinates is possible but how to get the desired values I don't know. Nevertheless if the problem is due to one of these values it should be fixable in principle.

I mentioned libxdo in my last post since at first glance it looked like it also uses XSendEvent. However, it is only used when the key is sent to a different window. In the other case it uses fake_input.

You wrote that fake_input does not work for arbitrary keys. I don't understand much of how keys are handled in X11, so the following might be nonsense. Is it correct that fake_input only works for keys that have a keycode mapped to their keysym? This probably means that it can only be used for the keys that can actually be typed with the keyboard. Reading this function of libxdo then could explain why it still works for (I think) arbitrary keys. It seems to map an unmapped keysym to an unused keycode.

moses-palmer commented 6 years ago

This seems related to issue #4](https://github.com/moses-palmer/pynput/issues/4).

The problem is that the two different methods maintain different sets of active modifiers.

ddast commented 6 years ago

I noticed an additional problem with synthetic keys when using Gnome3. I am not able to type into the search window that opens with the super key since pynput will continue to write into the last focused window (even clicking into the search field does not change that). I think the problem is that get_input_focus() is still reporting this last focused window.

I'm currently trying to use fake_input to avoid synthetic keys at all (see this commit). It is not finished, since in case of an KeyError it still uses synthetc keys but in the other cases it works at least on my (german) keyboard layout. However, my understanding is that keyboard layouts are the tricky part so most likely this does not work in general.

moses-palmer commented 6 years ago

Great to see work on this code!

Do you anticipate problems with your approach? By that I mean the extra sending of keyboard events, altering the global keyboard state and possibly causing applications listening to single modifier key presses to act.

ddast commented 6 years ago

I'm sorry for the late reply.

I don't think this approach sends more keyboard events than necessary but, to be honest, documentation is scarce and I'm far from sure whether this is the right way to do it.

My understanding is that this approach is pretty much the same as using the actual keyboard. This includes sending only a key press. As a result this key is continously typed as it would happen with the actual keyboard. If the pressed key uses a modifier this modifier also stays pressed. A pressed normal key is not much of a problem since typing another key on the actual keyboard seems to interreput this, but a pressed modifier key is not that easy to get rid of (withouth using pynput that is). I'm emphasizing this since the current implementation does not have this behaviour.
Then again what is the intended and expected behaviour when only a keypress is sent?

More problematic is the case of an unexpected termination of the program, since in this case the keyboard should not be left in a strange state, i.e., with some keys pressed.

Furthermore, I encountered a problem with dead keys. I don't understand why the joined key is only used for the keypress but not for the keyrelease. This leads to the keypress problem described above since a fitting keyrelease is missing. Hoewver, the problem is that this logic is not xorg specific but in the platform-independent part. I changed it naively in this commit and now dead keys seem to work too but I would like to hear what you think about this.

aaronfc commented 4 years ago

Hi, I am also having problems when trying to simulate a shortcut: CTRL + ALT + 0 I checked xev output for real scenario and the one simulated by pynput and I found that the 0 key was also sent as synthetic YES when simulating that. I want to generate a shortcut system-wide, and currently it is only working if I focus the program I want to react to that shortcut. Do you think it might be related?

Edit: Curious, I am also using i3wm