Switching kayboard layout with one key

Is it possible to turn on particular keyboard layout with just one key like Control_L or Shift_L without losing their main function?
I’ve tried to make custom keybinding for command, for example, setxkbmap -layout en and edit it via dconf-editor binding it to just Control_L. The layout switches on, but usual shortcuts with Control_L stop working.

Asked By: tzdm

||

Here is a Python script to do that. You need sudo apt install python3-xlib.

#!/usr/bin/env python3
# Based off https://stackoverflow.com/a/22368676/10477326
from Xlib.display import Display
from Xlib import X
from Xlib.ext import record
from Xlib.protocol import rq
import subprocess

TARGET_KEY = 37 # CTRL
target_pressed = False
disp = None

def handler(reply):
    global target_pressed
    data = reply.data
    # Listen for all keys
    while len(data):
        event, data = rq.EventField(None).parse_binary_value(data, disp.display, None, None)
        typ = event.type
        if typ not in (X.KeyPress, X.KeyRelease):
            # Interrupt on mouse Ctrl+Zoom or Ctrl+Click. Thank you @terdon
            target_pressed = False
            continue
        detail = event.detail
        # If there was another key in the middle,
        # stop because it's another key combination and not just
        # the one key that we desire.
        if detail != TARGET_KEY:
            target_pressed = False
            continue
        # On KeyPress, set a flag to wait for a KeyRelease to make sure
        # no other keys will be pressed.
        if typ == X.KeyPress:
            target_pressed = True
            continue
        # Previously another key in the middle was detected.
        if not target_pressed:
            continue
        # Now we know only the TARGET_KEY was pressed and released without any other keys.
        # Run your command
        subprocess.run(["setxkbmap", "-layout", "en"])
        target_pressed = False

disp = Display()
root = disp.screen().root
ctx = disp.record_create_context(
    0,
    [record.AllClients],
    [{
        'core_requests': (0, 0),
        'core_replies': (0, 0),
        'ext_requests': (0, 0, 0, 0),
        'ext_replies': (0, 0, 0, 0),
        'delivered_events': (0, 0),
        'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask),
        'errors': (0, 0),
        'client_started': False,
        'client_died': False,
    }],
)
disp.record_enable_context(ctx, handler)
disp.record_free_context(ctx)

while True:
    event = root.display.next_event()

You can then mark it as executable and add a link to it in startup applications. The main idea is to listen for all keypresses globally on all windows. Then I listen for any other keypresses in between to know if you want to switch the layout or use another shortcut. The target key is still sent to the original window all the time.

Answered By: Daniel T