diff --git a/default/sddm/hyprland.lua b/default/sddm/hyprland.lua index 977ae17e..42c19bcc 100644 --- a/default/sddm/hyprland.lua +++ b/default/sddm/hyprland.lua @@ -1,6 +1,55 @@ -- Minimal Hyprland config for the SDDM Wayland greeter. -- SDDM starts the greeter itself after the compositor is ready. + +-- The layout resolution below is deliberately duplicated from +-- default/hypr/input.lua. The greeter runs as the sddm user and starts from +-- this file directly, so it never goes through default/hypr/bootstrap.lua -- +-- which builds package.path from $HOME and therefore can't set up require() +-- here. Keep the two in sync. +local function read_vconsole() + local values = {} + local file = io.open("/etc/vconsole.conf", "r") + if not file then + return values + end + for line in file:lines() do + local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$") + if key and value then + value = value:gsub("%s+#.*$", "") + value = value:gsub('^"(.*)"$', "%1") + value = value:gsub("^'(.*)'$", "%1") + values[key] = value + end + end + file:close() + return values +end + +-- Layouts that can't type Latin letters. Keep in sync with the list in +-- etc/mkinitcpio.conf.d/omarchy_hooks.conf and default/hypr/input.lua. +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 kb_layout = vconsole.XKBLAYOUT or "us" +local kb_variant = vconsole.XKBVARIANT or "" +local kb_options = "" + +-- A greeter that can't type Latin letters can't take a password either, so a +-- non-Latin layout gets us in front, reachable back with Left Alt + Right Alt. +if non_latin_layouts:find(" " .. kb_layout:match("^[^,]*") .. " ", 1, true) then + kb_layout = "us," .. kb_layout + kb_variant = "," .. kb_variant + kb_options = "grp:alts_toggle" +end + hl.config({ + input = { + kb_layout = kb_layout, + kb_variant = kb_variant, + kb_options = kb_options, + }, + misc = { disable_hyprland_logo = true, disable_splash_rendering = true, diff --git a/test/shell.d/hyprland-keyboard-layout-test.sh b/test/shell.d/hyprland-keyboard-layout-test.sh index 8ed6fe38..cd2bc5e2 100644 --- a/test/shell.d/hyprland-keyboard-layout-test.sh +++ b/test/shell.d/hyprland-keyboard-layout-test.sh @@ -4,8 +4,9 @@ source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh" require_command lua -resolved_input() { - OMARCHY_PATH="$ROOT" OMARCHY_VCONSOLE="${1-}" lua <<'LUA' +resolved_input_for() { + local module="$1" + OMARCHY_PATH="$ROOT" OMARCHY_MODULE="$module" OMARCHY_VCONSOLE="${2-}" lua <<'LUA' package.path = os.getenv("OMARCHY_PATH") .. "/?.lua;" .. package.path local vconsole = os.getenv("OMARCHY_VCONSOLE") @@ -16,7 +17,9 @@ io.open = function(path, mode) return real_open(path, mode) end - if not vconsole then + -- An unset OMARCHY_VCONSOLE reaches Lua as "", never nil, so the cases that + -- ask for a missing /etc/vconsole.conf need the empty string to fail the open. + if not vconsole or vconsole == "" then return nil end @@ -35,19 +38,29 @@ hl = { o = { window = function() end } -require("default.hypr.input") +require(os.getenv("OMARCHY_MODULE")) LUA } -assert_input() { - local description="$1" - local expected="$2" +resolved_input() { + resolved_input_for "default.hypr.input" "${1-}" +} + +# The greeter compositor resolves the layout from its own entrypoint. +resolved_greeter_input() { + resolved_input_for "default.sddm.hyprland" "${1-}" +} + +assert_resolved_input() { + local resolver="$1" + local description="$2" + local expected="$3" local actual - if (( $# > 2 )); then - actual=$(resolved_input "$3") + if (( $# > 3 )); then + actual=$("$resolver" "$4") else - actual=$(resolved_input) + actual=$("$resolver") fi [[ $actual == "$expected" ]] || @@ -55,24 +68,48 @@ assert_input() { pass "$description" } +assert_input() { + assert_resolved_input resolved_input "$@" +} + +assert_greeter_input() { + assert_resolved_input resolved_greeter_input "$@" +} + base_options="compose:caps,shift:both_capslock_cancel" 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 ' +# The greeter takes no compose or capslock options: they're session comfort +# settings with nothing to do with typing a password. +assert_greeter_input "greeter falls back to us without vconsole.conf" "[us] [] []" + +assert_greeter_input "greeter follows the system layout" "[fr] [] []" 'XKBLAYOUT=fr +' + +assert_greeter_input "greeter keeps a latin layout in front" "[us,ru] [,phonetic] [grp:alts_toggle]" 'XKBLAYOUT=ru +XKBVARIANT=phonetic +' + hooks_conf="$ROOT/etc/mkinitcpio.conf.d/omarchy_hooks.conf" input_lua="$ROOT/default/hypr/input.lua" @@ -80,6 +117,14 @@ hooks_layouts=$(awk -F')' '/\) ;;$/ { gsub(/[[:space:]|]+/, "\n", $1); print $1 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" + +sddm_lua="$ROOT/default/sddm/hyprland.lua" +sddm_layouts=$(sed -n '/^local non_latin_layouts =/,+1p' "$sddm_lua" | grep -o '"[^"]*"' | tr -d '"' | tr ' ' '\n' | grep '^[a-z]\+$' | sort) + +[[ $hooks_layouts == "$sddm_layouts" ]] || + fail "greeter non-latin layout list stays in sync" "$(diff <(echo "$hooks_layouts") <(echo "$sddm_layouts"))" +pass "greeter non-latin layout list stays in sync with the initramfs hook"