record-2026-08-07_00-00-32.mp4
UnixKey is an fcitx5 input method that aims to make inputting arbitrary Unicode characters using an English keyboard layout easier. This is especially useful if English is not your native language. Take German for example: German words often contain ä, ü, ö or ß. UnixKey allows you to set up rules so that you just type ae, ue, oe or ss' which will then automatically get converted to the corresponding German letters. With a few more rules, you can even exclude common English-only segments (like "que") so you can still type most English words too. Obviously, UnixKey also let's you undo a replacement by pressing a key you can configure.
The replacement value can also be specified by a command that runs every time the replacement is triggered.
It is inspired by Timwis UniKey which is a similar program for Windows.
Dependencies: fcitx5, cmake, ninja or make, libicu, C++ compiler
Before installing UnixKey, install the dependencies above. Check your distros package repository to find them. (Most Linux distros should ship a C++ compiler so you likely won't need to install that)
Warning
Unless you trust me completely for some reason, please make absolutely sure to
check the content of install.sh before running the command below.
You can run the installer using
sh -c "$(curl -fsSL https://github.com/Schlafhase/UnixKey/raw/refs/heads/master/install.sh)".
It will ask you for your sudo password because it has to move files to
/usr/lib/fcitx5/ and /usr/share/fcitx5/ which the user shouldn't have permission
to do.
You can also install UnixKey manually for more control.
Start by installing UnixKey:
Note
If you know what you're doing, you can replace /usr with another install prefix. In most cases, this isn't necessary and will break the installation.
git clone https://github.com/Schlafhase/UnixKey .
cd UnixKey
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release
cmake --build build
sudo cmake --install buildNow the last thing you need is a valid configuration. You can start by copying the example configuration from src/unixkey.json to ~/.config/unixkey.json
You don't need the source any more. Delete it if you want:
cd ..
rm -rf UnixKeyNow restart fcitx5 using fcitx5 -rd (will keep running even after closing the
terminal) and finally configure fcitx5 using fcitx5-configtool to use the
UnixKey input method:
- Open the config tool
- Search for "UnixKey" in the "Search Input Method" field.
- Double click on UnixKey to move it to your input methods.
- (Optional) Move it to the top to make it the default.
Add this input to your flake:
# ...
inputs = {
unixkey = {
url = "github:Schlahfase/UnixKey";
inputs.nixpkgs.follows = "nixpkgs";
};
};
# ...Add this to your configuration:
# ...
i18n.inputMethod = {
type = "fcitx5";
enable = true;
fcitx5.addons = with pkgs; [
kdePackages.fcitx5-qt
inputs.unixkey.packages.${pkgs.system}.default
];
};
# ...To uninstall UnixKey, just remove the installed files
sudo rm /usr/share/fcitx5/addon/unixkey.conf
sudo rm /usr/share/fcitx5/inputmethod/unixkey.conf
sudo rm /usr/lib/fcitx5/unixkey.so
# optionally remove the config
rm ~/.config/unixkey.jsonNote
Everytime your configuration doesn't work or UnixKey doesn't behave as expected, you can type [help] in any input window which will expand to a (hopefully helpful) error message.
The way you use UnixKey really depends on your configuration. But before going more in depth about configuring, I'll explain basic usage.
The configuration lives in ~/.config/unixkey.json.
The concept is simple: type text, if it matches a rule, it gets replaced with
something else. A very useful feature that you might not immediately understand
is undoing replacements. The configuration specifies a key ("undo_key" in
unixkey.json) that undoes the last replacement, when pressed. The key is an
integer that represents the keycode.
Note
Since the keycode isn't very user-friendly, I'll provide keycodes that I think make sense here:
- Backspace: 65288
- Backslash: 92
- Escape: 65307
- F1-F35: 65470 - 65504
- Other keys: You can find a list of all keycodes in the fcitx5 source. The codes are in hex so you need to convert them to decimal before putting them into your configuration.
You can also add modifiers to the key using the "undo_modifiers" property in
the configuration file. The value is a number. Below is a table of all
modifiers:
| Modifier | Value |
|---|---|
| No modifier | 0 |
| Shift | 1 |
| CapsLock | 2 |
| Ctrl | 4 |
| Alt | 8 |
| NumLock | 16 |
| Hyper | 32 |
| Super | 64 |
| Mod5 | 128 |
| MousePressed | 256 |
| Meta | 268435456 |
| Repeat (when the key was held and gets repeated) | 2147483648 |
Note
You can combine multiple modifiers by adding their values together (technically you are performing a bitwise or). Common combinations are:
- Alt + Shift: 8 + 1 = 9
- Ctrl + Shift: 4 + 1 = 5
- Allow all modifiers: 4294967295 (just all bits set to 1 on the 32-bit integer)
- and so on...
The second thing about undoing that can be configured is how long the last
replacement should be remembered ("undo_reset" in unixkey.json). A value of
5 means: After the replacement was made, 5 more insertions (usually single key
presses) can happen before the last replacement will be forgotten. You can set
this to a very high value to basically be able to undo whenever you like but I
think that low values make more sense here. This is because undoing a
replacement that happened potentially hundreds of characters before the cursor
is basically never intentional. Higher values will also make you unable to use
the specified key for anything else during the time the last replacement is
remembered.
Let's go through the process of making a configuration step by step:
I'll start by setting the undo settings:
{
"undo_key": 65307, // escape key (i know json doesn't have comments but i don't care)
"undo_modifier": 1, // with shift modifier in case I want to use the escape key without undoing
"undo_reset": 5
}These three values were explained in detail already, so I'll just continue to the actual replacement rules. First, we have the "case_sensitive" ruleset. As the name suggests, these rules are case sensitive which means that they will only trigger when the pattern is typed EXACTLY like in the config file.
For my German-letter configuration, this should include the umlauts because I want to be able to type lowercase and uppercase variants:
{
"undo_key": 65307,
"undo_modifier": 1,
"undo_reset": 5,
"case_sensitive": {
"ae": "ä",
"Ae": "Ä",
"AE": "Ä",
"oe": "ö",
"Oe": "Ö",
"OE": "Ö",
"ue": "ü",
"Ue": "Ü",
"UE": "Ü"
}
}And you have probably guessed it, There is also a "case_insensitive" ruleset which doesn't care about case. Let's put the ß in there because it doesn't have a case. It's also great for little macros like [email] which can expand to your email address.
{
"undo_key": 65307,
"undo_modifier": 1,
"undo_reset": 5,
"case_sensitive": {
"ae": "ä",
"Ae": "Ä",
"AE": "Ä",
"oe": "ö",
"Oe": "Ö",
"OE": "Ö",
"ue": "ü",
"Ue": "Ü",
"UE": "Ü"
},
"case_insensitive": {
"ss'": "ß",
"[email]": "me@example.com"
}
}This is a pretty good configuration already but theres one problem: Try to type a common english word like "true", "value", "does" or similar and you will notice that they turn to "trü", "valü" and "dös". This is, of course, not what you want. UnixKey has a solution for this. You can set the replacement to "UNIXKEY_PRESERVE" to tell UnixKey to leave the pattern alone when it matches.
{
"undo_key": 65307,
"undo_modifier": 1,
"undo_reset": 5,
"case_sensitive": {
"ae": "ä",
"Ae": "Ä",
"AE": "Ä",
"oe": "ö",
"Oe": "Ö",
"OE": "Ö",
"ue": "ü",
"Ue": "Ü",
"UE": "Ü"
},
"case_insensitive": {
"ss'": "ß",
"[email]": "me@example.com",
"true": "UNIXKEY_PRESERVE",
"blue": "UNIXKEY_PRESERVE",
"value": "UNIXKEY_PRESERVE",
"que": "UNIXKEY_PRESERVE", // in many cases even small segments like this one are enough (since "qü" never appears in german but "que" quite often in english)
"queue": "UNIXKEY_PRESERVE",
"does": "UNIXKEY_PRESERVE"
}
}You might think that you will have to put the whole english dictionary in there
but from my experience, even the small list of words to preserve in the example
config at src/unixkey.json is enough to have a pretty consistent typing
experience without unexpected replacements (at least with English and German).
UnixKey has another special keyword which allows replacing text with the output of any command. The following example configuration replaces [pwd] with a randomly generated password and stores that password in the clipboard.
{
// ...
"case_insensitive": {
"[pwd]": "UNIXKEY_CMD GEN_PASSWORD=\"$(cat /dev/urandom | tr -dc 'a-z-A-Z0-9\"@_?$^!#' | head -c 16)\" && wl-copy $GEN_PASSWORD && echo -n $GEN_PASSWORD"
}
}The syntax of this keyword is UNIXKEY_CMD {cmd}. Do note that the command will
block the entire fcitx5 process so avoid running commands that take long because
you will be unable to type anything for the duration of the command.
UnixKey is an addon for fcitx5 (an input method provider). It works by intercepting every key event and checking if a matching replacement can be found.
The way it matches against the replacements is like this:
On the first key event it checks if any replacement (starting from the longest) starts with the character the key would produce. If it does, keep that in mind as the "currently matching replacement" otherwise treat the next key event as a "first" key event again.
Following key events check if they match the expected value for the currently matching replacement. If yes, great now check if the replacement has been completed in which case it can be applied. Otherwise try if a shorter replacement would match and continue with that.
I use neovim and I noticed that it doesn't always work well with UnixKey. In Normal mode, pressing "o" and then "e" usually means "Insert new line and type an 'e'". With UnixKey (my configuration), this inserts a new line, removes it again and types an "ö".
The solution: I wrote a simple lua config that changes the input method based on the mode you're in. In insert, command and terminal mode, it uses UnixKey and in other modes, a "normal" input method is used ('keyboard-gb' in my case):
local normalIM = "keyboard-gb"
local insertIM = "unixkey"
changeIM = function(name)
local success = pcall(vim.fn.system, "fcitx5-remote -s " .. name)
if not success then
vim.notify("Failed to set fcitx5 input method. Is fcitx5-remote installed?")
end
end
useCorrectIM = function()
local mode = vim.api.nvim_get_mode().mode
if mode == "i" or mode == "c" or mode == "t" then
M.changeIM(insertIM)
else
M.changeIM(normalIM)
end
end
local imUpdateGroup = vim.api.nvim_create_augroup("update-im", { clear = true })
vim.api.nvim_create_autocmd("FocusLost", {
group = imUpdateGroup,
callback = function()
changeIM("unixkey")
end,
})
vim.api.nvim_create_autocmd("QuitPre", {
group = imUpdateGroup,
callback = function()
changeIM("unixkey")
end,
})
vim.api.nvim_create_autocmd("FocusGained", {
group = imUpdateGroup,
callback = useCorrectIM,
})
vim.api.nvim_create_autocmd("ModeChanged", {
group = imUpdateGroup,
callback = useCorrectIM,
})You can integrate this into your neovim configuration however you'd like. The above code is not the way I integrated it and I wouldn't recommend doing it that way. You can check my configuration if you need help. Of course, you can also contact me.
First, install the dependencies (see Quick Start) and clone the repository. Then generate the build files like this:
cmake -S . -B build/debug -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_EXPORT_COMPILE_COMMANDS=ON =DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build/release -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=ReleaseUse these commands to build and install debug/release:
cmake --build build/debug; sudo cmake --install build/debug
cmake --build build/release; sudo cmake --install build/releaseYou can use this command to clean up includes:
cmake --build build/debug --target fix-includes