Skip to content

Instantly share code, notes, and snippets.

@revov
Last active April 2, 2023 09:13
Show Gist options
  • Save revov/1366755f335c82cbea04b1d9ed9bbf48 to your computer and use it in GitHub Desktop.
Save revov/1366755f335c82cbea04b1d9ed9bbf48 to your computer and use it in GitHub Desktop.
Remapping keys on Linux for a specific device (Wireless Laser Presenter)

Recently I bought a Chinese Wireless Laser Presenter. I planned on using it for RevealJS presentations and it worked perfectly (as PageUP/PageDown traverse well in the 2D slides). However, in order to have more control I wanted to map the other two buttons (FullScreen, BlankScreen) to Up and Down arrows, as well as remapping the PgUp/PgDn buttons to Left and Right. Since I will most probably be using Linux when giving presentations I though it would be easy to remap them. I quickly realised how wrong I was.

The presenter worked on Wayland but remapping the keys would be mission impossible. xinput --list didn't detect the presenter (the only way to get its EventID was to use the lower level sudo libinput-list-devices). The next steps that I am going to present also caused errors, so Wayland is out of the question as I didn't have patience to dig through it.

As I was skimming through various StackOverflow threads and blog posts I gained a bit of knowledge on how the Linux systems work with regards to input devices (keyboards). The input drivers will detect the KeyCode and emit an event. A subsystem, named XKeyboardExtensions (XKB) would then apply rules/layout settings/mappings over the KeyCode and transform it into KeySymbol (KeySym). The KeyCode could be <AC06> (6th button ont 3rd row of the keyboard) which in an US layout would translate to the symbol h. Using XKB we could modify this mapping, however we have no control over the KeyCode. It depends on the application whether it uses the KeyCode or the KeySym of the event (for example for a word processor it would make sense to listen for Ctrl+S based on the raw KeyCode since the symbol "S" on a Bulgarian keyboard may be the symbol "Я" when switched to BG layout.

First I used the xev tool to capture the keycodes that the presenter originally emitted. The Next, Previous and BlankScreen buttons emitted respectively <PGDN>, <PGUP> and <AB05>(letter "b") keycodes. However, the fullscreen button emitted either <ESC> either a bunch of key combinations (to work for different kinds of presentation applications) - "F5", "s", "p", "h" + all modifiers. This was a bit of a problem since "F5" for example would refresh the browser so I had to somehow disable all of them but one.

To create a new mapping first I created a folder, containing another folder, named symbols.

mkdir -p ~/my-xkb-config/symbols

In the symbols folder I created a file named custom with the following symbols mapping (I named it "presenter" for convenience):

xkb_symbols "presenter" {
    replace key <PGUP> { [ Left ] };
    replace key <PGDN> { [ Right ] };
    replace key <AB05> { [ Down ]  };
    replace key <ESC>  { [ Up ] };
    replace key <AC02> { [ m ] };
    replace key <LFSH> { [ m ] };
    replace key <LCTL> { [ m ] };
    replace key <FK05> { [ m ] };
    replace key <AD10> { [ m ] };
    replace key <LWIN> { [ m ] };
    replace key <LALT> { [ m ] };
    replace key <AC06> { [ Up ] };
};

It maps a KeyCode to a set of KeySyms (based on the modifier keys pressed). Since I don't care about the modifiers I added only a single entry in each array. All keycodes from the FullScreen button (except for one that I mapped to "Up") I mapped to the letter "m" since it was a letter that didn't produce side effects in the browser when accompanied with a modifier key. I thought of using some unused symbols such as "Break" or "ScrollLock" or even the special "VoidSymbol" and "NoSymbol" but none of them worked in Chrome because it was listening for the <FK05> keycode to reload the page instead of the F5 KeySym. The only case in which it did not reload the page was when <FK05> was translated to some letter keysym, e.g. m - then Chrome decided that this may be something that the Web Page would want to handle in the JavaScript code and propagated the event without refreshing the page. Firefox did not have these issues, but had a different one - it detected modifier keypresses based on KeyCode instead of KeySum, therefore if we used the letter "a" instead of "m" it would detect "Ctrl + A" and select the text on the page every second time I pressed the fullscreen button on my presenter (that I mapped to "Up") since the emitted keycodes included the Ctrl modifier <LCTL>. Therefore this was a compromise I had to make for the symbol map to work.

After that I created a different file - ~/xkb.conf (the directory and name do not matter):

xkb_keymap {
	xkb_keycodes  { include "evdev+aliases(qwerty)"	};
	xkb_types     { include "complete"	};
	xkb_compat    { include "complete"	};
	xkb_symbols   { include "pc+custom(presenter)"	};
};

This file was generated using setxkbmap -device 14 -print where 14 is the device ID for the presenter (the keyboard one, since there will be another pointer device associated with it with a different ID). Once again, this ID can be taken from xinput --list. I then removed the unnecesary configuration from it and for symbols I left only the "pc" map which was the most basic one (without it the modifier keycodes were not mapped and Gnome went crazy interpreting keycode combinations). Then I also added my +custom(presenter) map.

To compile the new layout and activate it only for the specified device I execute: cat ~/xkb.conf | xkbcomp -I/home/myuser/xkb -i 14 -synch - $DISPLAY. This throws warning because I did not map many keycodes for the "pc105" geometry (standard 105 keys keyboard), but it doesn't matter since the presenter will never emit them. Be careful with the Device ID as I accidentally wiped out my keyboard mappings by using the ID of my laptop keyboard and had to log out and log in again in my Gnome session to restore them. To persist the changes you need to write an XKB rule or think of some other way to execute the command when the presenter is connected to the PC but I was satisfied with manually running a single command.

@davthomaspilot
Copy link

Ah, it looks like xmodmap doesn't have a way to apply to a specific device.

@davthomaspilot
Copy link

You created the mapping file "custom" in ~/my-xkb-config/symbols. But how does the compile step

cat ~/xkb.conf | xkbcomp -I/home/myuser/xkb -i 14 -synch - $DISPLAY

find this file? Do I need another -I flag for ~/my-xkb-config/symbols?

(I get error: Can't find file "custom" for symbols include)

@davthomaspilot
Copy link

I think this line:

cat ~/xkb.conf | xkbcomp -I/home/myuser/xkb -i 14 -synch - $DISPLAY.

Should be

cat ~/xkb.conf | xkbcomp -I/home/myuser/my-xkb-config -i 14 -synch - $DISPLAY

(Your example did not create an xkb directory but a my-xkb-config dir)

Still, thanks for the info. Not working yet, but hopefully close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment