From e232c99ffdbe3fb5517c1f89882cb29339b9b189 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 26 Jul 2026 18:22:16 -0700 Subject: [PATCH] Only lead with us when the layout can't type Latin letters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepending us whenever the layout wasn't already us would have demoted every Latin layout — de, fr, dk — to a secondary group, so those users would have booted into a US layout they never asked for. Latin layouts resolve the default keysym bindings fine, so only the layouts that can't type Latin letters need us in front. That list already exists for the initramfs hook, and a test keeps the two copies in sync. Also match on the first layout rather than looking for us anywhere in the list: Hyprland resolves bindings against the leading entry, so "il,us" needed the fix just as much as "il" did. Co-Authored-By: Claude Opus 5 (1M context) --- default/hypr/input.lua | 26 ++++-- test/shell.d/hyprland-keyboard-layout-test.sh | 85 +++++++++++++++++++ 2 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 test/shell.d/hyprland-keyboard-layout-test.sh diff --git a/default/hypr/input.lua b/default/hypr/input.lua index 6fddb9e0..c3c7d43f 100644 --- a/default/hypr/input.lua +++ b/default/hypr/input.lua @@ -21,24 +21,32 @@ local function read_vconsole() return values end +-- Layouts that can't type Latin letters. Keep in sync with the list in +-- etc/mkinitcpio.conf.d/omarchy_hooks.conf. +local non_latin_layouts = + " af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua " + local vconsole = read_vconsole() -local layout = vconsole.XKBLAYOUT or "us" -local variant = vconsole.XKBVARIANT or "" +local kb_layout = vconsole.XKBLAYOUT or "us" +local kb_variant = vconsole.XKBVARIANT or "" local kb_options = "compose:caps,shift:both_capslock" --- Prepend 'us' so Omarchy's Latin-keysym bindings resolve — Hyprland matches --- bindings against the first layout in kb_layout, not the currently active one. -if not (","..layout..","):find(",us,", 1, true) then - layout = "us," .. layout - variant = "," .. variant +-- 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" end hl.config({ input = { - kb_layout = layout, - kb_variant = variant, + kb_layout = kb_layout, + kb_variant = kb_variant, kb_model = "", kb_options = kb_options, kb_rules = "", diff --git a/test/shell.d/hyprland-keyboard-layout-test.sh b/test/shell.d/hyprland-keyboard-layout-test.sh new file mode 100644 index 00000000..68626cc8 --- /dev/null +++ b/test/shell.d/hyprland-keyboard-layout-test.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" + +require_command lua + +resolved_input() { + OMARCHY_PATH="$ROOT" OMARCHY_VCONSOLE="${1-}" lua <<'LUA' +package.path = os.getenv("OMARCHY_PATH") .. "/?.lua;" .. package.path + +local vconsole = os.getenv("OMARCHY_VCONSOLE") +local real_open = io.open + +io.open = function(path, mode) + if path ~= "/etc/vconsole.conf" then + return real_open(path, mode) + end + + if not vconsole then + return nil + end + + local file = io.tmpfile() + file:write(vconsole) + file:seek("set") + return file +end + +hl = { + config = function(config) + local input = config.input + print(("[%s] [%s] [%s]"):format(input.kb_layout, input.kb_variant, input.kb_options)) + end, +} + +o = { window = function() end } + +require("default.hypr.input") +LUA +} + +assert_input() { + local description="$1" + local expected="$2" + local actual + + if (( $# > 2 )); then + actual=$(resolved_input "$3") + else + actual=$(resolved_input) + fi + + [[ $actual == "$expected" ]] || + fail "$description" "expected: $expected"$'\n'"actual: $actual" + pass "$description" +} + +base_options="compose:caps,shift:both_capslock" +toggle_options="$base_options,grp:alts_toggle" + +assert_input "missing vconsole.conf falls back to us" "[us] [] [$base_options]" +assert_input "us layout passes through" "[us] [intl] [$base_options]" 'XKBLAYOUT=us +XKBVARIANT=intl +' +assert_input "latin layouts are left alone" "[de] [nodeadkeys] [$base_options]" 'XKBLAYOUT=de +XKBVARIANT=nodeadkeys +' +assert_input "non-latin layout gains us in front" "[us,ara] [,] [$toggle_options]" 'XKBLAYOUT=ara +' +assert_input "prepended us keeps variants aligned" "[us,ru] [,phonetic] [$toggle_options]" 'XKBLAYOUT=ru +XKBVARIANT=phonetic +' +assert_input "non-latin layout in front gains us even when us trails" "[us,il,us] [,] [$toggle_options]" 'XKBLAYOUT=il,us +' + +hooks_conf="$ROOT/etc/mkinitcpio.conf.d/omarchy_hooks.conf" +input_lua="$ROOT/default/hypr/input.lua" + +hooks_layouts=$(awk -F')' '/\) ;;$/ { gsub(/[[:space:]|]+/, "\n", $1); print $1 }' "$hooks_conf" | grep '^[a-z]\+$' | sort) +lua_layouts=$(sed -n '/^local non_latin_layouts =/,+1p' "$input_lua" | grep -o '"[^"]*"' | tr -d '"' | tr ' ' '\n' | grep '^[a-z]\+$' | sort) + +[[ -n $hooks_layouts ]] || fail "non-latin layout list is readable from omarchy_hooks.conf" +[[ $hooks_layouts == "$lua_layouts" ]] || + fail "non-latin layout lists stay in sync" "$(diff <(echo "$hooks_layouts") <(echo "$lua_layouts"))" +pass "non-latin layout lists stay in sync with the initramfs hook"