cdown / dwm

Dynamic window manager for X (with extensive customisation)
MIT License
60 stars 11 forks source link

XF86XK_AudioPlay doesn't trigger event #11

Closed cdown closed 1 year ago

cdown commented 4 years ago

Weirdly, while XF86XK_AudioPause works, XF86XK_AudioPlay doesn't. xev sees it though:

KeyPress event, serial 28, synthetic NO, window 0x1800001,
    root 0x198, subw 0x0, time 532681, (479,269), root:(1440,270),
    state 0x0, keycode 208 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XKeysymToKeycode returns keycode: 172
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 29, synthetic NO, window 0x1800001,
    root 0x198, subw 0x0, time 532690, (479,269), root:(1440,270),
    state 0x0, keycode 208 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XKeysymToKeycode returns keycode: 172
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

dwm however only sees XF86XK_AudioPause events:

got keycode XF86AudioPause (269025073)
got keycode XF86AudioPause (269025073)
arg->v: /bin/sh
cdown commented 4 years ago

Before (using sxhkd to illustrate prebinding):

% cat .config/sxhkd/sxhkdrc
XF86AudioPlay
    audiotoggle             
% sxhkd
Could not grab key 172 with modfield 0: the combination is already grabbed.
Could not grab key 172 with modfield 16: the combination is already grabbed.
Could not grab key 172 with modfield 2: the combination is already grabbed.
Could not grab key 172 with modfield 18: the combination is already grabbed.

After removing AudioPause line:

% sxhkd
^C%     

So nobody else is binding it...

cdown commented 4 years ago

Interestingly, it seems to pass through sxhkd as well.

cdown commented 4 years ago

Maybe related: https://www.reddit.com/r/i3wm/comments/abbqy8/mpc_not_working_when_called_through_i3/

cdown commented 4 years ago

Something interesting: with openbox and no bindings, no matter what I do, xev always just sees XF86AudioPlay, never XF86AudioPause. I really wonder if something is messing around with the bindings, despite what XF86LogGrabInfo says...

cdown commented 4 years ago
MPD % gg -i XF86
MPD % 
f-galland commented 2 years ago

I'm facing the same problem. Did you find any solutions or workarounds?

Thanks!

cdown commented 2 years ago

Oh, so I'm not insane then :-)

Nope, still no idea what's going on here, but I didn't try to debug it again recently. If you find out please let me know!

mensogulo commented 1 year ago

An update from my side. XF86AudioPlay seems to be generated differently according to which device I use for input. From my USB keyboard the event seems to be grabbed by DWM perfectly. The events from my BT headset don't work at all. Please check the xev output for differences. In the OP post, there is also "XKeysymToKeycode" as in my log, which can be a hint for the root of the problem.

KEYBOARD:
KeyPress event, serial 32, synthetic NO, window 0x1600001,
    root 0x7bd, subw 0x0, time 52564801, (479,529), root:(2400,550),
    state 0x0, keycode 172 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False

KeyRelease event, serial 33, synthetic NO, window 0x1600001,
    root 0x7bd, subw 0x0, time 52564983, (479,529), root:(2400,550),
    state 0x0, keycode 172 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XLookupString gives 0 bytes:
    XFilterEvent returns: False

BT HEADSET:
KeyPress event, serial 35, synthetic NO, window 0x1600001,
    root 0x7bd, subw 0x0, time 52647735, (620,274), root:(2541,295),
    state 0x0, keycode 208 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XKeysymToKeycode returns keycode: 172
    XLookupString gives 0 bytes:
    XmbLookupString gives 0 bytes:
    XFilterEvent returns: False

KeyRelease event, serial 35, synthetic NO, window 0x1600001,
    root 0x7bd, subw 0x0, time 52647746, (620,274), root:(2541,295),
    state 0x0, keycode 208 (keysym 0x1008ff14, XF86AudioPlay), same_screen YES,
    XKeysymToKeycode returns keycode: 172
    XLookupString gives 0 bytes:
    XFilterEvent returns: False
mensogulo commented 1 year ago

I think, we got multiple keycodes (208 and 172) for one corresponding keysym (0x1008ff14, XF86AudioPlay) and the problem is in the grabkeys() function, which calls XKeysymToKeycode() and registers the keys using key codes.

void
grabkeys(void)
{
    updatenumlockmask();
    {
        unsigned int i, j;
        unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
        KeyCode code;

        XUngrabKey(dpy, AnyKey, AnyModifier, root);
        for (i = 0; i < LENGTH(keys); i++)
            if ((code = XKeysymToKeycode(dpy, keys[i].keysym)))
                for (j = 0; j < LENGTH(modifiers); j++)
                    XGrabKey(dpy, code, keys[i].mod | modifiers[j], root,
                        True, GrabModeAsync, GrabModeAsync);
    }
}

When I quick-and-dirty hardcode the keycode, my BT headset works!

void
grabkeys(void)
{
        updatenumlockmask();
        {
                unsigned int i, j;
                unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
                KeyCode code;

                XUngrabKey(dpy, AnyKey, AnyModifier, root);
                for (i = 0; i < LENGTH(keys); i++) {
                        if ((code = XKeysymToKeycode(dpy, keys[i].keysym)))
                                for (j = 0; j < LENGTH(modifiers); j++)
                                        XGrabKey(dpy, code, keys[i].mod | modifiers[j], root,
                                                True, GrabModeAsync, GrabModeAsync);
                        if (code == 172) {
                            code = 208;
                            for (j = 0; j < LENGTH(modifiers); j++)
                                XGrabKey(dpy, code, keys[i].mod | modifiers[j], root,
                                        True, GrabModeAsync, GrabModeAsync);
                        }
                }
        }
}

But what would be a nice solution for that?

update:

xmodmap -pke | grep XF86AudioPlay
keycode 172 = XF86AudioPlay XF86AudioPause XF86AudioPlay XF86AudioPause
keycode 208 = XF86AudioPlay NoSymbol XF86AudioPlay
keycode 215 = XF86AudioPlay NoSymbol XF86AudioPlay

xmodmap -pke | grep XF86AudioPause
keycode 172 = XF86AudioPlay XF86AudioPause XF86AudioPlay XF86AudioPause
keycode 209 = XF86AudioPause NoSymbol XF86AudioPause

UPDATE: Seems that the main problem is: there are many keycodes asscociated with the keysym XF86AudioPlay in the first row and the first one (172) is taken. There is only one keycode mapped with XF86AudioPause. That explains the behavior on my side and from OP. I googled and some people propose to unbind one of them using xmodmap which won't work for me because my hardware produces both.

cdown commented 1 year ago

wow! i... actually had no idea that keybinds could work this way in X. thanks, i will look at options

cdown commented 1 year ago

Thanks again for your help! I had no idea that it's possible to end up with multiple keycodes per keysym, that was the hint I needed to solve this. Can you try this patch? It seems to work for me -- if it works for you I'll send it upstream. :-) Thanks!

commit ac31142d24599ef15717dd78161e99838d2a0787
Author: Chris Down <chris@chrisdown.name>
Date:   Mon Dec 5 15:17:30 2022 +0000

    grabkeys: Grab all available keycodes for a keysym

    It's possible for one keysym to have multiple keycodes. For example, in
    my case I have a keyboard with XF86AudioPlay, but I also have that
    button on my headphones. When XF86AudioPlay is issued from the keyboard
    it has X keycode 172, but when it comes from the headphones it has
    keycode 208.

    You can see the relevant bindings with xmodmap, or similar:

        % xmodmap -pke | grep XF86AudioPlay
        keycode 172 = XF86AudioPlay XF86AudioPause XF86AudioPlay XF86AudioPause
        keycode 208 = XF86AudioPlay NoSymbol XF86AudioPlay
        keycode 215 = XF86AudioPlay NoSymbol XF86AudioPlay

    This is a problem because the current code only binds a single one of
    these keycodes, which means that events from other devices are lost. In
    my case, binding XF86AudioPlay does the right thing and correctly
    handles my keyboard's keys, but does nothing on my headphones.

    In order to fix this, we look at the keyboard mapping using
    XGetKeyboardMapping and pick out all of the matching keycodes rather
    than just the first one. The keypress() side of this just works, because
    the keycode gets converted back to a canonical keysym before any action
    is taken.

diff --git dwm.c dwm.c
index 253aba7..af75787 100644
--- dwm.c
+++ dwm.c
@@ -955,16 +955,24 @@ grabkeys(void)
 {
    updatenumlockmask();
    {
-       unsigned int i, j;
+       unsigned int i, j, k;
        unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
-       KeyCode code;
+       int start, end, skip;
+       KeySym *syms;

        XUngrabKey(dpy, AnyKey, AnyModifier, root);
-       for (i = 0; i < LENGTH(keys); i++)
-           if ((code = XKeysymToKeycode(dpy, keys[i].keysym)))
-               for (j = 0; j < LENGTH(modifiers); j++)
-                   XGrabKey(dpy, code, keys[i].mod | modifiers[j], root,
-                       True, GrabModeAsync, GrabModeAsync);
+       XDisplayKeycodes(dpy, &start, &end);
+       syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
+       for (k = start; k <= end; k++)
+           for (i = 0; i < LENGTH(keys); i++)
+               /* skip modifier codes, we do that ourselves */
+               if (keys[i].keysym == syms[(k - start) * skip])
+                   for (j = 0; j < LENGTH(modifiers); j++)
+                       XGrabKey(dpy, k,
+                            keys[i].mod | modifiers[j],
+                            root, True,
+                            GrabModeAsync, GrabModeAsync);
+       XFree(syms);
    }
 }
mensogulo commented 1 year ago

It works perfectly for me. Thanks a lot. I just discovered playerctl and bound the keys to control my current media (youtube videos, vlc). It is really cool.

cdown commented 1 year ago

That's great! I will send it upstream shortly then. Thanks a lot for your work working out what's going on! :-)

cdown commented 1 year ago

This is now in mainline: https://git.suckless.org/dwm/commit/89f9905714c1c1b2e8b09986dfbeca15b68d8af8.html

Thanks again for your help working this out!