I use the DVORAK keyboard layout for all work and typing in general, but while gaming on linux that means that I need to manually rebind the game controls in strange ways that don’t make a lot of sense when looking at the settings menu, only via muscle memory. So I made a script that automatically switches to QWERTY when Steam games are launched
Prerequisites
python3,setxkbmap
1. Niri config → register keyboard layouts
In ~/.config/niri/cfg/input.kdl, change the xkb layout to expose both Dvorak and QWERTY.
xkb {
layout "us, us" // Index 0 = Dvorak, Index 1 = QWERTY
variant "dvorak,"
}2. Script → auto-switch daemon for Steam games
~/.local/bin/niri-game-layout (chmod +x)
#!/usr/bin/env python3
import json
import subprocess
QWERTY = "1"
DVORAK = "0"
def switch_layout(index):
subprocess.run(["niri", "msg", "action", "switch-layout", index], capture_output=True)
if index == QWERTY:
subprocess.run(["setxkbmap", "us"], capture_output=True)
else:
subprocess.run(["setxkbmap", "us", "dvorak"], capture_output=True)
def is_steam_game(app_id):
return bool(app_id and app_id.startswith("steam_app_"))
def main():
steam_windows = set()
proc = subprocess.Popen(
["niri", "msg", "-j", "event-stream"],
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True,
)
try:
for line in proc.stdout:
line = line.strip()
if not line:
continue
try:
event = json.loads(line)
except json.JSONDecodeError:
continue
was_gaming = bool(steam_windows)
if "WindowsChanged" in event:
steam_windows = {
w["id"] for w in event["WindowsChanged"]["windows"]
if is_steam_game(w.get("app_id"))
}
elif "WindowOpenedOrChanged" in event:
w = event["WindowOpenedOrChanged"]["window"]
if is_steam_game(w.get("app_id")):
steam_windows.add(w["id"])
elif "WindowClosed" in event:
steam_windows.discard(event["WindowClosed"]["id"])
is_gaming = bool(steam_windows)
if is_gaming and not was_gaming:
switch_layout(QWERTY)
elif not is_gaming and was_gaming:
switch_layout(DVORAK)
except KeyboardInterrupt:
pass
finally:
proc.terminate()
if steam_windows:
switch_layout(DVORAK)
if __name__ == "__main__":
main()3. Script → manual toggle keybind
In case the auto-switch doesn’t work:
~/.local/bin/niri-toggle-layout (chmod +x)
#!/bin/sh
current=$(niri.msg -j keyboard-layouts | python3 -c "import json,sys; print(json.load(sys.stdin)['current_idk'])")
if [ "$current" = "0" ]; then
niri msg action switch-layout 1
setxkbmap us
else
niri msg action switch-layout 0
setxkbmap us dvorak
fi
4. Niri config → autostart the daemon
In ~/.config/niri/cfg/autostart.kdl (or equivalent):
spawn-sh-at-startup "/home/USERNAME/.local/bin/niri-game-layout &"5. Niri config → keybind for manual toggle
In ~/.config/niri/cfg/keybinds.kdl:
Mod+F10 { spawn-sh "/home/USERNAME/.local/bin/niri-toggle-layout"; }Must use a function key → letter-key binds are keysym-based, so the physical key changes between layouts and the bind stops working in QWERTY mode. F-keys are layout-independent.
6. Fish function → for non-Steam games (optional)
~/.config/fish/functions/game.fish
function game --description "Launch a program with US QWERTY layout, restoring Dvorak on exit"
niri msg action switch-layout 1
$argv
niri msg action switch-layout 0
endUsage: game ./some-game or game lutris