Files
omarchy/test/shell.d/hyprland-keyboard-layout-test.sh
T
David Heinemeier HanssonandClaude Opus 5.5 1b5222be74 Return Caps Lock to normal and make Shift + Caps Lock the compose key (#14939)
* Return Caps Lock to normal and make Shift + Caps Lock the compose key

Caps Lock as compose, with both Shifts as Caps Lock, confused too many
people. Stock XKB has no option for compose on the shifted Caps key, so
Omarchy defines one in /etc/xkb, which libxkbcommon reads before the
system rules. The explicit actions keep the Shift level from toggling
Caps Lock. Lone Shift is no longer part of Caps Lock, which leaves it
free for input methods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Require xkbcli so the keymap check cannot silently skip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:06:59 +09:00

166 lines
6.2 KiB
Bash

#!/bin/bash
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
require_command lua
require_command xkbcli
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")
local real_open = io.open
io.open = function(path, mode)
if path:match("/omarchy/keyboard%-layouts$") then
local selected = os.getenv("TEST_KEYBOARD_SELECTION")
if not selected then return nil end
local file = io.tmpfile()
file:write(selected)
file:seek("set")
return file
end
if path == "/etc/omarchy/input-method" then
local preference = os.getenv("TEST_INPUT_PREFERENCE")
if not preference then return nil end
local file = io.tmpfile()
file:write(preference)
file:seek("set")
return file
end
if path ~= "/etc/vconsole.conf" then
return real_open(path, mode)
end
-- 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
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(os.getenv("OMARCHY_MODULE"))
LUA
}
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 (( $# > 3 )); then
actual=$("$resolver" "$4")
else
actual=$("$resolver")
fi
[[ $actual == "$expected" ]] ||
fail "$description" "expected: $expected"$'\n'"actual: $actual"
pass "$description"
}
assert_input() {
assert_resolved_input resolved_input "$@"
}
assert_greeter_input() {
assert_resolved_input resolved_greeter_input "$@"
}
base_options="omarchy:shift_caps_compose"
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
'
caps=$(xkbcli compile-keymap --include "$ROOT/etc/xkb" --include-defaults --layout us,ru --options omarchy:shift_caps_compose | grep -A5 'key <CAPS>')
[[ $caps == *"Caps_Lock,"*"Multi_key ]"* && $caps == *"LockMods(modifiers=Lock),"*"NoAction() ]"* ]] ||
fail "Shift + Caps Lock composes without toggling Caps Lock" "$caps"
pass "Shift + Caps Lock composes without toggling Caps Lock"
# 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"
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"
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"
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=hangul\n XKB_LAYOUT="kr" # desktop only\n' assert_input "Korean selection overrides its installer US layout" "[kr] [] [$base_options]" $'XKBLAYOUT=us\n'
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=none\nXKB_LAYOUT=la\n' assert_input "Lao selection gets Lao behind US on the desktop" "[us,la] [,] [$toggle_options]" $'XKBLAYOUT=us\n'
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=none\nXKB_LAYOUT=xx\n' assert_input "an unknown desktop override is ignored" "[us] [] [$base_options]" $'XKBLAYOUT=us\n'
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=hangul\nXKB_LAYOUT=kr\n' assert_input "a later US variant overrides the Korean preference" "[us] [intl] [$base_options]" $'XKBLAYOUT=us\nXKBVARIANT=intl\n'
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=hangul\nXKB_LAYOUT=kr\n' assert_input "a later layout overrides the Korean preference" "[de] [nodeadkeys] [$base_options]" $'XKBLAYOUT=de\nXKBVARIANT=nodeadkeys\n'
TEST_KEYBOARD_SELECTION=$'XKBLAYOUT=us,fr\nXKBVARIANT=intl,\n' assert_input "menu selections replace installer desktop defaults" "[us,fr] [intl,] [$base_options]" $'XKBLAYOUT=jp\n'
TEST_KEYBOARD_SELECTION=$'XKBLAYOUT=us,fr\nXKBVARIANT=intl,\n' assert_greeter_input "menu selections leave the greeter keyboard unchanged" "[jp] [] []" $'XKBLAYOUT=jp\n'
TEST_KEYBOARD_SELECTION=$'XKBLAYOUT=us,ru\nXKBVARIANT=,phonetic\n' assert_input "saved non-Latin selections retain the layout shortcut" "[us,ru] [,phonetic] [$toggle_options]" $'XKBLAYOUT=us\n'