Things that live here:

  1. Work log, where I note things I feel I'll have to Google later.
  2. Journal, very similar but about non-IT topics.
  3. Blog for rare longer-form posts (last one below).
  4. Link wiki (almost abandoned) and it's WIP conversion to a static website.

Feel free to look at what you can find here and enjoy yourself.

Latest posts from the Work log

Day 1266 / Disable mouse while typing blues part N

I now have an easy 220614-0020 Linux toggle touchpad binding. Still not optimal.


The Internet told me about atareao/Touchpad-Indicator: An indicator for the touchpad, which also does basic settings, including disable touchpad when typing.

First thing it did is change some settings with speed/acceleration/… on open, touchpad behaves differently now.

The disable-touchpad-when-typing doesn’t work for me, but other options work. Looking deeper, it changes these options in the synaptics driver, that I can view/edit throughsynclient.

synclient -l to list them.

The actual option itself seems to do this:

synclient PalmDetect=1

which doesn’t work for me either.

Python script

Someone wrote a python script to do the touchpad disabling: How can I disable touchpad while typing? On Ubuntu 16.04 syndaemon isn’t working - Ask Ubuntu, but does it have to come to this?

A solution online was to disable one-finger-taps as clicks, but in my qtile setup the focus follows the mouse, even without clicks.

But actually actually actually - that’s a setting I’m not too attached to!

Disable one-tap-click and don’t focus on mouse hover

The hopefully final solution:

  1. synclient TapButton1=1
  2. Added this to follow_mouse_focus = False

Unexpectedly, helped with a lot of random usability bits.

Day 1265

Python parse library that's the opposite of formatted strings

Had a string generated like f"Something {filename} etc.", needed to get filename.

The parse · PyPI library does just that and is the opposite of python’s format. And has also additional neat functions.

pytest-print to print strings when running pytests

pytest-print · PyPI adds a printer that when passed to the pytest itself can be used to print stuff, like steps, debug values maybe, etc.

Day 1260

qtile logging

from libqtile.log_utils import logger
# ...
logger.warning("Disabling touchpad")

qtile lazy functions

Finally got them! Or maybe wasn’t clear in older versions of the docu.

Lazy objects — Qtile 0.1.dev50+g2b2cd60.d20220610 documentation

Option 1:

from libqtile.config import Key
from libqtile.lazy import lazy

def my_function(qtile):

keys = [
        ["mod1"], "k",

Option 2:

from libqtile.lazy import lazy
from libqtile.log_utils import logger

def multiply(qtile, value, multiplier=10):
    logger.warning(f"Multiplication results: {value * multiplier}")

keys = [
        ["mod1"], "k",
        lazy.function(multiply, 10, multiplier=2)

Or decorated version

from libqtile.config import Key
from libqtile.lazy import lazy
from libqtile.log_utils import logger

def multiply(qtile, value, multiplier=10):
    logger.warning(f"Multiplication results: {value * multiplier}")

keys = [
        ["mod1"], "k",
        multiply(10, multiplier=2)

Linux toggle touchpad

Toggle touchpad (enable/disable) in Linux with xinput.:

if xinput list-props 13 | grep "Device Enabled ([[:digit:]]\+):\s*1" >/dev/null; then xinput disable 13 && notify-send -u low -i mouse "Trackpad disabled"; else xinput enable 13 && notify-send -u low -i mouse "Trackpad enabled"; fi

With 13 being the xinput id of the touchpad.

My old enable/disable oneliners have bits on how to find the ID:

'bash -c "xinput | grep TouchPad | ag -o "[0-9][0-9]"  | xargs xinput disable"'

That said, I don’t remember the ID ever being anything else than 13.

Day 1251 / Qtile replacing countdown-notification mechanism

I had this:

tm_old() {
    local DATE=$(date +'%H:%M:%S %d/%m')
    local N="$1"; shift
	  (utimer -c $N && zenity --info --title="Time's Up" --text="${*:-BING} \n\n $DATE")

I used it as tm 3m message and get a popup in three minutes with “message”. Used it for reminders of random stuff like “turn off the stove” or “stop doing X”.

Now utimer seems to be dead, and qtile makes the alert popup messages pop up in the wrong workspace group, usually the one wrote the command in instead of the currently active one.

Today I solved the last part by switching to notify-send. Found dunst, added to startup, now notify-send creates nice visible alerts: 2022-06-05-001631_384x90_scrot.png

It seems to support a lot of cool stuff like progress bars and images: dunst-project/dunst: Lightweight and customizable notification daemon

Dunst - The Blue Book - nice post, and woohooo a digital garden!

Useful commands:

  • dunstctl close-all
  • dunstctl history-pop

Added the first one as qtile shortcut:

        [mod, ctrl],
        desc="Clear notifications",

There’s also dunstify which is a notify-send with more options.

Changed the zsh command to use notify-send. Everything works nicely now.

If utimer stops working I’ll prolly write a python script that does a countdown1 and then a configured notification/action/.., without relying on .zshrc aliases and bash functions. We’ll see.

  1. Or use existing solutions: alexwlchan/timers: A simple command-line stopwatch and countdown clock ↩︎

Day 1249 / Plotly updating graphs

Reading Creating and updating figures in Python.

  1. All of these are equivalent (code from link):
fig.update_layout(title_text="update_layout() Syntax Example",

fig.update_layout(title_text="update_layout() Syntax Example",

fig.update_layout(title=dict(text="update_layout() Syntax Example"),

fig.update_layout({"title": {"text": "update_layout() Syntax Example",
                             "font": {"size": 30}}})

fig.update_layout(title=go.layout.Title(text="update_layout() Syntax Example",
  1. Introducing linebreaks: <br> and <br /> work, <br/> doesn’t. 1
  1. Margins in graph: Setting graph size in Python
fig.update_layout(margin=dict(l=20, r=20, t=20, b=20))

And I just want to mention the very special design decision to have arguments named tickfont and title_font (with underscore), in the same function, getting identical arguments.

  1. r - How to add line breaks to plotly hover labels - Stack Overflow ↩︎

Day 1248

Huggingface HF Custom NER with BERT: tokenizing, aligning tokens, etc.

Really nice google colab showing more advanced datasets bits in addition to what’s on the label: Custom Named Entity Recognition with BERT.ipynb - Colaboratory

Pasting this example from there:

class dataset(Dataset):
	def __init__(self, dataframe, tokenizer, max_len):
		self.len = len(dataframe) = dataframe
		self.tokenizer = tokenizer
		self.max_len = max_len
	def __getitem__(self, index):
		# step 1: get the sentence and word labels
		sentence =[index].strip().split()
		word_labels =[index].split(",")
		# step 2: use tokenizer to encode sentence (includes padding/truncation up to max length)
		# BertTokenizerFast provides a handy "return_offsets_mapping" functionality for individual tokens
		encoding = self.tokenizer(sentence,
		# step 3: create token labels only for first word pieces of each tokenized word
		labels = [labels_to_ids[label] for label in word_labels]
		# code based on
		# create an empty array of -100 of length max_length
		encoded_labels = np.ones(len(encoding["offset_mapping"]), dtype=int) * -100
		# set only labels whose first offset position is 0 and the second is not 0
		i = 0
		for idx, mapping in enumerate(encoding["offset_mapping"]):
		if mapping[0] == 0 and mapping[1] != 0:
		# overwrite label
		encoded_labels[idx] = labels[i]
		i += 1
		# step 4: turn everything into PyTorch tensors
		item = {key: torch.as_tensor(val) for key, val in encoding.items()}
		item['labels'] = torch.as_tensor(encoded_labels)
		return item
	def __len__(self):
		return self.len

For aligning tokens, there’s Code To Align Annotations With Huggingface Tokenizers. It has a repo: LightTag/sequence-labeling-with-transformers: Examples for aligning, padding and batching sequence labeling data (NER) for use with pre-trained transformer models

Also the official tutorial (Token classification) has a function to do something similar:

def tokenize_and_align_labels(examples):
    tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)

    labels = []
    for i, label in enumerate(examples[f"ner_tags"]):
        word_ids = tokenized_inputs.word_ids(batch_index=i)  # Map tokens to their respective word.
        previous_word_idx = None
        label_ids = []
        for word_idx in word_ids:  # Set the special tokens to -100.
            if word_idx is None:
            elif word_idx != previous_word_idx:  # Only label the first token of a given word.
            previous_word_idx = word_idx

    tokenized_inputs["labels"] = labels
    return tokenized_inputs

git delete branch; git delete commit

git delete commit

git rebase -i SHA_of_commit_to_delete^ drops you into the usual screen, three you can change pick to drop in the first line (or any others) to just delete that commit.

Generally, On undoing, fixing, or removing commits in git seems like The README for that.

git delete branch

  • git branch -d some-branch deletes a local branch
  • git push origin --delete some-branch deletes a remote branch

(as usual, remembering that branches are pointers to commits)

Day 1242

Linux changing password delay

Changing the timeout delay for wrong logins on linux has a lot of details, in my case the TL;DR was:

  1. /etc/pam.d/login change the number, in microseconds;
  2. disable delays completely in /etc/pam.d/common-auth by adding nodelay to: auth [success=1 default=ignore] nullok_secure nodelay

The second one works also for everything inheriting that, which is a lot.

inxi for getting basic info about a system

When debugging an issue I had with my monitor, found a mention of inxi1, which seems to colorfully output basic system (incl. hardware) info.

The post asked for inxi -SMCGx, inxi help told me inxi -F is the fullest possible output.


  1. [SOLVED] HDMI Monitor is recognized but has no signal, if set to WQHD resolution - Linux Mint Forums ↩︎

Debugging general linux problems + listing files by modification date

debugging - I have a hardware detection problem, what logs do I need to look into? - Ask Ubuntu:

Then, causing the problem to happen, and listing the system’s logs in reverse order of modification time:

ls -lrt /var/log, tail -n 25 on recently modified log files (for reasonable values of 25), and dmesg.

Read, wonder, think, guess, test, repeat as needed

Causing the problem and then looking at the recently modified logs is common sense but brilliant.

And saving ls -lrt as “list by modification time”.

-t is “sort by modification time” and is easy to remember.

Day 1241 / Noise cancelling and pipewire

So, noisetorch says it’s potentially compromised: Release POTENTIAL COMPROMISE · noisetorch/NoiseTorch.

An improvement for the previous more dramatic formulation: Community code review? · noisetorch/NoiseTorch@b4bb8e6

This project is dead, i’ve failed you.

Thoughts and prayers (honestly! I loved it), with a heavy heart I keep looking.

Option1: werman/noise-suppression-for-voice: Noise suppression plugin based on Xiph’s RNNoise

Reading how to install it made me very sad, kept looking.

Saw EasyEffects mentioned, but it runs on Pipewire.

TIL Pipewire is a Pulseaudio replacement.

Installed via this guide: How to install PipeWire on Ubuntu Linux - Linux Tutorials - Learn Linux Configuration

Installed and ran EasyEffects using flatpak:

flatpak install easyeffects
flatpak run com.github.wwmm.easyeffects

EasyEffects' GUI looks awesome!

Had to choose another input source in pavucontrol, then once the input is piped thorugh it - the effect “Noise Reduction” works! Removes both keyboard and random background white noise.

You can even save the config as preset and make it run automagically on startup!

Day 1240

python sanitizing filenames with external library

sanitize-filename · PyPI does what it says on the box.

It’s more complex than the replace--/ that I had in mind: sanitize_filename/ · master · jplusplus / sanitize-filename · GitLab

And intution tells me using external semi-unknown libraries like this might be a security risk.

TODO - what is the best practice for user-provided values that might become filenames?.. Something not smelling of either injection vulns or dependency vulns?

pycharm nagging me about TODOs before committing might actually be useful

I use # TODOs for “Do later”.

If they exist, Pycharm asks me every time before committing if I really want to.

I guess the idea is to use them to mark things to do before committing, so much smaller scale and here-and-now?

HF datasets intro google colab

HF Datasets' README links this nice google colab that explain the basics: HuggingFace datasets library - Overview - Colaboratory

git bisect

TIL about git bisect.

git help bisect for help.

TL;DR: uses binary search to find a commit that introduced a change. You run it, it gives you a commit, you tell it if it’s good or bad, and it keeps narrowing down the options.

git bisect start -> git bisect good -> git bisect bad -> git bisect reset

Latest post from Blog

My custom keyboard layout with dvorak and LEDs


My keyboard setup has always been weird, and usually glued together with multiple programs. Some time ago I decided to re-do it from scratch and this led to some BIG improvements and simplifications I’m really happy about, and want to describe here.

Context: I regularly type in English, German, Russian, Ukrainian, and write a lot of Python code. I use vim, qutebrowser, tiling WMs and my workflows are very keyboard-centric.

TL;DR: This describes my current custom keyboard layout that has:

  • only two sub-layouts (latin/cyrillic)
  • the Caps Lock LED indicating the active one
  • Caps Lock acting both as Ctrl and Escape
  • things like arrow keys, backspace accessible without moving the right hand
  • Python characters moved closer to the main row

It looks like this1: kl_cut.png

and is available on Github.

How I got into custom keyboard layouts

First, one long summer, I switched to the Dvorak keyboard layout2 and loved it.

Then I saw xkcd’s Randall Munroe’s Mirrorboard: A one-handed keyboard layout for the lazy – xkcd. The idea is that it’s easy to repeat with your left hand movements that you do with your right if they are mirrored. This works for blind typing too - if you type l with your right pinky finger, probably your left pinky finger ‘knows’ that reflex as well.

I loved the idea. My right hand usually has either a mouse or a cup of tea in it, and casual left-hand typing without needing to learn an entirely new layout sounded really interesting.

I decided to create such a mirrored layout for Dvorak.

This led me to the topic of customizing xkb keyboard layouts (the Arch wiki describes it very well).

At the end I did create a Dvorak Mirrorboard layout and used it more than expected (for example, image editing is easier if you don’t need to move your hand from the mouse).

But almost immediately I realized the potential of editing layouts and started to add things I needed, like Enter/Backspace, ümläüts and ß etc. - still mirrored, but now not a generic layout anymore. Needing a new name I decided on Pchr8board.

There were N iterations, here’s an old post about one of them: Pchr8board - a mirrored left-hand keyboard layout for Dvorak -

Then I kept adding stuff, in the process abandoning most left-hand features. Slowly we converged to a layout I liked.

Non-xkb weirdness

I had other non-standard changes I was really attached to:

  • Since forever I have my Caps Lock key remapped to Ctrl, which I strongly recommend to literally everyone. Ctrl is used often and that position is much easier to reach, and no one needs Caps Lock. An ugly xmodmap hack on autostart remapped both keys.
  • Caps-Lock-but-now-Ctrl, if released quickly, it acted as Escape. Incredibly neat, for vim especially. I used xcape3 for this.

It all worked but not flawlessly

All together the setup was a net positive, but was very brittle.

Xcape is clearly abandoned, and neither xmodmap nor xcape work in Wayland. But there are far worse problems.

You need to run it manually on startup (could never get it to run automatically in a reliable way, believe me I tried) and every time you connect a new keyboard.4

Sometimes it took multiple attempts to get all parts working. And every time you run xmodmap it resets your layout and you need to re-run setxkbmap.

Not a hypothetical scenario

…Which you may not be able to do, because you’re stuck with a broken layout or Caps Lock on or a Ukrainian layout and no way to change it, because you can’t open a terminal and type a command to do that.

Also all the WM keybindings relying on the former Ctrl key are broken in the process.

You can GUESS where the Alt key is now and then try to get into a tty. Then your Ctrl and Esc are not where you are used to.

Long story short - it was worth the pain, but the pain was there. The setup was band aids on top of other band aids, some applied at the very beginning when I had no idea what I was doing but hey, it works.

One keyboard layout to rule them all

Then I finally did it all from scratch and for real.

The layout in all its glory


Only the changed keys are labelled, with the exception of the default characters when they help me to identify a key.

The keys are read like this: key_with_explanations.png

The key change is that the Left Alt button becomes a modifier key that makes more symbols/actions available. “Latch” is <Level3> and is located on Left Alt5.

For example, to get Ä you press <Shift+Latch+a>. For ä you just press <Latch+a>. A needs only Shift.

In the layout definition itself this is represented like this:

key <AC01> { [	    a,	A, adiaeresis,	Adiaeresis]	};

The Right Alt key still works like a normal Alt.


Notation that I made up6:

  • Written in full (‘Shift’ or ‘Left Alt’) or capitalized (<LALT>) are physical keys on the keyboard. Given as:
    • keylabel / what xkb calls them inside the layout file (<RCTRL>)
    • default Dvorak value (<c> refers to the key that produces an i in QWERTY or ш in Ukrainian/Russian).
  • Shift/LALT are the logical thing after all remappings and modifiers are applied
  • <Shift+q> are keybindings, with modifiers given by their logical/remapped values (Shift can be located anywhere on the keyboard as long as it works as a Shift), and the letters are the usual/normal/Level1 unchanged ones (<Shift+q> produces a Q, but alone the key <q> would be a lowercase q).

So <LCTRL> would be “left physical key on the keyboard with Ctrl written on it”, <Shift-Latch-g> would be “Press whatever keys / pedals / mouse buttons that are your Shift and Latch modifiers, then the key on the keyboard that in dvorak on normal systems results in a q appearing on the screen”.

Two languages instead of four and one LED

I created two layouts, v6 the latin one with umlauts and everything else, and ruua, that contains both Russian and Ukrainian characters in the same layout.

Pressing the right Control key once changes the layout:

	key  <RCTL> {	[ISO_Next_Group]	};

Having only two layouts means you never have to guess which one comes next or set up indicators in the taskbar. You just let your muscle memory automatically do its thing.7

But unlocking the laptop was still a pain. You don’t know the language you were typing in when you locked it, and things like i3lock don’t tell you the layout by default - and you never know if a wrong password is a typo or a wrong layout.

The grp_led option allows you to use keyboard LEDs as indicators.

setxkbmap -option -option 'grp_led:caps' vv,ruua

Now anytime I’m typing in Russian or Ukrainian the Caps Lock LED is on, regardless of what is shown on the display. Pressing RCTRL changes the layout and makes the LED turn off, and you know it worked.

Custom modifiers defined in the layout itself

No xmodmap anymore! Caps Lock is now Ctrl, with Latch it becomes an Escape (and the former Ctrl button is a new modifier key Hyper_L, guaranteed not to collide with anything).

That took time to get right, the key was making Caps Lock a four-level key5, then we can define what happens to it with Level3/Latch/<LALT>:

key <CAPS> { type[Group1] = "FOUR_LEVEL", symbols[Group1] = [ Control_L, Control_L, Escape, NoSymbol] };
modifier_map Control { <CAPS> };

For more, look into modifier_map8 and real/virtual modifiers9 on the Arch Linux Wiki.

Arrow keys and Backspace easy to reach


Shown in purple, directly in the right hand resting position:

  • Arrow keys (CHTN is the new WASD!).10
  • <Backspace> and <Delete>!
key <AD10> { [	    l,	L, BackSpace, Delete		]	};
key <AC07> { [	    h,	H,	Left,	Left		]	};
key <AC08> { [	    t,	T,	Down,	Down   ]	};
key <AC09> { [	    n,	N,	Right,	Right		]	};

Being able to quickly delete text with my ring finger without stopping to type to reach the backspace key feels as good as it sounds.

Best thing, all this works with keyboard shortcuts! <Ctrl-Alt-R> deletes the entire previous word, etc.

Programming features

Mostly the improvements cluster in two areas:

  • Move all brackets closer to resting position.
  • No Shift for frequent characters
    • Python and programming:+,-,=
    • Vim and vim-like things: :!
      • ; now needs a Shift, a sacrifice I’m ready to make.

Left Alt as modifier key works very well for them - it’s easier to reach for my left thumb than Shift was for any finger ever.

Some redundancy and left-hand features

There are two additional Enter keys, one on Space and the other one under Escape. Both closer than the real one, and the latter needs only the left hand. (I found I need a left-hand Enter more often than any other.)

On that same tilde key there’s a Compose key too, which allows to type some exotic characters that are used too rarely to get their own key.

There is also more than one way to do slashes, this mostly has to do with old layouts I had and still remember if I’m tired or stressed.


After you read ArchWiki’s Precautions and preparations and assuming you need both the latin and cyrillic layouts:

  • Copy the source files to /usr/share/X11/xkb/symbols/. (Or maybe create a symlink to a version-controlled version of the layout, then you can do your modifications and test them more easily.)
  • Name them something reasonable, the file name will be the name used by setxkbmap to refer to the layout.11

For the full experience

  1. Assuming the layouts are in /usr/share/X11/xkb/symbols/v6 and /usr/share/X11/xkb/symbols/ruua, run:
    setxkbmap -option -option 'grp_led:caps' v6,ruua
  2. If it works, add that command to autostart, it should work.

Light-mode experience

If you don’t want to go all-out:

  1. Run setxkbmap -option us, now you have it in your terminal history
  2. setxkbmap -option -option 'grp_led:caps' v6,us would give you the new layout and on <RCTRL> you get a standard QUERTY one.
  3. If something goes wrong, use the arrow keys to find the command setxkbmap -option us and press Enter to run it, and you’re back in known territory.

The layouts definitions

The sources, .json and the pictures are all available on Github. Pasting them below too for completeness and redundancy.

View the sources of both layouts


// My current layout, no connection to dvorak-mirrorboard anymore

default  partial alphanumeric_keys modifier_keys
xkb_symbols   "sh" {

	name[Group1] = "SH Custom layout";

	// Using L-Alt as modifier instead of Caps lock.
	key <LALT> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [ ISO_Level3_Shift ] };

	// Mod+Space is return
	// TODO
	key <SPCE> { [ space, space, Return ] };

	// Bsp, Enter, **Compose Key **
	key <TLDE> {	[     BackSpace,	Multi_key,	Return,	 NoSymbol]	};

	// Tab, LTab, /, b\

	key  <TAB> {	[ Tab,	backslash, slash, NoSymbol]	};

	// Switch groups by RCTL
	key  <RCTL> {	[ISO_Next_Group]	};

	// Caps is Ctrl, ? <Escape> ?
	// Mapping Escape to Caps+Shift doesn't work for some reason
	key <CAPS> { type[Group1] = "FOUR_LEVEL", symbols[Group1] = [ Control_L, Control_L, Escape, NoSymbol] };
    modifier_map Control { <CAPS> };

	key <LCTL> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [Hyper_L] };
	modifier_map Mod3 { Hyper_L };


	//// FIRST ROW 
	// '"`?
	key <AD01> { [  apostrophe,	quotedbl, quoteleft, NoSymbol] };
	// ,<[?
	key <AD02> { [	comma,	less,   bracketleft, NoSymbol] };
	// .>]?
	key <AD03> { [      period,	greater, bracketright, NoSymbol] };

	key <AD04> { [	    p,	P, asciitilde, NoSymbol		]	};
	key <AD05> { 
		[y,	Y, f, F], 
		[a, a, a, a] 

	// Umlauts
	key <AC01> { [	    a,	A, adiaeresis,	Adiaeresis]	};
	key <AC02> { [	    o,	O, odiaeresis,	Odiaeresis]	};
	key <AC03> { [	    e,	E, ediaeresis,	Ediaeresis]	};
	key <AC04> { [	    u,	U, udiaeresis,	Udiaeresis]	};
	key <AC05> { [	    i,	I, d, D		]	};

	key <AB01> { [   colon,	semicolon,z, Z] };
	key <AB02> { [	    q,	Q, v, V		]	};
	key <AB03> { [	    j,	J, w, W		]	};
	key <AB04> { [	    k,	K, m, M		]	};
	key <AB05> { [	    x,	X, b, B		]	};

	key <AE01> {	[	  1,	exclam,		NoSymbol,	NoSymbol	]	};

	// 2@<{
	key <AE02> {	[	  2,	at,		less,	NoSymbol	]	};
	// 3#>}
	key <AE03> {	[	  3,	numbersign,	greater,	NoSymbol	]	};
	key <AE04> {	[	  4,	dollar,		EuroSign,	NoSymbol	]	};
	key <AE05> {	[	  5,	percent,	NoSymbol,	NoSymbol	]	};

	//// Backspace, arrow keys, ...
	// TODO 
	// key <AD07> { [	    g,	G, Prior, NoSymbol		]	};
	key <AD07> { [	    g,	G, parenleft, braceleft		]	};
	key <AD08> { [	    c,	C,	Up,	 Up	]	};
	key <AD09> { [	    r,	R,	parenright,	braceright		]	};
	// key <AD09> { [	    r,	R,	Next,	Next		]	};
	key <AD10> { [	    l,	L, BackSpace, Delete		]	};
	key <AC07> { [	    h,	H,	Left,	Left		]	};
	key <AC08> { [	    t,	T,	Down,	Down   ]	};
	key <AC09> { [	    n,	N,	Right,	Right		]	};

	key <AD06> { [	    f,	F  		]	};
	// Slash and Backslash
	key <AD11> { [	slash,	question, backslash, NoSymbol	]	};
	key <AD12> { [	equal,	plus		]	};

	// TODO
	key <AC06> { [	    d,	D, NoSymbol, NoSymbol		]	};
    key <AC10> { [	    s,	S,	ssharp,	ssharp		]	};
	key <AC11> { [	minus,	underscore	]	};

	key <AB06> { [	    b,	B		]	};
	key <AB07> { [	    m,	M		]	};
	key <AB08> { [	    w,	W		]	};
	key <AB09> { [	    v,	V		]	};
	key <AB10> { [	    z,	Z		]	};

	// +|\? - the key that by default has only backslash+bar
	key <BKSL> { [  plus,  bar, backslash, NoSymbol             ]       };

	key <AE06> {	[	  6,	asciicircum	]	};
	key <AE07> {	[	  7,	ampersand	]	};
	key <AE08> {	[	  8,	asterisk	]	};
	key <AE09> {	[	  9,	parenleft	]	};
	key <AE10> {	[	  0,	parenright	]	};
	key <AE11> {	[     bracketleft,	braceleft	]	};
	key <AE12> {	[     bracketright,	braceright		]	};


Russian-Ukrainian layout, I adapted an existing one I found:

// Keyboard layouts for Russia.
// AEN <>
// 2001/12/23 by Leon Kanter <>
// 2005/12/09 Valery Inozemtsev <>
// 2018/07/15 @a13 (a.k.a. @dbvvmpg) and Stepanenko Andrey <>
// 2021 - Adapted to contain Ukrainian characters -

// Windows layout
default  partial alphanumeric_keys
xkb_symbols "winkeys" {

    include "ruua(ruua)"
    name[Group1]= "Russian";

    key <AE03> { [           3,  numerosign  ] };
    key <AE04> { [           4,   semicolon  ] };
    key <AE05> { [           5,     percent  ] };
    key <AE06> { [           6,       colon  ] };
    key <AE07> { [           7,    question  ] };
    key <AE08> { [           8,    asterisk, U20BD  ] };

    key <AB10> { [      period,       comma  ] };

    // SH -- now adding the bksp and stuff and removing the Enter thing.
	key <SPCE> { [ space] };
	// Mod+Tab gives a slash, which I use often (searching etc.) 
	// Mod+Shift+Tab gives an umlaut on the next character

hidden partial alphanumeric_keys
xkb_symbols "ruua" {

    key <AE01> { [           1,      exclam  ] };
    key <AE02> { [           2,    quotedbl  ] };
    key <AE03> { [           3,  numbersign  ] };
    key <AE04> { [           4,    asterisk  ] };
    key <AE05> { [           5,       colon  ] };
    key <AE06> { [           6,       comma  ] };
    key <AE07> { [           7,      period  ] };
    key <AE08> { [           8,   semicolon  ] };
    key <AE09> { [           9,   parenleft  ] };
    key <AE10> { [           0,  parenright  ] };
    key <AE11> { [       minus,  underscore  ] };
    key <AE12> { [       equal,        plus  ] };
    key <BKSL> { [   slash,         backslash  ] };

    key <AB10> { [       slash,    question  ] };
    key <LSGT> { [       slash,         bar  ] };

    key <TLDE> { [       Cyrillic_io,	apostrophe,	U02BC,       Cyrillic_IO  ] };
    key <AD01> { [   Cyrillic_shorti,   Cyrillic_SHORTI  ] };
    key <AD02> { [      Cyrillic_tse,      Cyrillic_TSE  ] };
    key <AD03> { [        Cyrillic_u,        Cyrillic_U  ] };
    key <AD04> { [       Cyrillic_ka,       Cyrillic_KA  ] };
    key <AD05> { [       Cyrillic_ie,       Cyrillic_IE] };
    key <AD06> { [       Cyrillic_en,       Cyrillic_EN  ] };
    key <AD07> { [      Cyrillic_ghe,      Cyrillic_GHE  ] };
    key <AD08> { [      Cyrillic_sha,      Cyrillic_SHA  ] };
    key <AD09> { [    Cyrillic_shcha,    Cyrillic_SHCHA  ] };
    key <AD10> { [       Cyrillic_ze,       Cyrillic_ZE  ] };
    key <AD11> { [       Cyrillic_ha,       Cyrillic_HA  ] };
    key <AD12> { [ Cyrillic_hardsign,	Cyrillic_HARDSIGN,	Ukrainian_yi,	Ukrainian_YI] };

    key <AC01> { [       Cyrillic_ef,       Cyrillic_EF  ] };
    key <AC02> { [     Cyrillic_yeru,     Cyrillic_YERU,	Ukrainian_i,	Ukrainian_I] };
    key <AC03> { [       Cyrillic_ve,       Cyrillic_VE  ] };
    key <AC04> { [        Cyrillic_a,        Cyrillic_A  ] };
    key <AC05> { [       Cyrillic_pe,       Cyrillic_PE  ] };
    key <AC06> { [       Cyrillic_er,       Cyrillic_ER  ] };
    key <AC07> { [        Cyrillic_o,        Cyrillic_O  ] };
    key <AC08> { [       Cyrillic_el,       Cyrillic_EL  ] };
    key <AC09> { [       Cyrillic_de,       Cyrillic_DE  ] };
    key <AC10> { [      Cyrillic_zhe,      Cyrillic_ZHE  ] };
    key <AC11> { [        Cyrillic_e,        Cyrillic_E,	Ukrainian_ie,	Ukrainian_IE] };

    key <AB01> { [       Cyrillic_ya,       Cyrillic_YA  ] };
    key <AB02> { [      Cyrillic_che,      Cyrillic_CHE  ] };
    key <AB03> { [       Cyrillic_es,       Cyrillic_ES  ] };
    key <AB04> { [       Cyrillic_em,       Cyrillic_EM  ] };
    key <AB05> { [        Cyrillic_i,        Cyrillic_I] };
    key <AB06> { [       Cyrillic_te,       Cyrillic_TE  ] };
    key <AB07> { [ Cyrillic_softsign, Cyrillic_SOFTSIGN  ] };
    key <AB08> { [       Cyrillic_be,       Cyrillic_BE  ] };
    key <AB09> { [       Cyrillic_yu,       Cyrillic_YU  ] };

    include "kpdl(comma)"

Parting thoughts

Custom keyboard layouts for the win

Tweaking to your purposes something as fundamental as a keyboard layout is strangely empowering. And adapting to a new layout is like learning a foreign language - if you did it at least once in your life, the next ones are much easier. Especially if it’s small things like moving a key, or just adding more symbols to the existing layout.

Wouldn’t recommend it to everyone, though.

One thing I would recommend to everyone without exception is switching the Ctrl and Caps Lock keys. It can be easily done on any OS, including Linux, where too it can be done without editing any layout files12.

Interesting resources on topic

Thank you for reading!

  1. Visualization done with the excellent Keyboard Layout Editor ↩︎

  2. Dvorak keyboard layout - Wikipedia ↩︎

  3. xcape: Linux utility to configure modifier keys to act as other keys when pressed and released on their own. ↩︎

  4. Can be automated of course, but all tutorials I found gave me the impression it’s a worse can of worms than the one I already had, and I never tried. ↩︎

  5. We set <LALT> as a one-level key, that is not affected by anything. (If a key can be changed only by Shift it’d be two-level, for example.) And we make it act as Level3 modifier, basically another kind of Shift, closer to AltGr originally (and still in countries like Germany).

    key <LALT> { type[Group1] = "ONE_LEVEL", symbols[Group1] = [ ISO_Level3_Shift ] };

    Any keys that accept it have to also accept Shift and therefore have to be at least four-level. ↩︎

  6. I don’t feel like doing the “(keycode, group, state) → keysym” thing in this post, it’s not meant to be a tutorial ↩︎

  7. The beauty of two layouts instead of more can only be appreciated by someone who constantly had to switch between multiple ones. ↩︎

  8. X keyboard extension - ArchWiki ↩︎

  9. X keyboard extension - ArchWiki ↩︎

  10. Arrow keys - Wikipedia ↩︎

  11. v6.cpp was born from my wish to have syntax highlighting in vim, it being late and a .cpp extension being the easiest way to get some adequate highlighting going. ↩︎

  12. keyboard - How do I remap the Caps Lock and Ctrl keys? - Ask Ubuntu ↩︎