From 9d61915b2e2e1d75b58ef9428006d7a13bd65659 Mon Sep 17 00:00:00 2001 From: Artem Popov Date: Sun, 9 Aug 2026 15:11:26 +0200 Subject: [PATCH] Fix KeyboardLayout plugin label permanently displaying ENG on some systems (#6646) * Fix hyprctl output parsing in KeyboardLayout plugin * Never fall back to a non-active keyboard for the layout label find(k => k.main) returning nothing fell through to keyboards[0], which is the case the fix is for: on hardware whose first device is a permanently English (US) radio-control keyboard, the label was wrong and the 10s poll kept it wrong. The seat can also hold no active keyboard while a device is re-added, and older Hyprland has no main field at all. Keep the last known value instead, and skip entries without an active_keymap, since assigning undefined to the string property throws before the label is ever set. Co-Authored-By: Claude Opus 5 (1M context) * Run hyprctl directly from the keyboard layout widget The shell wrapper only existed for a pipeline that is gone, so spawn the command directly, as Style.qml already does for its own hyprctl query. Co-Authored-By: Claude Opus 5 (1M context) * Read the layout from the keyboard the user types on Every Omarchy install runs fcitx5 for ~/.XCompose, and it binds a virtual keyboard that takes the seat's main flag whenever it injects. That keyboard keeps the us layout the input method gave it, so on a machine configured for another layout the widget flipped to ENG and the poll kept it there until the next physical keypress. Skip virtual keyboards and hold the last known layout instead, which the next poll corrects once a real keyboard is active again. Co-Authored-By: Claude Opus 5 (1M context) * Keep tracking the keyboard the layout was last read from Holding a frozen label while fcitx5 owns the main flag went stale as soon as the layout changed underneath it, and cycling still dispatched against "current", which is that same virtual keyboard. Remember the keyboard the label came from, re-read its layout on every poll, and cycle it by name so the widget shows and switches one device. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: David Heinemeier Hansson Co-authored-by: Claude Opus 5 (1M context) --- shell/plugins/bar/widgets/KeyboardLayout.qml | 33 +++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/shell/plugins/bar/widgets/KeyboardLayout.qml b/shell/plugins/bar/widgets/KeyboardLayout.qml index 8d3fcbb0..979ff12b 100644 --- a/shell/plugins/bar/widgets/KeyboardLayout.qml +++ b/shell/plugins/bar/widgets/KeyboardLayout.qml @@ -12,13 +12,24 @@ BarWidget { property string layoutLabel: "" property string layoutFull: "" + property string keyboardName: "" function refresh() { if (!queryProc.running) queryProc.running = true } + // fcitx5 binds a virtual keyboard and takes over the seat's main flag whenever + // it injects, but that keyboard keeps the us layout the input method gave it. + // Stay on the keyboard we last read until a real one is active again, so the + // label keeps tracking that keyboard's layout rather than freezing. + function selectKeyboard(keyboards) { + const typed = keyboards.filter(k => !String(k.name).startsWith("hl-virtual-keyboard")) + return typed.find(k => k.main) ?? typed.find(k => k.name === root.keyboardName) + } + function cycleLayout() { - Hyprland.dispatch("switchxkblayout current next") + if (!root.keyboardName) return + Hyprland.dispatch("switchxkblayout " + root.keyboardName + " next") refreshTimer.restart() } @@ -34,16 +45,22 @@ BarWidget { Process { id: queryProc - command: ["bash", "-c", "hyprctl -j devices 2>/dev/null | sed -n '/keyboards/,$p' | head -200"] + command: ["hyprctl", "-j", "devices"] stdout: StdioCollector { waitForEnd: true onStreamFinished: { - var match = String(text || "").match(/"active_keymap":\s*"([^"]+)"/) - if (!match) return - var full = match[1] - root.layoutFull = full - var token = full.split(/\s+/)[0] - root.layoutLabel = token.substring(0, 3).toUpperCase() + let kb + try { + kb = root.selectKeyboard(JSON.parse(text || "{}").keyboards ?? []) + } catch (e) { + return + } + + if (!kb || !kb.active_keymap) return + + root.keyboardName = String(kb.name || "") + root.layoutFull = kb.active_keymap + root.layoutLabel = kb.active_keymap.split(/\s+/)[0].substring(0, 3).toUpperCase() } } }