Add omarchy keyboard layout bar widget - #6358
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “Keyboard Layout” bar widget/panel plugin for Omarchy, backed by a new CLI command and Hyprland input configuration that persists selected layouts and the layout-switch shortcut in a per-user state file.
Changes:
- Adds a new
omarchy.keyboardbar widget/panel UI with language switching, add/remove, and switcher preset selection. - Adds
bin/omarchy-keyboard-layoutto read/write keyboard layout state, query available XKB layouts, and drive Hyprland layout switching. - Updates Hyprland input configuration to read the keyboard layout state file and apply the selected switcher preset.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| shell/plugins/panels/keyboard/Panel.qml | New keyboard layout widget/panel UI and Hyprland event syncing. |
| shell/plugins/panels/keyboard/Model.js | Parsing/filtering helpers and switcher preset definitions for the panel. |
| shell/plugins/panels/keyboard/manifest.json | Declares the new omarchy.keyboard bar-widget plugin. |
| config/hypr/input.lua | Reads keyboard state to set kb_layout and kb_options switcher preset. |
| bin/omarchy-keyboard-layout | CLI for status/available/add/remove/set/next/switcher and state persistence. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| PanelKeyCatcher { | ||
| id: keyCatcher | ||
| anchors.fill: parent | ||
| onCloseRequested: { | ||
| if (root.viewMode === "add") root.closeAddView() | ||
| else root.close() | ||
| } | ||
|
|
| // The four switch-shortcut presets shown as pills in the panel. Keep this in | ||
| // sync with SWITCHER_OPTIONS in default/hypr/input.lua. |
|
|
||
| if (needle === "") return list | ||
|
|
||
| return list.sort(function(a, b) { return matchRank(a, needle) - matchRank(b, needle) }) |
|
i like this addition i spent alot of time trying to make something like this today |
Yeah, I think this is something that's genuinely needed. For anyone who regularly works with two keyboard layouts, having a clear indicator is almost essential. It makes switching between languages much more seamless and helps avoid a lot of unnecessary typing mistakes. |
|
Maybe this could be released as a plugin instead? It's now easy to put it in its own repo and people who use multiple keyboard layouts can install it with the |
Appreciate the idea! My concern is that as a standalone plugin, most people who'd actually want this simply won't come across it. I think it fits better in core, since it's something a lot of users would want available by default. On a related note, I also built a calendar for Omarchy — that one's available as a plugin. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (4)
shell/plugins/panels/keyboard/Panel.qml:204
- PanelKeyCatcher only handles close; none of the navigation/activation signals are wired (onMoveRequested/onActivateRequested/onTabRequested). As a result, arrow keys / hjkl / Enter/Space won’t drive the cursor model (focusSection/selectedIndex/cursorActive), which breaks the advertised full keyboard navigation.
PanelKeyCatcher {
id: keyCatcher
anchors.fill: parent
onCloseRequested: {
if (root.viewMode === "add") root.closeAddView()
shell/plugins/panels/keyboard/Panel.qml:151
- The status poll timer runs continuously even when the bar icon is hidden (barIconVisible=false), so the widget will keep spawning
omarchy-keyboard-layout statusin the background unnecessarily.
Timer {
interval: 3000
running: true
repeat: true
onTriggered: root.refresh()
shell/plugins/panels/keyboard/Model.js:2
- Model.js says the switcher presets must be kept in sync with SWITCHER_OPTIONS in default/hypr/input.lua, but that file doesn’t define SWITCHER_OPTIONS (the mapping is currently in config/hypr/input.lua). This comment will send future edits to the wrong file.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in default/hypr/input.lua.
config/hypr/input.lua:79
- config/hypr/input.lua is meant for personal overrides loaded after defaults, but this change re-specifies the full input + misc config (and hard-codes kb_variant to ""), which will override Omarchy defaults wholesale and block future default improvements from applying. If the goal is to let the keyboard panel control layouts/switcher, only override kb_layout/kb_options here and leave the rest to default/hypr/input.lua (and/or user overrides).
input = {
kb_layout = kb_layout,
kb_variant = "",
kb_model = "",
kb_options = kb_options,
| // Hyprland switches the layout itself for the native Alt+Shift/Ctrl+Shift/ | ||
| // etc. shortcut (see default/input.lua's kb_options grp:*_toggle) -- that | ||
| // path never goes through our `omarchy-keyboard-layout` CLI, so it doesn't | ||
| // update `root.status` on its own. Listen for Hyprland's own layout-change | ||
| // event and refresh from it so the bar icon stays in sync even when the | ||
| // switch happened outside the panel. A slow poll is kept as a fallback in |
| "id": "omarchy.keyboard", | ||
| "name": "Keyboard Layout", | ||
| "version": "1.0.0", | ||
| "author": "Omarchy", | ||
| "description": "Active keyboard language with add/remove and switch-shortcut picker", |
| "setup.monitors": {"icon":"","label":"Monitors","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""}, | ||
| "setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""}, | ||
| "setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""}, | ||
| "setup.keyboard-layout-icon": {"icon":"","label":"Keyboard Layout","action":"omarchy-keyboard-layout bar-icon toggle"}, |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
shell/plugins/panels/keyboard/Model.js:2
- This header comment points at
default/hypr/input.lua, but the switcher preset mapping lives inconfig/hypr/input.lua(SWITCHER_OPTIONS). As-is, the reference is misleading for future edits.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in default/hypr/input.lua.
|
|
||
| // Raw status from `omarchy-keyboard-layout status`: | ||
| // { layouts: [{code, label}], switcher, active, show_bar_icon } | ||
| property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: true }) |
| hl.config({ | ||
| input = { | ||
| kb_layout = kb_layout, | ||
| kb_variant = "", |
| -- dot while waiting for the next key. Bind Compose to the Menu key | ||
| -- instead: it's unused by every switcher preset above, so Caps Lock keeps | ||
| -- working normally and Compose sequences (e.g. Menu, ', e -> é) still work. | ||
| local kb_options = "compose:menu," .. grp_option |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
shell/plugins/panels/keyboard/Panel.qml:120
- Comment text is split mid-sentence ("this is a" on its own line), which reads like an accidental line break.
// Manual on/off switch for the bar icon, controlled from omarchy-menu
// (Trigger > Toggle > Show Keyboard Layout) via `omarchy-keyboard-layout
// bar-icon toggle`. Independent of the language-code icon above -- this
// is a
// deliberate user choice, not an automatic language-count heuristic.
shell/plugins/panels/keyboard/Panel.qml:138
- The comment references
default/input.lua, but that file doesn't exist in the repo; the kb_options live underdefault/hypr/input.lua/config/hypr/input.lua. This makes the reference hard to follow.
// Hyprland switches the layout itself for the native Alt+Shift/Ctrl+Shift/
// etc. shortcut (see default/input.lua's kb_options grp:*_toggle) -- that
// path never goes through our `omarchy-keyboard-layout` CLI, so it doesn't
// update `root.status` on its own. Listen for Hyprland's own layout-change
// event and refresh from it so the bar icon stays in sync even when the
// switch happened outside the panel. A slow poll is kept as a fallback in
// case an event is ever missed.
| "setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"omarchy-launch-config-editor \"$HOME/.config/hypr/input.lua\""}, | ||
| "setup.direct-boot": {"icon":"","label":"Direct Boot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-direct-boot"}, | ||
| "setup.keyboard-layout-icon": {"icon":"","label":"Keyboard Layout","action":"omarchy-keyboard-layout bar-icon toggle"}, | ||
| "setup.direct-boot": {"icon":"","label":"Direct Boot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-direct-boot"} |
| Timer { | ||
| interval: 3000 | ||
| running: true | ||
| repeat: true | ||
| onTriggered: root.refresh() | ||
| } |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
default/omarchy/omarchy-menu.jsonc:126
- Missing commas after the new Setup menu entries makes this JSONC invalid (the following keys won't parse correctly).
"setup.keyboard-layout-icon": {"icon":"","label":"Keyboard Layout","action":"omarchy-keyboard-layout bar-icon toggle"},
"setup.direct-boot": {"icon":"","label":"Direct Boot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-direct-boot"}
shell/plugins/panels/keyboard/Panel.qml:145
- The layout-change event handler (and the fallback polling Timer below) still refreshes every time even when the bar icon is disabled/hidden. That keeps spawning
omarchy-keyboard-layout statusin the background after the user explicitly hid the widget.
Connections {
target: Hyprland
function onRawEvent(event) {
if (!event || !event.name) return
if (String(event.name).indexOf("activelayout") !== -1) root.refresh()
}
}
shell/plugins/panels/keyboard/Model.js:115
- The sort comparator only compares match rank. When ranks tie, ordering becomes engine-dependent (JS sort stability isn’t guaranteed), which can cause the filtered list to reorder unexpectedly between refreshes.
return list.sort(function(a, b) { return matchRank(a, needle) - matchRank(b, needle) })
shell/plugins/panels/keyboard/Panel.qml:134
- Comment references
default/input.lua, but the referenced Hyprland defaults live underdefault/hypr/input.luain this repo. The incorrect path makes it harder to trace the behavior.
// Hyprland switches the layout itself for the native Alt+Shift/Ctrl+Shift/
// etc. shortcut (see default/input.lua's kb_options grp:*_toggle) -- that
// path never goes through our `omarchy-keyboard-layout` CLI, so it doesn't
| -- The keyboard panel (bin/omarchy-keyboard-layout) owns this file: it's the | ||
| -- list of layouts the user picked via "Add language" plus their chosen | ||
| -- switch shortcut. jq is already a hard dependency of the Omarchy CLI | ||
| -- scripts, so shelling out to it here is simpler and less error-prone than | ||
| -- hand-rolling JSON parsing in Lua. | ||
| local STATE_FILE = os.getenv("HOME") .. "/.local/state/omarchy/settings/keyboard.json" |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
default/omarchy/omarchy-menu.jsonc:127
- Missing trailing comma after the "setup.direct-boot" menu entry breaks the surrounding JSONC object (the next key starts immediately on the next line).
"setup.keyboard-layout-icon": {"icon":"","label":"Keyboard Layout","action":"omarchy-keyboard-layout bar-icon toggle"},
"setup.direct-boot": {"icon":"","label":"Direct Boot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-direct-boot"}
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
shell/plugins/panels/keyboard/Panel.qml:120
- This comment is broken across lines ("this is a" on its own) and references a menu path that doesn’t match the current omarchy-menu entry, making it confusing to maintain.
// Manual on/off switch for the bar icon, controlled from omarchy-menu
// (Trigger > Toggle > Show Keyboard Layout) via `omarchy-keyboard-layout
// bar-icon toggle`. Independent of the language-code icon above -- this
// is a
// deliberate user choice, not an automatic language-count heuristic.
shell/plugins/panels/keyboard/Panel.qml:130
- The widget refreshes its status on startup and on open even when the user has hidden the bar icon (barIconVisible=false). That still spawns subprocesses and polling work for an intentionally-disabled widget.
onOpenedChanged: if (opened) refresh()
Component.onCompleted: refresh()
shell/plugins/panels/keyboard/Panel.qml:151
- The fallback polling timer runs unconditionally every 3s, even when the bar icon is hidden. This keeps invoking the CLI in the background for a widget the user explicitly disabled.
Timer {
interval: 3000
running: true
repeat: true
onTriggered: root.refresh()
|
I haven't tried this first hand so apologies if I'm getting it wrong, but looking at screenshots, it feels odd. The bar should be really simple, just switching the language (maybe with an OSD showing the selected language when hitting the binding). Adding languages, selecting the keyboard bindings belong in the settings, since they don't change that often, they do not need to be so immediately accessible. Just my two cents. |
Thanks for the feedback! I'd actually lean toward keeping it as one panel. Switching is already the first thing you see (click any language or use arrow keys, no digging needed) — add/shortcut are secondary and further down, so they're not really competing for attention. Splitting them out would add a navigation step for something that's currently zero clicks away. Also tried to account for the single-language case — if someone only uses one language and doesn't want the icon taking up space in the bar, there's a toggle for that under Setup > Keyboard Layout. Happy to revisit if real usage shows otherwise. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (5)
shell/plugins/panels/keyboard/Panel.qml:18
- The initial
statusdefault setsshow_bar_icon: false, which hides the bar widget until the firstomarchy-keyboard-layout statusrefresh completes. Since the CLI defaultsshow_bar_iconto true (and Model.parseStatus also falls back to true), this causes an avoidable “missing icon” flicker on startup.
// Raw status from `omarchy-keyboard-layout status`:
// { layouts: [{code, label}], switcher, active, show_bar_icon }
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
property var available: []
shell/plugins/panels/keyboard/Panel.qml:220
- This comment points to a menu location that doesn’t match the actual default menu entry added in this PR (
setup.keyboard-layout-iconunder the Setup section). Keeping this accurate matters for future maintainers trying to trace the toggle.
// Manual on/off switch for the bar icon, controlled from omarchy-menu
// (Trigger > Toggle > Show Keyboard Layout) via `omarchy-keyboard-layout
// bar-icon toggle`. Independent of the language-code icon above -- this
// is a deliberate user choice, not an automatic language-count heuristic.
shell/plugins/panels/keyboard/Panel.qml:745
- The comment says there are four switch-shortcut presets, but the UI hard-codes a 3-column row (
count: 3) and Model.js defines 3 presets. This should be corrected to avoid confusion when changing presets later.
// One of the four switch-shortcut presets, same pill styling as the
// network panel's DNS provider picker.
shell/plugins/panels/keyboard/Model.js:7
- This header comment says there are four switch-shortcut presets, but
SWITCHERSdefines three entries. The comment should match the actual list (and the UI’s 3-column layout).
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
var SWITCHERS = [
{ id: "alt_shift", label: "Alt+Shift" },
{ id: "ctrl_shift", label: "Ctrl+Shift" },
{ id: "right_alt", label: "Right Alt" }
]
shell/plugins/panels/keyboard/manifest.json:7
- Other first-party panel manifests use a consistently-capitalized author string (e.g.
"author": "Omarchy"). This manifest uses"omarchy", which is inconsistent with the existing convention.
"name": "Keyboard Layout",
"version": "1.0.0",
"author": "omarchy",
"description": "Active keyboard language with add/remove and switch-shortcut picker",
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
shell/plugins/panels/keyboard/Panel.qml:17
- The widget defaults to show_bar_icon: false, which keeps it invisible until the first CLI refresh completes. Since the CLI defaults show_bar_icon to true when unset, the panel should match that default to avoid a startup flicker / missing widget.
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
shell/plugins/panels/keyboard/Panel.qml:745
- Comment says there are four switch-shortcut presets, but only three presets are defined/used. This can drift out of sync with the actual UI behavior and the Hyprland mapping.
// One of the four switch-shortcut presets, same pill styling as the
// network panel's DNS provider picker.
shell/plugins/panels/keyboard/Model.js:2
- Comment says there are four switcher presets, but SWITCHERS only defines three. This should be corrected to avoid misleading future edits (and to match default/hypr/input.lua's SWITCHER_OPTIONS).
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
shell/plugins/panels/keyboard/Model.js:135
- This comment still refers to four presets, but the implementation exposes three. Keeping the comment accurate helps prevent future UI/layout math from hard-coding the wrong count.
// The four presets, for the pill row -- a fresh copy each time so callers
// can't accidentally mutate the shared list.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
shell/plugins/panels/keyboard/Panel.qml:18
status.show_bar_icondefaults tofalse, which makes the widget start out invisible until the firstrefresh()completes. That can cause the bar widget to appear late (or never, if the initial status call fails). Defaulting totruematches the CLI/model behavior where a missingshow_bar_iconis treated as enabled.
// Raw status from `omarchy-keyboard-layout status`:
// { layouts: [{code, label}], switcher, active, show_bar_icon }
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
property var available: []
shell/plugins/panels/keyboard/Panel.qml:745
- Comment says there are "four" switch-shortcut presets, but
Model.switcherPresets()only defines three. This is likely to confuse anyone updating presets later.
// One of the four switch-shortcut presets, same pill styling as the
// network panel's DNS provider picker.
shell/plugins/panels/keyboard/Model.js:2
SWITCHERSdefines three presets, but the header comment says "four", and it points atconfig/hypr/input.luaeven thoughSWITCHER_OPTIONSlives indefault/hypr/input.luain this repo. Keeping these comments accurate helps prevent the presets from drifting.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
shell/plugins/panels/keyboard/Model.js:136
- This comment says "four presets" but
SWITCHERSonly has three entries. Keeping the comment accurate avoids confusion when adding/removing presets later.
// The four presets, for the pill row -- a fresh copy each time so callers
// can't accidentally mutate the shared list.
function switcherPresets() {
| -- The keyboard panel (bin/omarchy-keyboard-layout) owns this file: it's the | ||
| -- list of layouts the user picked via "Add language" plus their chosen | ||
| -- switch shortcut. jq is already a hard dependency of the Omarchy CLI | ||
| -- scripts, so shelling out to it here is simpler and less error-prone than | ||
| -- hand-rolling JSON parsing in Lua. | ||
| local STATE_FILE = os.getenv("HOME") .. "/.local/state/omarchy/settings/keyboard.json" | ||
|
|
||
| local vconsole = read_vconsole() | ||
| -- Runs a shell command and returns its trimmed stdout, or an empty string | ||
| -- if the command couldn't even be started. | ||
| local function popen_trim(cmd) | ||
| local handle = io.popen(cmd) | ||
| if not handle then | ||
| return "" | ||
| end | ||
| local result = handle:read("*a") or "" | ||
| handle:close() | ||
| return (result:gsub("^%s+", ""):gsub("%s+$", "")) | ||
| end | ||
|
|
||
| local kb_layout = vconsole.XKBLAYOUT or "us" | ||
| local kb_variant = vconsole.XKBVARIANT or "" | ||
| local kb_options = "compose:caps,shift:both_capslock" | ||
|
|
||
| -- Hyprland resolves keybindings against the first entry in kb_layout, not the | ||
| -- layout that's currently active, so Omarchy's Latin-keysym bindings (SUPER + W | ||
| -- and friends) only fire when a Latin layout leads. Installing with a non-Latin | ||
| -- one would otherwise leave the desktop unusable. | ||
| if non_latin_layouts:find(" " .. kb_layout:match("^[^,]*") .. " ", 1, true) then | ||
| kb_layout = "us," .. kb_layout | ||
| kb_variant = "," .. kb_variant | ||
| -- Reach the original layout with Left Alt + Right Alt. | ||
| kb_options = kb_options .. ",grp:alts_toggle" | ||
| -- Reads one field out of the keyboard state file via jq. Falls back | ||
| -- cleanly if the file is missing, unreadable, or the field isn't set -- | ||
| -- Hyprland should never fail to start just because this file is stale | ||
| -- or hasn't been created yet. | ||
| local function state_field(jq_filter, fallback) | ||
| local cmd = string.format("jq -r %s %q 2>/dev/null", jq_filter, STATE_FILE) | ||
| local value = popen_trim(cmd) | ||
| if value == "" or value == "null" then | ||
| return fallback | ||
| end | ||
| return value | ||
| end | ||
|
|
||
| local vconsole = read_vconsole() | ||
|
|
||
| local kb_layout = state_field("'[.layouts[].code] | join(\",\")'", vconsole.XKBLAYOUT or "us") | ||
| local switcher = state_field("'.switcher // \"alt_shift\"'", "alt_shift") | ||
|
|
||
| -- The three switch-shortcut presets offered by the keyboard panel's pill | ||
| -- row, mapped to the matching xkb grp option. | ||
| local SWITCHER_OPTIONS = { | ||
| alt_shift = "grp:alt_shift_toggle", | ||
| ctrl_shift = "grp:ctrl_shift_toggle", | ||
| right_alt = "grp:toggle", | ||
| } | ||
|
|
||
| local grp_option = SWITCHER_OPTIONS[switcher] or SWITCHER_OPTIONS.alt_shift | ||
|
|
30b168c to
29fe044
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
shell/plugins/panels/keyboard/Panel.qml:17
- The initial default status sets show_bar_icon:false, which hides the bar widget until the first successful CLI status refresh. Since the CLI defaults show_bar_icon to true when unset, initialize it to true to avoid a missing icon flicker on startup.
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
shell/plugins/panels/keyboard/Model.js:2
- The comment says there are four switch-shortcut presets, but SWITCHERS only defines three. This is misleading for future maintenance.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
shell/plugins/panels/keyboard/Model.js:135
- This comment also says “four presets” but the code defines three. Update the wording to avoid confusion.
// The four presets, for the pill row -- a fresh copy each time so callers
// can't accidentally mutate the shared list.
shell/plugins/panels/keyboard/manifest.json:18
- The repo already ships a keyboard layout bar widget plugin with id
omarchy.keyboard-layout(shell/plugins/bar/widgets/KeyboardLayout.manifest.json). Adding a second widget id (omarchy.keyboard) risks confusing users (two similar widgets, and the new menu toggle only affects the new one). Consider either replacing/migrating the existing widget, or clearly differentiating/aliasing them so there’s a single obvious keyboard-layout widget.
"id": "omarchy.keyboard",
"name": "Keyboard Layout",
"version": "1.0.0",
"author": "omarchy",
"description": "Active keyboard language with add/remove and switch-shortcut picker",
"kinds": [
"bar-widget"
],
"entryPoints": {
"barWidget": "Panel.qml"
},
"barWidget": {
"displayName": "Keyboard Layout",
"description": "Active keyboard language with add/remove and switch-shortcut picker",
"category": "System",
"allowMultiple": false
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
shell/plugins/panels/keyboard/Panel.qml:18
- The panel starts with
show_bar_icon: false, which makes the widget invisible (width/height 0) until the first successfulstatusrefresh. That can cause the bar to reflow a moment after startup. Defaulting to visible (and letting the state file/CLI override it) avoids the initial layout shift.
// Raw status from `omarchy-keyboard-layout status`:
// { layouts: [{code, label}], switcher, active, show_bar_icon }
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
property var available: []
shell/plugins/panels/keyboard/Model.js:7
- Comments refer to “four” switch-shortcut presets, but the code defines three (
alt_shift,ctrl_shift,right_alt). This is misleading for future maintenance (and contradicts the UI). Update the comments to match the actual preset count.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
var SWITCHERS = [
{ id: "alt_shift", label: "Alt+Shift" },
{ id: "ctrl_shift", label: "Ctrl+Shift" },
{ id: "right_alt", label: "Right Alt" }
]
default/hypr/input.lua:65
- This removes the previous non‑Latin layout guard. If the first configured layout is non‑Latin, Hyprland resolves Omarchy’s keybindings against that first layout and Latin-keysym bindings can stop working (the existing
test/shell.d/hyprland-keyboard-layout-test.shasserts the prepend behavior). Reintroduce the non‑Latin list and prependus,(and a leading variant comma) when needed.
local kb_layout = state_field("'[.layouts[].code] | join(\",\")'", vconsole.XKBLAYOUT or "us")
local switcher = state_field("'.switcher // \"alt_shift\"'", "alt_shift")
-- The three switch-shortcut presets offered by the keyboard panel's pill
-- row, mapped to the matching xkb grp option.
default/hypr/input.lua:88
- After reintroducing the non‑Latin prepend logic,
kb_variantneeds to use the computedkb_variantvalue (so variants stay aligned when a leadingus,is inserted). Right now it always usesvconsole.XKBVARIANTdirectly, which will break variant alignment for the prepended layout and fails the existing keyboard-layout test expectations.
-- Falls back to "" (xkb's default variant) if the system's vconsole.conf
-- never set one, or the user hasn't customized their keyboard variant.
kb_variant = vconsole.XKBVARIANT or "",
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (6)
default/hypr/input.lua:72
- default input.lua no longer implements the non‑Latin layout safety behavior (prepending
us+ aligning variants) that existing shell tests assert and that prevents Hyprland keybindings from becoming unusable when the first layout is non‑Latin (seetest/shell.d/hyprland-keyboard-layout-test.sh). The test suite also expects the non_latin_layouts list to remain in sync withetc/mkinitcpio.conf.d/omarchy_hooks.conf, but the list/logic has been removed here.
local kb_layout = state_field("'[.layouts[].code] | join(\",\")'", vconsole.XKBLAYOUT or "us")
local switcher = state_field("'.switcher // \"alt_shift\"'", "alt_shift")
-- The three switch-shortcut presets offered by the keyboard panel's pill
-- row, mapped to the matching xkb grp option.
local SWITCHER_OPTIONS = {
alt_shift = "grp:alt_shift_toggle",
ctrl_shift = "grp:ctrl_shift_toggle",
right_alt = "grp:toggle",
}
local grp_option = SWITCHER_OPTIONS[switcher] or SWITCHER_OPTIONS.alt_shift
shell/plugins/panels/keyboard/Panel.qml:17
- The panel’s initial
statussetsshow_bar_icon: false, which makes the widget start hidden. If the firststatuscall fails (e.g. missing/errored CLI), the widget can remain permanently hidden with no way to recover from the UI. Defaulting to visible is safer and matchesModel.parseStatus/CLI defaults.
property var status: ({ layouts: [], switcher: "alt_shift", active: "", show_bar_icon: false })
shell/plugins/panels/keyboard/Panel.qml:232
- This comment references an omarchy-menu path ("Trigger > Toggle > Show Keyboard Layout") that doesn’t match the actual menu entry added in this PR (it’s under
setup.keyboard-layout-icon). Keeping this accurate matters because it’s effectively user-facing documentation for how the state is controlled.
// Manual on/off switch for the bar icon, controlled from omarchy-menu
// (Trigger > Toggle > Show Keyboard Layout) via `omarchy-keyboard-layout
// bar-icon toggle`. Independent of the language-code icon above -- this
// is a deliberate user choice, not an automatic language-count heuristic.
readonly property bool barIconVisible: status.show_bar_icon !== false
shell/plugins/panels/keyboard/Model.js:2
- The header comment says there are "four" switch-shortcut presets, but the list currently defines three. This should match to avoid confusion when updating presets alongside
default/hypr/input.lua.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
shell/plugins/panels/keyboard/Model.js:126
- Indentation inside the
filterAvailable()sort callback is inconsistent with the 2-space indentation used elsewhere in the repo’s panel Model.js files (for exampleshell/plugins/panels/network/Model.js).
return list.sort(function(a, b) {
var ra = matchRank(a, needle)
var rb = matchRank(b, needle)
if (ra !== rb) return ra - rb
var la = String(a && a.label || "")
shell/plugins/panels/keyboard/manifest.json:7
- Panel manifests in
shell/plugins/panels/*/manifest.jsonconsistently use"author": "Omarchy". This new manifest uses a different casing ("omarchy"), which is inconsistent in listings.
"version": "1.0.0",
"author": "omarchy",
"description": "Active keyboard language with add/remove and switch-shortcut picker",
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
shell/plugins/panels/keyboard/Panel.qml:55
Model.availableScales(...)is called here, but the newshell/plugins/panels/keyboard/Model.jsdoes not defineavailableScales(it contains keyboard-layout helpers). This will throw at QML load time. Either import the correct model for this Panel.qml or implement/rename the functions so Panel.qml and Model.js match.
readonly property var scalePresets: ["1", "1.25", "1.6", "2", "3", "4"]
readonly property var scaleValues: {
for (var i = 0; i < displays.length; i++) {
var display = displays[i]
if (display && display.focused)
return Model.availableScales(scalePresets, display.width, display.height)
}
return scalePresets
shell/plugins/panels/keyboard/Model.js:2
- The header comment is incorrect: there are 3 switch-shortcut presets (not 4), and
SWITCHER_OPTIONSis defined indefault/hypr/input.lua(notconfig/hypr/input.lua). Keeping this accurate helps prevent the panel and Hyprland defaults drifting out of sync.
// The four switch-shortcut presets shown as pills in the panel. Keep this in
// sync with SWITCHER_OPTIONS in config/hypr/input.lua.
| Panel { | ||
| id: root | ||
| moduleName: "omarchy.monitor" | ||
| ipcTarget: "omarchy.monitor" | ||
| manageIpc: false |
Omarchy Keyboard Layout
A keyboard layout bar widget for Omarchy. Shows your active
keyboard language in the bar, and gives you a clean panel to add,
remove, and switch between languages — plus configure the shortcut
used to cycle between them, all without leaving the keyboard.
Features
at a glance (e.g.
EN,FA,DE) — even with only one languageconfigured, so you always know what layout is active without
opening the panel.
language, or right-click the bar icon to instantly cycle to the
next one.
keyboard layouts — type to filter by name or code.
layout always kept safe from accidental removal.
Alt+ShiftCtrl+ShiftRight Altkeyboard shortcut directly, the bar updates automatically.
only ever uses one language and doesn't want the icon sitting in
the bar. Toggle it off any time, and back on the same way whenever
you want it back. An OSD notification confirms the new state.
How it works
The widget is made up of three parts that work together:
bin/omarchy-keyboard-layoutinput/input.luakeyboard/(Panel.qml, Model.js, manifest.json)Usage
Click the keyboard icon in the bar to open the panel:
+toadd a new one from the full list.
keyboard shortcut cycles your layouts.
configured language.
Handy if you only work in one language and prefer a cleaner bar;
turning it back on brings the icon right back.
opensubcommand, which checks show_bar_icon itself so the panel only
opens when its bar icon is enabled/visible.
Test
screenrecording-2026-07-26_15-41-21.mp4