Repository: qtile/qtile

Issue:
Mapping XF86CD disables all input to windows
#  Issue description

I have connected a Bluetooth-device to my machine and it comes with a play/pause-button I wanted to make use of.

The following is in my config.py right now:

```python
Key([], 'XF86AudioPlay', lazy.spawn('playerctl play-pause'), desc='Play/pause playback.'),
# Key([], 'XF86CD', lazy.spawn('playerctl play-pause'), desc='Play/pause playback.'),
```
The first of those lines refers to a button on my keyboard, which does work perfectly fine. As soon as I remove the comment from the second line (and restart), however, any input done by my keyboard is not passed to the applications I am running anymore (if there is a cursor visible, it might blink when I hit a key, though). The only thing that the keyboard can still be used for is to enter my Qtile-shortcuts.
For this problem to occur I don't even have to connect the Bluetooth-device or press the button, there just appears to be something wrong with binding that particular key.

# Qtile version

0.15.1

# Stacktrace

A bit of the log (looks like something hangs in an infinite loop since the output adds up very quickly):

```log
2020-08-16 12:43:00,902 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65515, 0)
2020-08-16 12:43:00,961 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65513, 64)
2020-08-16 12:43:08,271 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65293, 0)
2020-08-16 12:43:11,535 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65507, 0)
2020-08-16 12:43:11,556 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65513, 4)
2020-08-16 12:43:43,920 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65515, 0)
2020-08-16 12:43:43,921 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65507, 64)
```

# Configuration

```python
Key([], 'XF86AudioPlay', lazy.spawn('playerctl play-pause'), desc='Play/pause playback.'),
# Key([], 'XF86CD', lazy.spawn('playerctl play-pause'), desc='Play/pause playback.'),
```



Discussion:
This also happens with the most recent version (current commit).
I don't think it's an infinite loop, but it does seem like it probably happens on every keypress that qtile sees. Can you print out keys_map? Seems like it might never get populated, and there's some error in the code that populates it that we swallow somehow (or maybe it's at the beginning of your log?)
I printed out `keys_map` from `process_key_event` (with a rather small test-config), which is triggered after every key press:

```log
{(107, 8): <Key (['mod1'], k)>, (106, 8): <Key (['mod1'], j)>, (107, 12): <Key (['mod1', 'control'], k)>, (106, 12): <Key (['mod1', 'control'], j)>, (32, 8): <Key (['mod1'], space)>, (32, 9): <Key (['mod1', 'shift'], space)>, (65293, 9): <Key (['mod1', 'shift'], Return)>, (65293, 8): <Key (['mod1'], Return)>, (65289, 8): <Key (['mod1'], Tab)>, (120, 8): <Key (['mod1'], x)>, (113, 12): <Key (['mod1', 'control'], q)>, (114, 8): <Key (['mod1'], r)>, (269025107, 0): <Key ([], XF86CD)>, (49, 8): <Key (['mod1'], 1)>, (49, 9): <Key (['mod1', 'shift'], 1)>}
```

Without the `XF86CD`-key the map looks almost the same (minus the XF86CD-key), but `process_key_event` is seemingly only hit while qtile starts up (instead of with every key press).

It seems that the log-messges shown above are either gone with the latest version or they end up in a different place (this output is from my development environment).
Yeah, I fixed a bug where we would stack trace instead of printing a reasonable log messsage on a bad key. You should see *something* about ignoring the keysym, though.
This is the beginning of the log I got from 0.15.1; the current commit only gives me two warnings when I start Qtile, and those are independent of the existence of `XF86CD`:

```log
2020-08-16 12:42:40,519 WARNING libqtile manager.py:loop():L245 Restarting Qtile with os.execv(...)
2020-08-16 12:42:40,757 WARNING libqtile xcursors.py:_setup_xcursor_binding():L112 xcb-cursor not found, fallback to font pointer
2020-08-16 12:42:40,962 WARNING libqtile utils.py:safe_import():L191 Unmet dependencies for optional Widget: '.widget.launchbar.LaunchBar', No module named 'xdg'
2020-08-16 12:42:40,963 WARNING libqtile utils.py:safe_import():L191 Unmet dependencies for optional Widget: '.widget.mpd2widget.Mpd2', No module named 'mpd'
2020-08-16 12:42:40,966 WARNING libqtile utils.py:safe_import():L191 Unmet dependencies for optional Widget: '.widget.wlan.Wlan', No module named 'iwlib'
2020-08-16 12:42:40,973 WARNING libqtile utils.py:safe_import():L191 Unmet dependencies for optional Widget: '.widget.imapwidget.ImapWidget', No module named 'keyring'
2020-08-16 12:42:43,425 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65515, 0)
2020-08-16 12:42:47,073 ERROR libqtile xcore.py:_xpoll():L277 Got an exception in poll loop
Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 246, in _xpoll
    ret = target(event)
  File "/usr/lib/python3.8/site-packages/libqtile/backend/x11/xcore.py", line 494, in handle_KeyPress
    self.qtile.process_key_event(keysym, event.state & self._valid_mask)
  File "/usr/lib/python3.8/site-packages/libqtile/core/manager.py", line 329, in process_key_event
    key = self.keys_map[(keysym, mask)]
KeyError: (65513, 0)
```
I only have 0.15.1 on my main machine, everything else I tried inside Xephyr inside a VM. If there is anything else I could gather, please let me know.
I would look at what's going on with process_key_event(), seems like something there isn't quite as it should be.
That's what I did initially, but the function isn't even called under normal circumstances. Only once I try to bind `XF86CD` does every keypress end up in that method. Something must be going wrong during initialization.
I have put a line of code into `process_key_event` to print out the keysyms and masks coming in. Then I launched Qtile, opened a terminal, pressed `a`, closed the terminal and quit Qtile again. Here are the logs (first with `XF86CD`, then without):

```log
### XF86CD added
Connected to pydev debugger (build 201.7846.77)
2020-08-16 18:37:04,699 libqtile xcursors.py:_setup_xcursor_binding():L112  xcb-cursor not found, fallback to font pointer
2020-08-16 18:37:04,795 libqtile manager.py:setup_eventloop():L251  importing dbus/gobject failed, dbus will not work.
Got keysym: 65513, mask: 0             Alt_L as keysym
Got keysym: 65293, mask: 8   Alt+Enter - open window
Got keysym: 97, mask: 0      'a' - should have gone to the window
Got keysym: 65513, mask: 0             Alt_L as keysym
Got keysym: 120, mask: 8     Alt+x
Got keysym: 65507, mask: 0             Ctrl+L without modifier
Got keysym: 65513, mask: 4             Ctrl+Alt_L (the latter is the keysym)
Got keysym: 113, mask: 12    Ctrl+Alt+q

Process finished with exit code 0


### No XF86CD
Connected to pydev debugger (build 201.7846.77)
2020-08-16 18:38:55,500 libqtile xcursors.py:_setup_xcursor_binding():L112  xcb-cursor not found, fallback to font pointer
2020-08-16 18:38:55,588 libqtile manager.py:setup_eventloop():L251  importing dbus/gobject failed, dbus will not work.
Got keysym: 65293, mask: 8   Alt+Enter - open window
Got keysym: 120, mask: 8     Alt+x
Got keysym: 113, mask: 12    Ctrl+Alt+q

Process finished with exit code 0
```

It seems that all the modifier-keys also trigger the function as keysyms when the `XF86CD`-key is added. That's weird. Do you have any hints where to look next?
On Sun, Aug 16, 2020 at 10:08:45AM -0700, tcld wrote:
> I have put a line of code into `process_key_event` to print out the keysyms and masks coming in. Then I launched Qtile, opened a terminal, pressed `a`, closed the terminal and quit Qtile again. Here are the logs (first with `XF86CD`, then without):
> 
> ```log
> ### XF86CD added
> Connected to pydev debugger (build 201.7846.77)
> 2020-08-16 18:37:04,699 libqtile xcursors.py:_setup_xcursor_binding():L112  xcb-cursor not found, fallback to font pointer
> 2020-08-16 18:37:04,795 libqtile manager.py:setup_eventloop():L251  importing dbus/gobject failed, dbus will not work.
> Got keysym: 65513, mask: 0             Alt_L as keysym
> Got keysym: 65293, mask: 8   Alt+Enter - open window
> Got keysym: 97, mask: 0      'a' - should have gone to the window
> Got keysym: 65513, mask: 0             Alt_L as keysym
> Got keysym: 120, mask: 8     Alt+x
> Got keysym: 65507, mask: 0             Ctrl+L without modifier
> Got keysym: 65513, mask: 4             Ctrl+Alt_L (the latter is the keysym)
> Got keysym: 113, mask: 12    Ctrl+Alt+q
> 
> Process finished with exit code 0
> 
> 
> ### No XF86CD
> Connected to pydev debugger (build 201.7846.77)
> 2020-08-16 18:38:55,500 libqtile xcursors.py:_setup_xcursor_binding():L112  xcb-cursor not found, fallback to font pointer
> 2020-08-16 18:38:55,588 libqtile manager.py:setup_eventloop():L251  importing dbus/gobject failed, dbus will not work.
> Got keysym: 65293, mask: 8   Alt+Enter - open window
> Got keysym: 120, mask: 8     Alt+x
> Got keysym: 113, mask: 12    Ctrl+Alt+q
> 
> Process finished with exit code 0
> ```
> 
> It seems that all the modifier-keys also trigger the function as keysyms when the `XF86CD`-key is added. That's weird. Do you have any hints where to look next?

The code that does the key setup is
libqtile/backend/x11/xcore.py:grab_key(), although I don't see
anything particularly suspicious there. It's also weird that other
XF86* keys work. I wonder if this is because your keyboard definition
doesn't have the key somehow? What happens when you don't bind it and
press it in an xev window?

`xev` actually tells me that the key is `XF86AudioPlay` (although with a different keycode than the actual `XF86AudioPlay` on my keyboard), while libinput suggests `XF86CD`. When binding the former, the button doesn't do anything - when binding the latter, the button works.

I could try binding one of the other more exotic keys that don't natively exist on my keyboard. If that did cause the same problems, however, I would still have no idea where in qtile the problem manifests.
You might be right, binding `XF86ToDoList` or `XF86Excel` doesn't work either.
It seems that the keycode for the keysym cannot be found here: https://github.com/qtile/qtile/blob/master/libqtile/backend/x11/xcbq.py#L865

Maybe returning `0` in such cases causes the other issues.

Maybe the keycodes are read and translated from the actual hardware?https://github.com/qtile/qtile/blob/master/libqtile/backend/x11/xcbq.py#L830
That would explain everything. I'll have another look at this in the next few days. It would be a shame if I couldn't make use of the buttons on my Bluetooth-device.

Resolving patch:
diff --git a/libqtile/backend/x11/xcore.py b/libqtile/backend/x11/xcore.py
--- a/libqtile/backend/x11/xcore.py
+++ b/libqtile/backend/x11/xcore.py
@@ -397,14 +397,21 @@ def grab_key(self, key: config.Key) -> Tuple[int, int]:
         keysym, modmask = self.lookup_key(key)
         code = self.conn.keysym_to_keycode(keysym)
 
-        for amask in self._auto_modmasks():
-            self.conn.conn.core.GrabKey(
-                True,
-                self._root.wid,
-                modmask | amask,
-                code,
-                xcffib.xproto.GrabMode.Async,
-                xcffib.xproto.GrabMode.Async,
+        if code != 0:
+            for amask in self._auto_modmasks():
+                self.conn.conn.core.GrabKey(
+                    True,
+                    self._root.wid,
+                    modmask | amask,
+                    code,
+                    xcffib.xproto.GrabMode.Async,
+                    xcffib.xproto.GrabMode.Async,
+                )
+        else:
+            logger.warning(
+                "Keysym could not be mapped: {keysym}, mask: {modmask}".format(
+                    keysym=hex(keysym), modmask=modmask
+                )
             )
 
         return keysym, modmask & self._valid_mask


Test patch: