Keep Ctrl+Space out of Fcitx's toggles instead of probing for it
The terminal prefix bind read the Fcitx config and profile on every press to avoid fighting a Ctrl+Space toggle. configure --defaults now removes that key from every trigger list, so the bind only checks for a terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf9e8f3625
commit
0cd685946b
5 files changed
+39
-68
No files matched your search
@@ -1,5 +1,3 @@
|
||||
local paths = require("default.hypr.paths")
|
||||
|
||||
-- Same terminal tag test as clipboard.lua.
|
||||
local function active_window_is_terminal()
|
||||
local window = hl.get_active_window()
|
||||
@@ -16,55 +14,15 @@ local function active_window_is_terminal()
|
||||
return false
|
||||
end
|
||||
|
||||
-- Read on demand so adding an engine in a live desktop needs no reload.
|
||||
local function has_input_method()
|
||||
local file = io.open(paths.config_home .. "/fcitx5/profile", "r")
|
||||
if not file then return false end
|
||||
local in_item, found = false, false
|
||||
for line in file:lines() do
|
||||
if line:match("^%[") then
|
||||
in_item = line:match("^%[Groups/%d+/Items/%d+%]$") ~= nil
|
||||
elseif in_item then
|
||||
local name = line:match("^Name=(.+)$")
|
||||
if name and not name:match("^keyboard%-") then found = true end
|
||||
end
|
||||
end
|
||||
file:close()
|
||||
return found
|
||||
end
|
||||
|
||||
-- Keep the desktop shortcut independent of Fcitx's custom switching keys.
|
||||
-- Read the live Fcitx group so setup works without a compositor reload.
|
||||
o.bind("SUPER + I", "Switch input language", "omarchy-input-method cycle")
|
||||
o.bind("SUPER + SHIFT + I", "Switch input language back", "omarchy-input-method cycle back")
|
||||
|
||||
-- Preserve custom Ctrl + Space triggers. Without a
|
||||
-- [Hotkey/TriggerKeys] list in the config, fcitx5 uses its default, which has
|
||||
-- Ctrl + Space.
|
||||
local function fcitx5_toggles_on_ctrl_space()
|
||||
local file = io.open(paths.config_home .. "/fcitx5/config", "r")
|
||||
if not file then
|
||||
return true
|
||||
end
|
||||
|
||||
local in_triggers, has_triggers, toggles = false, false, false
|
||||
for line in file:lines() do
|
||||
if line:match("^%[") then
|
||||
in_triggers = line == "[Hotkey/TriggerKeys]"
|
||||
has_triggers = has_triggers or in_triggers
|
||||
elseif in_triggers and line:match("^%d+=Control%+space$") then
|
||||
toggles = true
|
||||
end
|
||||
end
|
||||
|
||||
file:close()
|
||||
return toggles or not has_triggers
|
||||
end
|
||||
|
||||
-- Pass the tmux/Herdr prefix through and return to direct input, so the next
|
||||
-- key reaches the multiplexer instead of the engine's composition.
|
||||
o.bind("CTRL + SPACE", "Direct input for the terminal prefix", function()
|
||||
if active_window_is_terminal() and has_input_method() and not fcitx5_toggles_on_ctrl_space() then
|
||||
if active_window_is_terminal() then
|
||||
-- Wait for the local controller reply before dispatching the next key.
|
||||
o.shell_succeeds("timeout 0.3 fcitx5-remote --check -c")
|
||||
end
|
||||
|
||||
@@ -72,6 +72,21 @@ def key_list(entries):
|
||||
return {frozenset(value.split("+")) for value in entries.values()}
|
||||
|
||||
|
||||
def without_ctrl_space(text):
|
||||
# Ctrl+Space belongs to tmux and Herdr, and the terminal binding returns to
|
||||
# direct input on it; a Fcitx toggle on the same key would undo that. Without
|
||||
# a TriggerKeys list Fcitx falls back to its default, which has Ctrl+Space.
|
||||
section = re.search(r"(?ms)^\[Hotkey/TriggerKeys\][ \t]*\n(.*?)(?=^\[|\Z)", text)
|
||||
if not section:
|
||||
return text.rstrip() + ("\n\n" if text.strip() else "") + "[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n1=Hangul\n"
|
||||
keys = values(section[1])
|
||||
kept = [keys[key] for key in sorted(keys, key=lambda key: int(key) if key.isdigit() else 0) if keys[key] != "Control+space"]
|
||||
if len(kept) == len(keys):
|
||||
return text
|
||||
body = "".join(f"{index}={key}\n" for index, key in enumerate(kept)) + ("\n" if section[1].endswith("\n\n") else "")
|
||||
return text[:section.start(1)] + body + text[section.end(1):]
|
||||
|
||||
|
||||
def defaults(config_home, fresh=False):
|
||||
changed = False
|
||||
files = ["config", "conf/quickphrase.conf", "conf/wayland.conf", "conf/xcb.conf", "conf/pinyin.conf"]
|
||||
@@ -80,6 +95,8 @@ def defaults(config_home, fresh=False):
|
||||
original = read(target)
|
||||
shipped = read(ROOT / "config/fcitx5" / name)
|
||||
if name == "config" or name == "conf/quickphrase.conf":
|
||||
if name == "config":
|
||||
original = without_ctrl_space(original)
|
||||
existing = sections(original)
|
||||
additions = []
|
||||
for section, entries in sections(shipped).items():
|
||||
@@ -89,7 +106,6 @@ def defaults(config_home, fresh=False):
|
||||
# Existing lists (including explicitly empty lists) are user choices.
|
||||
# A fresh profile has no working IME shortcut to preserve.
|
||||
stock_lists = {
|
||||
"Hotkey/TriggerKeys": {"0": "Control+space", "1": "Zenkaku_Hankaku", "2": "Hangul"},
|
||||
"Hotkey/ActivateKeys": {"0": "Hangul_Hanja"},
|
||||
"Hotkey/DeactivateKeys": {"0": "Hangul_Romaja"},
|
||||
"Hotkey/AltTriggerKeys": {"0": "Shift_L"},
|
||||
@@ -103,7 +119,8 @@ def defaults(config_home, fresh=False):
|
||||
if section not in existing:
|
||||
additions.append(f"[{section}]\n" + "".join(f"{key}={value}\n" for key, value in entries.items()))
|
||||
if additions:
|
||||
changed |= atomic_write(target, original.rstrip() + ("\n\n" if original.strip() else "") + "\n".join(additions))
|
||||
original = original.rstrip() + ("\n\n" if original.strip() else "") + "\n".join(additions)
|
||||
changed |= atomic_write(target, original)
|
||||
elif name == "conf/pinyin.conf":
|
||||
# Suppress the first-use cloud prompt while preserving any explicit
|
||||
# cloud-prediction preference and all other existing engine settings.
|
||||
|
||||
@@ -14,7 +14,7 @@ The ISO carries the extra fields in `omarchy_install.input_method` and `omarchy_
|
||||
|
||||
Keyboard selections are persisted as data in `~/.config/omarchy/keyboard-layouts`; `default.hypr.keyboard.selected()` reads them before falling back to installation defaults. Personal Hyprland overrides still take precedence. A save reloads and validates the compositor, aligns the Fcitx group's primary keyboard with the leading layout/variant, and restores the previous configuration and group on failure. Non-Latin selections keep the installer's Latin-leading rule for desktop shortcuts. System console, disk-unlock, and greeter layouts are unchanged.
|
||||
|
||||
Shortcut defaults reserve Ctrl+Space for Tmux/Herdr, Super+Space for the menu, Super+Shift+Space for the bar, and Super+grave for the console. Hyprland owns Super+I to cycle all selected compositor layouts followed by the configured Fcitx input methods, independently of Fcitx switching keys. With keyboard layouts alone, the same shortcut cycles the compositor layouts, keeping matching devices in sync even without the bar widget. The widget is enabled in the default right-hand bar layout and appears only when more than one input method or keyboard layout is configured. Fcitx handles Zenkaku/Hankaku and Hangul; Henkan activates and Muhenkan deactivates. An existing configured list, including an empty list, is preserved. Stock generated lists are repaired only while the user still has the single-keyboard default profile. A terminal Ctrl+Space returns to direct input before the following key, with a 300 ms timeout and no D-Bus activation; a keyboard-only profile skips this step. Wayland and XCB addons do not override the compositor's XKB settings.
|
||||
Shortcut defaults reserve Ctrl+Space for Tmux/Herdr, Super+Space for the menu, Super+Shift+Space for the bar, and Super+grave for the console. Hyprland owns Super+I to cycle all selected compositor layouts followed by the configured Fcitx input methods, independently of Fcitx switching keys. With keyboard layouts alone, the same shortcut cycles the compositor layouts, keeping matching devices in sync even without the bar widget. The widget is enabled in the default right-hand bar layout and appears only when more than one input method or keyboard layout is configured. Fcitx handles Zenkaku/Hankaku and Hangul; Henkan activates and Muhenkan deactivates. Ctrl+Space is never a Fcitx toggle: `configure --defaults` removes it from any TriggerKeys list, keeping the user's other keys, and writes Omarchy's list where none exists, since Fcitx's built-in default includes it. Other existing lists, including empty ones, are preserved, and stock generated lists are repaired only while the user still has the single-keyboard default profile. A terminal Ctrl+Space returns to direct input before the following key, with a 300 ms timeout and no D-Bus activation. Wayland and XCB addons do not override the compositor's XKB settings.
|
||||
|
||||
The migration installs the standard engines and applies conservative defaults. It aligns an untouched Fcitx keyboard profile with the install layout, using the live controller when Fcitx is running. It leaves existing multilingual profiles and working trigger keys alone. Atomic file writes follow configuration symlinks, preserve file modes, and roll back on a failed seed or migration so a retry can complete. Migration bus errors defer profile alignment to the next login and do not restart input. If a pre-update daemon has not discovered a new engine, the migration aligns only its keyboard and prints the setup command.
|
||||
|
||||
|
||||
@@ -107,29 +107,16 @@ assert_fires "the JIS + key zooms the Obsidian Flatpak in with Ctrl + ^" \
|
||||
$'send CTRL asciicircum down\nsend CTRL asciicircum up' "$zoom" "md.obsidian.Obsidian"
|
||||
|
||||
# The prefix bind has to pass Ctrl + Space on, or tmux and Herdr never see it.
|
||||
# Fcitx never toggles on it (configure --defaults removes it from its keys), so
|
||||
# a terminal can always return to direct input first.
|
||||
prefix="CTRL + SPACE"
|
||||
freed=$'[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n\n[Hotkey/ActivateKeys]\n0=Henkan'
|
||||
TEST_FCITX5_CONFIG=$freed assert_fires "the terminal prefix switches to direct input once Ctrl + Space no longer toggles" \
|
||||
$'non_consuming\nexec timeout 0.3 fcitx5-remote --check -c' "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$freed assert_fires "the prefix leaves input alone outside terminals" \
|
||||
"non_consuming" "$prefix" "chromium" "browser"
|
||||
|
||||
# While fcitx5 still toggles on Ctrl + Space, switching to direct input would
|
||||
# undo the toggle it just made.
|
||||
assert_fires "the prefix leaves input alone without an fcitx5 config" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$'[Behavior]\nShareInputState=No' assert_fires "the prefix leaves input alone with the default trigger keys" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$'[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n1=Control+space' assert_fires "the prefix leaves input alone while Ctrl + Space is a trigger key" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
|
||||
direct=$'non_consuming\nexec timeout 0.3 fcitx5-remote --check -c'
|
||||
assert_fires "the terminal prefix switches to direct input" "$direct" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
assert_fires "the prefix leaves input alone outside terminals" "non_consuming" "$prefix" "chromium" "browser"
|
||||
for layout in us kr; do
|
||||
TEST_LAYOUT="$layout" TEST_FCITX5_CONFIG=$freed assert_fires "the terminal prefix works with $layout keyboards too" \
|
||||
$'non_consuming\nexec timeout 0.3 fcitx5-remote --check -c' "$prefix" "foot" "terminal"
|
||||
TEST_LAYOUT="$layout" assert_fires "the terminal prefix works with $layout keyboards too" "$direct" "$prefix" "foot" "terminal"
|
||||
done
|
||||
|
||||
TEST_KEYBOARD_ONLY=true TEST_FCITX5_CONFIG=$freed assert_fires "keyboard-only profiles skip the terminal controller call" \
|
||||
"non_consuming" "$prefix" "foot" "terminal"
|
||||
TEST_KEYBOARD_ONLY=true assert_fires "keyboard-only profiles use the same prefix bind" "$direct" "$prefix" "foot" "terminal"
|
||||
|
||||
assert_fires "Super + I cycles configured input independently of custom Fcitx keys" \
|
||||
"exec omarchy-input-method cycle" "SUPER + I" "chromium"
|
||||
|
||||
@@ -105,6 +105,14 @@ class InputMethodTest(unittest.TestCase):
|
||||
self.assertEqual(config["Behavior"], {"ActiveByDefault": "True"})
|
||||
self.assertEqual(quickphrase.read_text(), "[TriggerKey]\n\n")
|
||||
|
||||
def test_ctrl_space_is_removed_from_every_trigger_list(self):
|
||||
path = self.config / "fcitx5/config"
|
||||
setup.atomic_write(path, "[Hotkey/TriggerKeys]\n0=Alt+space\n1=Control+space\n2=Hangul\n\n[Behavior]\nActiveByDefault=True\n")
|
||||
setup.defaults(self.config)
|
||||
config = setup.sections(path.read_text())
|
||||
self.assertEqual(config["Hotkey/TriggerKeys"], {"0": "Alt+space", "1": "Hangul"})
|
||||
self.assertEqual(config["Behavior"], {"ActiveByDefault": "True"})
|
||||
|
||||
def test_generated_stock_shortcuts_are_repaired(self):
|
||||
path = self.config / "fcitx5/config"
|
||||
setup.atomic_write(path, "[Hotkey/TriggerKeys]\n0=Control+space\n1=Zenkaku_Hankaku\n2=Hangul\n\n[Hotkey/EnumerateGroupForwardKeys]\n0=Super+space\n")
|
||||
@@ -113,11 +121,12 @@ class InputMethodTest(unittest.TestCase):
|
||||
self.assertEqual(config["Hotkey/EnumerateGroupForwardKeys"], {})
|
||||
self.assertNotIn("Control+space", config["Hotkey/TriggerKeys"].values())
|
||||
|
||||
def test_existing_engine_inherited_hotkeys_are_preserved(self):
|
||||
def test_inherited_hotkeys_keep_fcitx_defaults_except_the_ctrl_space_toggle(self):
|
||||
path = self.config / "fcitx5/config"
|
||||
setup.atomic_write(path, "[Behavior]\nActiveByDefault=True\n")
|
||||
setup.defaults(self.config, fresh=False)
|
||||
self.assertEqual(path.read_text(), "[Behavior]\nActiveByDefault=True\n")
|
||||
self.assertEqual(path.read_text(), "[Behavior]\nActiveByDefault=True\n\n[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n1=Hangul\n")
|
||||
self.assertFalse(setup.defaults(self.config, fresh=False))
|
||||
|
||||
def test_lao_preference_keeps_a_latin_fcitx_keyboard(self):
|
||||
self.preference.write_text("INPUT_METHOD=none\nXKB_LAYOUT=la\n")
|
||||
|
||||
Reference in new issue
Block a user