Add keyboard layout management to the bar picker - #11681
MadMatt341 wants to merge 2 commits into
Conversation
Automated AI review
Outcome: Selection by sysfs Bluetooth and remapper keyboards are dropped
Suggested change: identify each such interface by name and let the user choose. Reproducer: Bluetooth keyboard (fails at 16b1317)Bluetooth keyboard dropped when a Bluetooth mouse is connectedLinux sets the input device Save the file below as OMARCHY_PATH=$(git rev-parse --show-toplevel) PYTHONPATH=$(git rev-parse --show-toplevel)/shell/plugins/bar/widgets/keyboard python3 -B -m unittest -v test_repro_bluetooth"""A Bluetooth keyboard is dropped when a Bluetooth mouse is connected.
Linux reports the local Bluetooth adapter address as `phys` for every
Bluetooth HID input device (net/bluetooth/hidp/core.c, and BlueZ
src/shared/uhid.c for BLE), so a keyboard and a mouse on the same adapter
share one `phys` value with no `/inputN` suffix.
"""
from pathlib import Path
import tempfile
import unittest
from backend.devices import metadata, resolve
from test_backend import keyboard
def bitmap(bits):
value = sum(1 << bit for bit in bits)
words = []
while value:
words.append('%x' % (value & (2 ** 64 - 1)))
value >>= 64
return ' '.join(reversed(words)) or '0'
def input_device(root, event, name, phys, keys, rel=()):
base = Path(root) / event / 'device'
(base / 'capabilities').mkdir(parents=True)
(base / 'name').write_text(name + '\n')
(base / 'phys').write_text(phys + '\n')
(base / 'capabilities/key').write_text(bitmap(keys) + '\n')
(base / 'capabilities/rel').write_text(bitmap(rel) + '\n')
KEYBOARD_KEYS = list(range(1, 89))
MOUSE_KEYS, MOUSE_REL = [272, 273, 274], [0, 1, 8]
class BluetoothKeyboardTests(unittest.TestCase):
def groups(self, keyboard_phys, mouse_phys):
with tempfile.TemporaryDirectory() as folder:
input_device(folder, 'event5', 'Keychron K2', keyboard_phys, KEYBOARD_KEYS)
if mouse_phys:
input_device(folder, 'event6', 'MX Master 3', mouse_phys, MOUSE_KEYS, MOUSE_REL)
records = metadata(Path(folder))
return resolve({'keyboards': [keyboard('keychron-k2')]}, records)
def test_bluetooth_keyboard_next_to_a_bluetooth_mouse_is_offered(self):
adapter = 'aa:bb:cc:dd:ee:ff'
groups, excluded = self.groups(adapter, adapter)
self.assertEqual([group['names'] for group in groups], [['keychron-k2']], f'excluded: {excluded}')
def test_control_bluetooth_keyboard_alone(self):
groups, _ = self.groups('aa:bb:cc:dd:ee:ff', None)
self.assertEqual([group['names'] for group in groups], [['keychron-k2']])
def test_control_usb_keyboard_and_usb_mouse(self):
groups, _ = self.groups('usb-0000:00:14.0-2/input0', 'usb-0000:00:14.0-3/input0')
self.assertEqual([group['names'] for group in groups], [['keychron-k2']])
if __name__ == '__main__':
unittest.main()Actual output at the PR head (checkout copied to /tmp/omarchy, OMARCHY_PATH set to it): Reproducer: remapper keyboard (fails at 16b1317)Typing keyboards with an empty sysfs
|
Add layout management to the existing keyboard indicator: clicking opens a picker, and Edit layouts… adds/removes up to four layouts, searches variants, chooses the next-login default and configures the switching shortcut. The existing widget ID and bar position stay in use. The indicator remains visible with one layout so users can add another.
This is a focused keyboard contribution related to #7252. It adapts the working Keyboard Layouts community plugin to first-party ownership. The picker and backend could also be used by the broader Input panel; this PR makes that integration available for review without adding pointer settings or a second bar entry.
The backend preserves Compose/Caps Lock and other unrelated XKB options, validates characters and shortcut chords with libxkbcommon, verifies physical typing interfaces and rolls back failed edits. Ordinary switching preserves the login default. A fixed package-owned Lua loader reads bounded, non-executable state through a supervised Python helper. Saves and recovery never rewrite the loader, helper or user
input.lua. No pip dependencies, raw-input collection orhyprctl eval hl.devicecalls.The native version requires no plugin activation command. It starts with no saved overrides, uses a separate state directory, and refuses editing while the community plugin's loader is present. Its existing cleanup procedure remains the transition path; automatic profile migration is outside this proposal. Source and test details are in
docs/keyboard-layouts.md; the reused code retains its MIT notice.Preview
Fixture rendering of the picker, editor and variant search; this is not a live desktop capture.
Actual installed desktop, captured during disposable-VM validation:
Verification
91 isolated Python tests, including real XKB compilation, native first-save/rollback, loader and next-session promotion, file ownership, stale revisions and supervised-process cleanup.
40 offscreen native UI tests; rendered picker/editor/search/ambiguity states inspected. The entry-point wiring test substitutes the layer-shell container because offscreen Qt has no PanelWindow backend.
Omarchy plugin clone and manifest checks; Hyprland keyboard-layout and default-config checks.
Fresh ISO built from the source and installed in an encrypted KVM guest. A clean keyboard run passed first-save UI navigation/search, switching, login-default selection, Polish AltGr/Shift+AltGr character entry, both Alt press orders, and persistence across reboot, with the user's
input.luaunchanged.Active-layout removal passed, including safe duplicate groups for the current session and promotion to a true single group after another reboot. Actual desktop captures were inspected.
The broader desktop suite had two unrelated OCR failures (weather location and reminder prompt); both texts were visible in the captured UI. Its remaining test files and QMP shortcut smoke checks passed. The ISO harness needed an installer-greeting matcher update, and an initial unencrypted install failed its second boot; the keyboard results above use a fresh encrypted installation. Exact source/ISO identifiers and the verification sequence are in
docs/keyboard-layouts.md.Ready for review, including maintainer direction on the relationship to #7252. Typing was validated through QMP virtual-hardware input on one emulated AT keyboard; multiple physical keyboards and hardware hotplug remain outside the VM evidence. The upstream adaptation has not been installed on the development desktop.