From Bloated to Beautiful: X11 Keyboard Layout Detection in Bunsenlabs
Introduction
After years of running Ubuntu with GNOME, my system was starting to feel sluggish and bloated. Rather than retiring my hardware, I decided to give it a second life by switching to BunsenLabs with OpenBox. This lightweight setup breathed new life into my machine, but it came with its own set of challenges - one of them being reliable keyboard layout detection.
The Challenge
The traditional methods of detecting keyboard layouts in Linux (setxkbmap -query, xset -q) proved unreliable - they would show the configured layouts but wouldn’t reflect real-time changes. This became particularly frustrating when trying to display the current layout in tint2’s executor.
The Solution: Going Direct with X11
After much experimentation, the solution emerged: bypass the high-level commands and talk directly to X11 using Python’s ctypes library. Here’s the complete script:
#!/usr/bin/env python3
import ctypes
import ctypes.util
import time
import sys
import os
import signal
import subprocess
# Static layouts list
layouts = ['us', 'de', 'il']
def get_layout():
"""Single-shot layout check"""
display = None
try:
x11 = ctypes.cdll.LoadLibrary(ctypes.util.find_library('X11'))
class XkbStateRec(ctypes.Structure):
_fields_ = [('group', ctypes.c_ubyte)] + [(f,ctypes.c_ubyte) for f in 'abcdefghijklm'] + [('n',ctypes.c_ushort)]
display = x11.XOpenDisplay(None)
if not display:
return None
state = XkbStateRec()
if x11.XkbGetState(display, 0x100, ctypes.byref(state)):
return None
group = state.group
return layouts[group] if group < len(layouts) else layouts[0]
finally:
if display:
try:
x11.XCloseDisplay(display)
except:
pass
def monitor():
"""Single process monitoring loop"""
last_layout = None
while True:
try:
current = get_layout()
if current and current != last_layout:
print(current, flush=True)
last_layout = current
time.sleep(0.1)
except:
time.sleep(0.1)
def restart_on_crash():
"""Main process that restarts monitor on crash"""
while True:
try:
p = subprocess.Popen([sys.executable, __file__, '--monitor'])
p.wait()
time.sleep(0.5)
except KeyboardInterrupt:
if p:
p.terminate()
sys.exit(0)
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == '--monitor':
monitor()
else:
restart_on_crash()
How It Works
- Direct X11 Communication: Instead of relying on system commands, we use Python’s ctypes to communicate directly with the X11 library.
- State Structure: We define an XkbStateRec structure that matches X11’s internal representation of keyboard state.
- Real-time Detection: By querying the X11 server directly, we get the actual current keyboard group number.
- Layout Mapping: We map the group number to our configured layouts array to get the current layout code.
Integration with tint2
Add this to your tint2rc to display the current layout:
execp = new
execp_command = /path/to/your/keyboard-layout-script
execp_interval = 1
execp_has_icon = 0
execp_cache_icon = 0
execp_continuous = 0
execp_markup = 1
execp_tooltip = Current Keyboard Layout
execp_lclick_command =
The script runs every second, immediately reflecting any layout changes in your tint2 panel.
Why This Works Better
Our solution works because:
- It queries the X server’s state directly instead of configuration
- Uses a supervisor pattern to ensure continuous operation
- Maintains clean resource management
- Recovers automatically from X11 connection issues
- Updates instantaneously with layout changes
The Journey to Stability
Getting to this stable version wasn’t straightforward. Early versions would occasionally crash with segmentation faults, especially during window focus changes. The breakthrough came with implementing the supervisor pattern - even if the X11 connection becomes invalid, the supervisor ensures the service continues running.
Conclusion
This lightweight solution perfectly complements the BunsenLabs philosophy - it’s efficient, reliable, and gets the job done without unnecessary overhead. Whether you’re using it with tint2 or another panel, you now have a reliable way to display your current keyboard layout.
Remember, sometimes the best solutions come from going back to basics and talking directly to the underlying system. In this case, that meant bypassing the usual command-line tools and communicating directly with X11.
