Closed cdown closed 1 year 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...
Interestingly, it seems to pass through sxhkd as well.
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...
MPD % gg -i XF86
MPD %
I'm facing the same problem. Did you find any solutions or workarounds?
Thanks!
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!
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
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.
wow! i... actually had no idea that keybinds could work this way in X. thanks, i will look at options
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);
}
}
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.
That's great! I will send it upstream shortly then. Thanks a lot for your work working out what's going on! :-)
This is now in mainline: https://git.suckless.org/dwm/commit/89f9905714c1c1b2e8b09986dfbeca15b68d8af8.html
Thanks again for your help working this out!
Weirdly, while XF86XK_AudioPause works, XF86XK_AudioPlay doesn't.
xev
sees it though:dwm however only sees XF86XK_AudioPause events: