moses-palmer / pynput

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

X: Cannot type unicode characters without corresponding keysym #94

Closed nzahasan closed 4 years ago

nzahasan commented 6 years ago

I was trying to type Bangla unicode character, but it fails with this error

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/pynput/keyboard/_base.py", line 450, in type
    self.release(character)
  File "/usr/local/lib/python3.6/dist-packages/pynput/keyboard/_base.py", line 404, in release
    self._handle(resolved, False)
  File "/usr/local/lib/python3.6/dist-packages/pynput/keyboard/_xorg.py", line 196, in _handle
    raise self.InvalidKeyException(key)
pynput.keyboard._base.InvalidKeyException: 'খ'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "pyinp.py", line 6, in <module>
    virtualKB.type("I am bad খ")
  File "/usr/local/lib/python3.6/dist-packages/pynput/keyboard/_base.py", line 453, in type
    raise self.InvalidCharacterException(i, character)
pynput.keyboard._base.InvalidCharacterException: (9, 'খ')
moses-palmer commented 6 years ago

Thank you for your report.

Could you please include the full string you're trying to type?

Edit: Sorry, found it

moses-palmer commented 6 years ago

It appears that the letter you are trying to type does not have a corresponding X keysymdef as can be seen when searching for it in xorg_keysyms.py.

What output do you get when running xev and pressing the key you use to type BENGALI LETTER KHA?

nzahasan commented 6 years ago

I do not have a BANGLA KHA in my keyboard. I my intention is to press k and type BANGLA KHA .

Evidlo commented 6 years ago

So what's the right solution for typing characters for which there is no keysym? I'm having the same problem with . I know some applications using python-xlib get around this by typing through CLIPBOARD or PRIMARY, but maybe there is a less hacky way?

You could potentially copy xdotool's type command:

https://github.com/jordansissel/xdotool/blob/master/cmd_type.c#L190

Theres also python-libxdo which implements Xdo.enter_text_window, used above.

nzahasan commented 6 years ago

apparently you have to add the character in xorg_keysyms.py .

moses-palmer commented 6 years ago

That is not entirely correct, as that file is generated by parsing the actual Xlib header files. These files, unfortunately, do not contain definitions for RETURN SYMBOL and BENGALI LETTER KHA.

Does anybody know whether xdotool is able to type these characters?

Evidlo commented 6 years ago

Xdotool can type both.

On Jun 7, 2018 8:17 AM, "moses-palmer" notifications@github.com wrote:

That is not entirely correct, as that file is generated by parsing the actual Xlib header files. These files, unfortunately, do not contain definitions for RETURN SYMBOL and BENGALI LETTER KHA.

Does anybody know whether xdotool is able to type these characters?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/moses-palmer/pynput/issues/94#issuecomment-395416563, or mute the thread https://github.com/notifications/unsubscribe-auth/AFM_4aQ8gnVeAU5_T8EcLtMmxY6mXzmqks5t6SfbgaJpZM4UXnif .

moses-palmer commented 6 years ago

@nzahasan @Evidlo If you run xev at the same time as typing these characters with xdotool, what events are displayed in the terminal?

Evidlo commented 6 years ago

xdotool type খ

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 53, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 57, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 248

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 63, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 1

MappingNotify event, serial 72, synthetic NO, window 0x0,
    request MappingKeyboard, first_keycode 8, count 248
gdiShun commented 4 years ago

To resurrect this, I was able to remap Caps Lock to a unicode emoji with xmodmap like this: xmodmap -e "keycode 66 = U0001F61A"

xev output:

KeyPress event, serial 37, synthetic NO, window 0x6800001,
    root 0x6bf, subw 0x0, time 15707692, (11,-24), root:(1842,985),
    state 0x12, keycode 66 (keysym 0x101f61a, U0001F61A), same_screen YES,
    XLookupString gives 4 bytes: (f0 9f 98 9a) "😚"
    XmbLookupString gives 4 bytes: (f0 9f 98 9a) "😚"
    XFilterEvent returns: False

KeyRelease event, serial 37, synthetic NO, window 0x6800001,
    root 0x6bf, subw 0x0, time 15707774, (11,-24), root:(1842,985),
    state 0x12, keycode 66 (keysym 0x101f61a, U0001F61A), same_screen YES,
    XLookupString gives 4 bytes: (f0 9f 98 9a) "😚"
    XFilterEvent returns: False

EDIT: And the OP's original:

KeyPress event, serial 37, synthetic NO, window 0x4000001,
    root 0x6bf, subw 0x0, time 16154712, (938,637), root:(2769,1646),
    state 0x10, keycode 66 (keysym 0x1000996, U0996), same_screen YES,
    XLookupString gives 3 bytes: (e0 a6 96) "খ"
    XmbLookupString gives 3 bytes: (e0 a6 96) "খ"
    XFilterEvent returns: False

KeyRelease event, serial 37, synthetic NO, window 0x4000001,
    root 0x6bf, subw 0x0, time 16154792, (938,637), root:(2769,1646),
    state 0x12, keycode 66 (keysym 0x1000996, U0996), same_screen YES,
    XLookupString gives 3 bytes: (e0 a6 96) "খ"
    XFilterEvent returns: False

EDIT2: Adding it to xorg_keysyms.py like this:

[...]
    'nzahasan': (0x1000996, u'\u0996'),
    'kissy': (0x101f61a, u'\U0001F61A')}

Causes it to no longer complain, but still does not send the keys.

EDIT3: Name appears to be important. 'kissy': (0x100002c, u'\u002c') did the same until I changed the name to 'comma'. Unsure how to find or create names for these characters.

EDIT4: So I mapped them to Caps Lock again and tried having pynput tap Caps Lock, and this was the resulting error. The 😚 at the end is me pressing the physical Caps Lock key immediately after.

Traceback (most recent call last):
  File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/local/bin/StreamDeck/my/StreamDeck/Devices/StreamDeck.py", line 116, in _read
    self.key_callback(self, k, new)
  File "/usr/local/bin/StreamDeck/my/StreamDeck.py", line 38, in key_change_callback
    key.callback(deck, state)
  File "/usr/local/bin/StreamDeck/my/Keys/test.py", line 11, in callback
    Controller().tap(PyKey.caps_lock)
  File "/usr/lib/python3.8/site-packages/pynput/keyboard/_base.py", line 443, in tap
    self.press(key)
  File "/usr/lib/python3.8/site-packages/pynput/keyboard/_base.py", line 392, in press
    self._handle(resolved, True)
  File "/usr/lib/python3.8/site-packages/pynput/keyboard/_xorg.py", line 242, in _handle
    Xlib.ext.xtest.fake_input(
  File "/usr/lib/python3.8/contextlib.py", line 120, in __exit__
    next(self.gen)
  File "/usr/lib/python3.8/site-packages/pynput/_util/xorg.py", line 78, in display_manager
    raise X11Error(errors)
pynput._util.xorg.X11Error: [(BadValue(<Xlib.display._BaseDisplay object at 0x7fad57f59fd0>, b'\x00\x02\x0b\x00\x00\x00\x00\x00\x02\x00\x84\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'), None)]
😚
moses-palmer commented 4 years ago

@gdiShun, thank you for your thorough analysis!

I downloaded the source for xmodmap to check what it does. keycode kc = ks will eventually cause to to pass ks to XStringToKeysym. This is a utility function defined in C and is not available to the Python library Xlib, but for strings starting with 'U' it will perform the following:

    if (*s == 'U') {
        val = 0;
        for (p = &s[1]; *p; p++) {
            c = *p;
            if ('0' <= c && c <= '9') val = (val<<4)+c-'0';
            else if ('a' <= c && c <= 'f') val = (val<<4)+c-'a'+10;
            else if ('A' <= c && c <= 'F') val = (val<<4)+c-'A'+10;
            else return NoSymbol;
            if (val > 0x10ffff)
                return NoSymbol;
        }
        if (val < 0x20 || (val > 0x7e && val < 0xa0))
            return NoSymbol;
        if (val < 0x100)
            return val;
        return val | 0x01000000;
    }

This is where 0x100002c comes from. As you note, simply passing this as a keysym does not help. It will then call XChangeKeyboardMapping to remap the keyboard. Perhaps this is what makes it work? In your experiments above, did you attempt to have pynput type '😚' after having remapped it? Did you try with the following patch applied?

diff --git a/lib/pynput/_util/xorg_keysyms.py b/lib/pynput/_util/xorg_keysyms.py
index db501ff..ee61ead 100644
--- a/lib/pynput/_util/xorg_keysyms.py
+++ b/lib/pynput/_util/xorg_keysyms.py
@@ -18,6 +18,7 @@
 # pylint: disable=C0111,C0302

 SYMBOLS = {
+    'Kissing_Face_with_Closed_Eye': (0x0101F61A, u'\u1F61A'),
     '0': (0x0030, u'\u0030'),
     '1': (0x0031, u'\u0031'),
     '2': (0x0032, u'\u0032'),
gdiShun commented 4 years ago

In your experiments above, did you attempt to have pynput type '😚' after having remapped it?

I tried a lot of things, so I can't say with 100% certainty that I did, but I'm pretty sure I did. The result was likely just no output. What I do clearly remember doing is using from_vk(0x0001f61a). It would return the proper character ('😚') when print()'d. pynput's type would only return ''. And pynput's tap would give a similar error as the last one in my previous post, until I added it to Xlib's keysymdef. Then it would once again do nothing.

Did you try with the following patch applied?

The patch as-is causes the same pynput.keyboard._base.InvalidCharacterException error. The u'\u1F61A' needs to be replaced with u'\U0001F61A' for no errors. But still no output. Also tried adding XK_Kissing_Face_with_Closed_Eye = 0x1F61A(and other variations) to the Xlib keysymdef. And again, no errors, but no output either. Interestingly, even with all these keysymdef changes, using tap(Key.caps_lock) after mapping it will still cause that same BadValue error.

I'm thinking this might be more font-, or software-based than input-, or hardware-based. More of a hunch than anything. Really don't know what I'm talking about here. :p But I've been scouring for a library to do this, and root-access or not, I can't find any that will actually output emojis or other non-standard unicode characters.

As a bit of a side-note, that may be helpful. I found a solution to inputting these characters, not with pynput, but evdev. A hacky solution using the CTRL+SHIFT+U shortcut. I posted it here.What's interesting to me, is that this works great with evdev, but inputting the same CTRL+SHIFT+U shortcut on pynput does not work either. I'm guessing it has to do with how evdev injects inputs directly?

moses-palmer commented 4 years ago

I have pushed a proposed solution to fixup-xorg-chars-without-keysyms. It actually appears to work on my system, but I would like confirmation from any of @Evidlo, @gdiShun or @nzahasan before merging into master.

gdiShun commented 4 years ago
Controller().type("'😚'\N{kissing face with closed eyes}\U0001F61A")
Controller().tap('😚')

'😚'😚😚😚'😚'😚😚😚'😚'😚😚😚'😚'😚😚😚

Working! Awesome stuff. Great job and thank you!

moses-palmer commented 4 years ago

This has been released as pynput 1.7.