Switch out of an input method and back with a lone Shift (#14975)

* Switch out of an input method and back with a lone Shift

Fcitx's alternate trigger only acts while an engine like Pinyin is or was
active, so Shift + letter and plain Latin typing are unaffected. It needs
Shift releases intact, which the both-Shifts Caps Lock option prevented.

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

* Follow XDG_CONFIG_HOME in the Shift migration and test it

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier HanssonandClaude Opus 5.5 authored and GitHub committed 2026-10-11 15:15:03 +09:00
1 parent 35a55fd236
commit dc50bbe574
6 files changed
+89 -2

No files matched your search

+2
View File
@@ -9,7 +9,9 @@
[Hotkey/DeactivateKeys]
0=Muhenkan
# A lone Shift leaves an engine like Pinyin for Latin input and returns to it.
[Hotkey/AltTriggerKeys]
0=Shift_L
[Hotkey/EnumerateGroupForwardKeys]
-1
View File
@@ -108,7 +108,6 @@ def defaults(config_home, fresh=False):
stock_lists = {
"Hotkey/ActivateKeys": {"0": "Hangul_Hanja"},
"Hotkey/DeactivateKeys": {"0": "Hangul_Romaja"},
"Hotkey/AltTriggerKeys": {"0": "Shift_L"},
"Hotkey/EnumerateGroupForwardKeys": {"0": "Super+space"},
"Hotkey/EnumerateGroupBackwardKeys": {"0": "Super+Shift+space"},
"TriggerKey": {"0": "Super+grave", "1": "Super+semicolon"},
+1 -1
View File
@@ -61,7 +61,7 @@ On Dell XPS laptops with a haptic touchpad, you can also set the click strength
Select Japanese, Korean, Chinese (Simplified, Pinyin), or Chinese (Traditional, Zhuyin) when the installer asks for your keyboard. Omarchy configures the keyboard and matching input engine automatically, including during offline first-boot setup. Japanese uses Mozc, Korean uses standard two-set Hangul, Simplified Chinese uses Pinyin, and Traditional Chinese uses Chewing's standard Zhuyin arrangement. Japanese (US keyboard) provides Mozc with a US keyboard layout.
Typing starts in Latin mode. Press `Super + I` to cycle through configured input methods, and `Super + Shift + I` to cycle back. On Japanese keyboards, 全角/半角 toggles input, 変換 turns it on, and 無変換 turns it off. On a Korean keyboard layout, dedicated 한/영 and 한자 keys switch Hangul and convert to Hanja. `Ctrl + Space` stays available for Tmux and Herdr; `Shift + CapsLock` stays the compose key.
Typing starts in Latin mode. Press `Super + I` to cycle through configured input methods, and `Super + Shift + I` to cycle back. Tap `Shift` on its own to step out of an input method like Pinyin into Latin letters, and tap it again to go back, the way Sogou, Microsoft Pinyin, and macOS work; `Shift` plus a letter still types a capital. On Japanese keyboards, 全角/半角 toggles input, 変換 turns it on, and 無変換 turns it off. On a Korean keyboard layout, dedicated 한/영 and 한자 keys switch Hangul and convert to Hanja. `Ctrl + Space` stays available for Tmux and Herdr; `Shift + CapsLock` stays the compose key.
The keyboard indicator appears on the right of the bar when you have multiple keyboard layouts or input methods. Click it to switch input languages; Japanese displays あ for hiragana. For Japanese character choices, switch to Japanese, type a word such as `nihongo`, then press Space twice. Press Enter to accept your choice. If you also have multiple keyboard layouts, right-click the indicator to cycle those.
+10
View File
@@ -0,0 +1,10 @@
echo "Let a lone Shift switch out of an input method and back"
# An earlier migration wrote this list empty. Fill only that empty list: a
# missing one already falls back to Fcitx's Shift_L, and a filled one is the
# user's choice.
config="${XDG_CONFIG_HOME:-$HOME/.config}/fcitx5/config"
if [[ -f $config ]] && awk '/^\[Hotkey\/AltTriggerKeys\]$/ { in_list = 1; found = 1; next } /^\[/ { in_list = 0 } in_list && /=/ { filled = 1 } END { exit !(found && !filled) }' "$config"; then
sed -i --follow-symlinks '/^\[Hotkey\/AltTriggerKeys\]$/a 0=Shift_L' "$config"
busctl --user call org.fcitx.Fcitx5 /controller org.fcitx.Fcitx.Controller1 ReloadConfig 2>/dev/null || true
fi
+1
View File
@@ -66,6 +66,7 @@ class InputMethodTest(unittest.TestCase):
self.assertEqual(keys["Behavior"]["ActiveByDefault"], "False")
self.assertNotIn("Control+space", keys["Hotkey/TriggerKeys"].values())
self.assertEqual(set(keys["Hotkey/TriggerKeys"].values()), {"Zenkaku_Hankaku", "Hangul"})
self.assertEqual(keys["Hotkey/AltTriggerKeys"], {"0": "Shift_L"})
def test_vconsole_variants_quotes_and_japanese_without_preference(self):
self.preference.unlink()
+75
View File
@@ -0,0 +1,75 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
migration="$ROOT/migrations/1791676907.sh"
test_dir=$(mktemp -d)
trap 'rm -rf "$test_dir"' EXIT
mkdir -p "$test_dir/bin"
export CALL_LOG="$test_dir/calls"
# A live Fcitx is asked to reload; never reach the real session bus.
cat >"$test_dir/bin/busctl" <<'SH'
#!/bin/bash
printf 'busctl %s\n' "$*" >>"$CALL_LOG"
SH
chmod +x "$test_dir/bin/busctl"
# Runs the migration twice, as a retried update would. The caller's own
# XDG_CONFIG_HOME would point at the developer's real config, so it is cleared
# unless a case sets MIGRATION_XDG_CONFIG_HOME.
migrate() {
local home=$1
for run in 1 2; do
env -u XDG_CONFIG_HOME ${MIGRATION_XDG_CONFIG_HOME:+XDG_CONFIG_HOME="$MIGRATION_XDG_CONFIG_HOME"} \
HOME="$home" PATH="$test_dir/bin:$PATH" bash -euo pipefail "$migration" >/dev/null
done
}
fresh_home() {
rm -rf "$test_dir/home" "$CALL_LOG"
mkdir -p "$test_dir/home/.config/fcitx5"
printf '%s' "$1" >"$test_dir/home/.config/fcitx5/config"
}
fresh_home $'[Hotkey/TriggerKeys]\n0=Hangul\n\n[Hotkey/AltTriggerKeys]\n\n[Behavior]\nActiveByDefault=False\n'
migrate "$test_dir/home"
[[ $(<"$test_dir/home/.config/fcitx5/config") == $'[Hotkey/TriggerKeys]\n0=Hangul\n\n[Hotkey/AltTriggerKeys]\n0=Shift_L\n\n[Behavior]\nActiveByDefault=False' ]] ||
fail "an empty list gets Shift_L once" "$(<"$test_dir/home/.config/fcitx5/config")"
[[ $(grep -c ReloadConfig "$CALL_LOG") == 1 ]] || fail "Fcitx reloads only when the list changes" "$(cat "$CALL_LOG")"
pass "an empty list gets Shift_L once, and Fcitx reloads once"
fresh_home $'[Hotkey/AltTriggerKeys]\n0=Shift_R\n'
migrate "$test_dir/home"
[[ $(<"$test_dir/home/.config/fcitx5/config") == $'[Hotkey/AltTriggerKeys]\n0=Shift_R' && ! -e $CALL_LOG ]] ||
fail "a chosen key is kept" "$(<"$test_dir/home/.config/fcitx5/config")"
pass "a list the user filled is kept"
fresh_home $'[Behavior]\nActiveByDefault=False\n'
migrate "$test_dir/home"
[[ $(<"$test_dir/home/.config/fcitx5/config") == $'[Behavior]\nActiveByDefault=False' ]] ||
fail "a missing list keeps Fcitx's own Shift_L default" "$(<"$test_dir/home/.config/fcitx5/config")"
rm -rf "$test_dir/home"
mkdir -p "$test_dir/home"
migrate "$test_dir/home"
[[ ! -e $test_dir/home/.config/fcitx5/config ]] || fail "no Fcitx config is created"
pass "a missing list or config is left to Fcitx's default"
fresh_home ""
mkdir -p "$test_dir/dotfiles"
printf '[Hotkey/AltTriggerKeys]\n\n' >"$test_dir/dotfiles/fcitx5-config"
ln -sf "$test_dir/dotfiles/fcitx5-config" "$test_dir/home/.config/fcitx5/config"
migrate "$test_dir/home"
[[ -L $test_dir/home/.config/fcitx5/config && $(grep -c '^0=Shift_L$' "$test_dir/dotfiles/fcitx5-config") == 1 ]] ||
fail "a symlinked config stays linked and its target is updated" "$(ls -l "$test_dir/home/.config/fcitx5/config"; cat "$test_dir/dotfiles/fcitx5-config")"
pass "a symlinked config stays linked and its target is updated"
rm -rf "$test_dir/home" "$test_dir/xdg"
mkdir -p "$test_dir/home" "$test_dir/xdg/fcitx5"
printf '[Hotkey/AltTriggerKeys]\n' >"$test_dir/xdg/fcitx5/config"
MIGRATION_XDG_CONFIG_HOME="$test_dir/xdg" migrate "$test_dir/home"
[[ $(<"$test_dir/xdg/fcitx5/config") == $'[Hotkey/AltTriggerKeys]\n0=Shift_L' ]] ||
fail "XDG_CONFIG_HOME is followed" "$(<"$test_dir/xdg/fcitx5/config")"
pass "the config under XDG_CONFIG_HOME is the one repaired"