Quick Start
If you don't care about any of the explanation (I know I can be longwinded), just follow the {bolded numbers in curly brackets} at the beginning of some paragraphs. Follow each of these steps in order, and you can probably have this implemented in a few minutes. Note that this guide assumes some amount of Unix competency (being able to create directories, create files, sudo
elevating to root access, etc). Also note that root access is only required where indicated, so you do not need to use sudo
unless told to.
General Description of the Solution
We will be using xkb to add a "locking" (like Caps lock) numpad emulation to Linux. I would like my keys "jkluio789" to represent the numpad representations of the numbers "123456789", as well as a few other inclusions ("m,"->"0", "-=[]"->[numpad]"-+*", "."->[numpad]"."). I will toggle this "numpad mode" by using the key combination of [Shift
+Mod4
+[key]
] where Mod4
is the modifier code for my operating system key (also called "Command" or "Windows Key", and sometimes assigned to the modifier code of Super
or Hyper
), and [key]
is any of the keys used in my emulated numpad (such as "j" or "["). Simple modifications to this setup should be relatively simple after reading the full solution.
To do this, we will define a custom xkb "type" file, which tells xkb how to interpret the various modifier keys we will use to initialize our keypad emulation, as well as a custom xkb "symbols" file, which tells xkb how each key we press should behave normally (Group 1), how it should behave during the numpad emulation (Group 2), and how to switch back and for between the two (Level 3 action for both groups). Finally, we will make our solution permanent by integrating it into the current xkbmap using sed
each time we start a new session (so that our solution does not get erased each time xkb
is updated).
Detailed Description of the Solution
Directory Structure
{1} The first thing we will do is define a directory for us to keep our various files in. Yours can look like pretty much anything, but mine looks like this
/home
+-<username>
+-.xkb
+-symbols
+-types
+-keymap
+-log
Type File
Once we have our directory tree, lets define the actual files in our solution. The first thing we will do is define our "type" file. This file will tell xkb
how to move between "levels" (like how Shift
capitalizes a letter, moving from the first level of a lowercase letter to an uppercase level of the uppercase letter). These levels are a bit difficult to grasp, particularly for native English speakers, but international keyboards use them to great effect for alternative letters and symbols, as well as diacritical marks.
We will be using it to define how we intend to indicate a change in our keys. In other words, we tell it that we expect "level 1" behavior when no modifier is pressed (usually, a standard lower case letter when in our "normal mode"), "level 2" behavior when we hold the Shift
key (usually, a standard upper case letter in our "normal mode"), and "level 3" behavior uwhen we hold both Shift
+Mod4
(a special case for our purposes, which we use to indicate that, when used to modify a key, the key will now switch between modes).
{2} Open a new file, which we will call togglekeypad
. Copy the following codeblock into it, and save it into your types
directory under \home\<username>\.xkb\types
. NOTE: You may need to change all instances of Mod4
to whatever modifier your "Command"/"Windows Key" button corresponds to (you might need to experiment, see this webpage under Modifier Keys for guidance) or whatever other modifier you would like.
partial default xkb_types "togglekeypad" { // Name of this type file
type "TOGGLEKEYPAD" { // Name of this "type"
modifiers = Shift+Mod4; // The modifiers that this type concerns itself with
map[Shift] = level2; // Shift brings us to level 2
map[Mod4+Shift] = level3; // Windows key plus shift brings us to level 3
level_name[Level1] = "Base"; // Human-readable names for each level (not really used, but convenient)
level_name[Level2] = "Shift";
level_name[Level3] = "Transfer";
};
};
{3} We must also copy this file to the directory /usr/share/X11/xkb/types/
. This will require root privileges, which unfortunately kind of defeats the purpose of xkb
being a user-space application, but I can't seem to get setxkbmap
to recognize the file without doing this. Suggestions welcome!
Symbols File
The next thing we will do it tell xkb
what each key should do when modified in each of the ways we described in the types file.
We will say that we want to use two Groups in our symbols file. This means that each key has two different general behaviors that we will switch between in some way, those behaviors being the normal typing behavior, and the new numpad emulation behavior. For each key, we will say that 1) we want to use the TOGGLEKEYPAD
type, 2) we will define the symbols (i.e. what the computer sees) associated with each of the physical keys in both Groups for all Levels, and 3) we will define any actions (any special things that xkb
should do) associated with each key for both Groups across all Levels. This sounds rather confusing, but should make a bit more sense when look at an example.
The first key that we see in the symbols file I pasted below is the <AC07>
key. This corresponds to the "J" key on most keyboards, according to the map seen here (Figure 2). For this physical key, we are saying that, in normal mode: in level 1 (no modifiers, according to our type file) it will just type "j", and in level 2 (Shift
modifier) it will just type "J". In level 3, it does something special: there is no symbol associated with level 3, but there is an action, and that action is to LockGroup(group=2)
. In other words, change us over to our second group, our "Keypad" group. If we look on the next few lines, we see that we have more symbols and actions defined for group 2 for this same key. It says that, in level 1 (no modifiers) type no symbol, but RedirectKey(keycode=<KP1>)
. In other words, register this key as if we actually just pressed the <KP1>
key, which corresponds to the "1" on a keypad. (Note: we could have again put NoAction() and used the symbol KP_1
, which is the symbol that key <KP1>
corresponds to, but I thought this would give the best compatibility). For level 2, do the same thing, but add the Shift
modifier to the key. Finally, for level 3, we lock ourselves back to Group 1, "Standard" mode.
{4} Open a new file, which we will call togglekeypad_symbols
. Copy the following codeblock into it, and save it into your symbols
directory under \home\<username>\.xkb\symbols
.
default partial
xkb_symbols "togglekeypad" {
name[Group1]= "Standard";
name[Group2]= "Keypad";
key <AC07> { // J
type = "TOGGLEKEYPAD",
symbols[Group1] = [ j, J, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP1>), RedirectKey(keyCode=<KP1>, modifiers=Shift), LockGroup(group=1)]
};
key <AC08> { // K
type = "TOGGLEKEYPAD",
symbols[Group1] = [ k, K, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP2>), RedirectKey(keyCode=<KP2>, modifiers=Shift), LockGroup(group=1)]
};
key <AC09> { // L
type = "TOGGLEKEYPAD",
symbols[Group1] = [ l, L, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP3>), RedirectKey(keyCode=<KP3>, modifiers=Shift), LockGroup(group=1)]
};
key <AD07> { // U
type = "TOGGLEKEYPAD",
symbols[Group1] = [ u, U, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP4>), RedirectKey(keyCode=<KP4>, modifiers=Shift), LockGroup(group=1)]
};
key <AD08> { // I
type = "TOGGLEKEYPAD",
symbols[Group1] = [ i, I, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP5>), RedirectKey(keyCode=<KP5>, modifiers=Shift), LockGroup(group=1)]
};
key <AD09> { // O
type = "TOGGLEKEYPAD",
symbols[Group1] = [ o, O, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP6>), RedirectKey(keyCode=<KP6>, modifiers=Shift), LockGroup(group=1)]
};
key <AE07> { // 7
type = "TOGGLEKEYPAD",
symbols[Group1] = [ 7, ampersand, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP7>), RedirectKey(keyCode=<KP7>, modifiers=Shift), LockGroup(group=1)]
};
key <AE08> { // 8
type = "TOGGLEKEYPAD",
symbols[Group1] = [ 8, asterisk, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP8>), RedirectKey(keyCode=<KP8>, modifiers=Shift), LockGroup(group=1)]
};
key <AE09> { // 9
type = "TOGGLEKEYPAD",
symbols[Group1] = [ 9, parenleft, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP9>), RedirectKey(keyCode=<KP9>), LockGroup(group=1)]
};
// NumLock
key <AE06> { // 6
type = "TOGGLEKEYPAD",
symbols[Group1] = [ 6, asciicircum, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<NMLK>), RedirectKey(keyCode=<NMLK>), LockGroup(group=1)]
};
// Bottom Row (and zero)
key <AB07> { // M
type = "TOGGLEKEYPAD",
symbols[Group1] = [ m, M, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP0>), RedirectKey(keyCode=<KP0>, modifiers=Shift), LockGroup(group=1)]
};
key <AE10> { // 0
type = "TOGGLEKEYPAD",
symbols[Group1] = [ 0, parenright, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KP0>), RedirectKey(keyCode=<KP0>, modifiers=Shift), LockGroup(group=1)]
};
key <AB09> { // .
type = "TOGGLEKEYPAD",
symbols[Group1] = [ period, greater, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KPDL>), RedirectKey(keyCode=<KPDL>, modifiers=Shift), LockGroup(group=1)]
};
// Arithmetic Operators
key <AE11> { // -
type = "TOGGLEKEYPAD",
symbols[Group1] = [ minus, underscore, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KPSU>), RedirectKey(keyCode=<KPSU>, modifiers=Shift), LockGroup(group=1)]
};
key <AE12> { // +
type = "TOGGLEKEYPAD",
symbols[Group1] = [ equal, plus, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KPAD>), RedirectKey(keyCode=<KPAD>, modifiers=Shift), LockGroup(group=1)]
};
key <AD12> { // [
type = "TOGGLEKEYPAD",
symbols[Group1] = [ bracketleft, braceleft, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KPDV>), RedirectKey(keyCode=<KPDV>, modifiers=Shift), LockGroup(group=1)]
};
key <AD12> { // ]
type = "TOGGLEKEYPAD",
symbols[Group1] = [ bracketright, braceright, NoSymbol],
actions[Group1] = [NoAction(), NoAction(), LockGroup(group=2)],
symbols[Group2] = [NoSymbol, NoSymbol, NoSymbol],
actions[Group2] = [RedirectKey(keyCode=<KPMU>), RedirectKey(keyCode=<KPMU>, modifiers=Shift), LockGroup(group=1)]
};
};
Test our Keypad
{5} To test the keypad configuration as it is, open a Terminal
window and type
setxkbmap -types complete+togglekeypad -print | sed -e '/xkb_symbols/s/"[[:space:]]/+togglekeypad_symbols(togglekeypad)&/' > $HOME/.xkb/keymap/customMap
xkbcomp -I$HOME/.xkb -R$HOME/.xkb keymap/customMap $DISPLAY
This will grab the current settings of our xkb
map (using setxkbmap - print
) while setting the types used to complete+togglekeypad
(everything in the file /usr/share/X11/xkb/types/complete
and also including our types file at /usr/share/X11/xkb/types/togglekeypad
). It will then feed this into sed
, which will add our symbols togglekeypad
from our file togglekeypad_symbols
into the used symbols files. Finally, we used xkbcomp
to compile the new keymap.
Note that, on my machine, NumLock is presumed to be off (because there is no numpad on my machine), so the numpad keys will actually send their primary functions to the computer, i.e. Home, End, PG Up, PG Down, etc. To get numbers typed when using the emulated numpad, hold Shift. I have tried various methods to get this behavior flipped (swapping the modifers
argument between levels in the symbols file, assigning a new key to emulate the NumLock key <NMLK>
and toggling it), but nothing has yet worked for me. Thankfully, though, when tested in Blender, it performs exactly as expected without need for Shift being held down.
{6} If things have gone horribly wrong at this point, then don't worry, just logout/login (or at worst restart), debug, and try again. If everything is working, let's make it permanent.
Making the Solution Permanent
There are certainly some more elegant ways to make our solution persistent between sessions, but the easiest and most reliable method for me was to simply put the above commands at the end of my ~/.bashrc
file. I used the solution proposed here which adds a bit of error checking, and added a bit more (so I could see any error outputs).
{7} Open the file ~/.bashrc
. Add the following script to the end of it:
# Setup custom keyboard remapping to emulate a number pad when "Shift+Cmd+numap_key" is pressed to initialize
if [ -d $HOME/.xkb/keymap ]; then
setxkbmap -types complete+togglekeypad -print | \
sed -e '/xkb_symbols/s/"[[:space:]]/+togglekeypad_symbols(togglekeypad)&/' > $HOME/.xkb/keymap/customMap 2> $HOME/.xkb/log/sedErrors
xkbcomp -w0 -I$HOME/.xkb -R$HOME/.xkb keymap/customMap $DISPLAY > $HOME/.xkb/log/outputOfCommand 2>&1
fi
{8} Upon restarting, the numpad emulation should now be made permanent!
Conclusion
While the explanation is long, the method itself it relatively short. The shortfalls are that Blender requires a locking method to properly work, whereas I would have preferred a holding method instead, and also that it requires root access to get xkb
to recognize our custom types file for some reason. However, overall, this appears to be working well for me. If you have any questions or suggestions, please feel free to leave them below!