From 3aa498a6d70966390cecc8aecf1838fff9029ee8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 11 Oct 2026 07:36:45 +0900 Subject: [PATCH] Load engines installed after Fcitx started when they are selected The Typing menu refused an engine whose package an update had installed, since Fcitx discovers engines only at startup. Restart input once and wait for the engine; name the package when it really is not installed. Co-Authored-By: Claude Opus 5.5 --- default/input-methods/typing_setup.py | 19 +++++++++++++++++-- test/shell.d/typing-setup-checks.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/default/input-methods/typing_setup.py b/default/input-methods/typing_setup.py index add64095..6364aa1d 100644 --- a/default/input-methods/typing_setup.py +++ b/default/input-methods/typing_setup.py @@ -106,12 +106,27 @@ def configure_inputs(): choose("Input Methods", catalog, current, "input", dict(items)) +def load_engines(names, catalog): + packages = sorted({package for name in names for package in setup.PRESETS.get(name, {}).get("packages", [])}) + absent = [package for package in packages + if subprocess.run(["omarchy-pkg-present", package], capture_output=True).returncode] + if absent or any(name not in setup.PRESETS for name in names): + labels = ", ".join(catalog.get(name, name) for name in names) + raise RuntimeError(f"Install {' '.join(absent) or 'its engine'} to use {labels}") + # Fcitx discovers engines only at startup, so one installed while it was + # running (by an update, say) needs a restart before it can be selected. + setup.run(["omarchy-restart-xcompose"]) + for name in names: + setup.wait_ready(name) + + def save_inputs(selected, overrides=None): group, layout, items = setup.live_group() catalog, available = input_catalog(items) - missing = [catalog.get(name, name) for name in selected if name not in available] + missing = [name for name in selected if name not in available] if missing: - raise RuntimeError("Restart input after updating Omarchy to use: " + ", ".join(missing)) + load_engines(missing, catalog) + group, layout, items = setup.live_group() set_inputs(group, layout, items, input_items(items, selected, overrides)) # Only a saved engine claims the CJK font fallback, and a failed font write # returns the group so the menu's restored checks stay true. diff --git a/test/shell.d/typing-setup-checks.py b/test/shell.d/typing-setup-checks.py index 1fcc56c1..21dca265 100644 --- a/test/shell.d/typing-setup-checks.py +++ b/test/shell.d/typing-setup-checks.py @@ -63,6 +63,23 @@ class TypingSetupTest(unittest.TestCase): typing.save_inputs(["mozc"]) self.assertFalse((Path(temporary) / "fontconfig/conf.d/50-omarchy-input-method.conf").exists()) + def test_an_engine_installed_after_fcitx_started_loads_on_selection(self): + group = ("Default", "us", [["keyboard-us", ""]]) + present = subprocess.CompletedProcess([], 0) + with patch.object(typing.setup, "live_group", return_value=group), patch.object(typing.setup, "available_methods", return_value=[]), patch.object(typing.subprocess, "run", return_value=present), patch.object(typing.setup, "run") as run, patch.object(typing.setup, "wait_ready") as ready, patch.object(typing, "set_inputs") as save, patch.object(typing.setup, "font_default"): + typing.save_inputs(["mozc"]) + run.assert_called_once_with(["omarchy-restart-xcompose"]) + ready.assert_called_once_with("mozc") + self.assertEqual(save.call_args.args[3], [["keyboard-us", ""], ["mozc", ""]]) + + def test_an_uninstalled_engine_names_its_package_without_restarting(self): + absent = subprocess.CompletedProcess([], 1) + with patch.object(typing.setup, "live_group", return_value=("Default", "us", [["keyboard-us", ""]])), patch.object(typing.setup, "available_methods", return_value=[]), patch.object(typing.subprocess, "run", return_value=absent), patch.object(typing.setup, "run") as run, patch.object(typing, "set_inputs") as save: + with self.assertRaisesRegex(RuntimeError, "Install fcitx5-mozc to use Japanese"): + typing.save_inputs(["mozc"]) + run.assert_not_called() + save.assert_not_called() + def test_failed_font_write_restores_the_input_group(self): before = [["keyboard-us", ""]] with patch.object(typing.setup, "live_group", return_value=("Default", "us", before)), patch.object(typing.setup, "available_methods", return_value=[["mozc", "Mozc"]]), patch.object(typing, "set_inputs"), patch.object(typing.setup, "font_default", side_effect=OSError("read-only")), patch.object(typing.setup, "live_set") as setter: