diff --git a/bin/omarchy-input-method b/bin/omarchy-input-method index 36004c2f..a3dcb653 100755 --- a/bin/omarchy-input-method +++ b/bin/omarchy-input-method @@ -7,7 +7,11 @@ set -euo pipefail if [[ ${1:-} == "cycle" ]]; then - python "$OMARCHY_PATH/default/input-methods/indicator.py" cycle + # The shell's reader retains a choice made while nothing has focus. Without + # the shell, switch directly. + if [[ $(omarchy-shell shell cycleInput 2>/dev/null) != "ok" ]]; then + python "$OMARCHY_PATH/default/input-methods/indicator.py" cycle + fi else python "$OMARCHY_PATH/default/input-methods/configure.py" "$@" fi diff --git a/default/input-methods/indicator.py b/default/input-methods/indicator.py index bdd12c05..bb6703db 100644 --- a/default/input-methods/indicator.py +++ b/default/input-methods/indicator.py @@ -112,12 +112,18 @@ class Indicator: self.entries = {} def select_next(self): - state = snapshot(self.bus) + devices = subprocess.run(["hyprctl", "-j", "devices"], check=True, text=True, capture_output=True) + keyboards = json.loads(devices.stdout).get("keyboards", []) + try: + state = snapshot(self.bus) + except GLib.Error: + # Without Fcitx, Super+I still cycles the compositor layouts. + for command in layout_switches(keyboards): + subprocess.run(command, check=True, capture_output=True) + return methods = state["methods"] current = self.pending or state["current"] or self.last state["current"] = current if current in methods else next(iter(methods), "") - devices = subprocess.run(["hyprctl", "-j", "devices"], check=True, text=True, capture_output=True) - keyboards = json.loads(devices.stdout).get("keyboards", []) following, index = cycle_choice(state, keyboards) if following is None: return diff --git a/docs/omarchy-shell.md b/docs/omarchy-shell.md index 280268dc..b900a24a 100644 --- a/docs/omarchy-shell.md +++ b/docs/omarchy-shell.md @@ -122,6 +122,7 @@ or `omarchy.power`. There is no `bar` target. | `reloadConfig` | reload shell.json | | `applyTheme ` | push theme colors + shell.toml | | `toggleBarTransparency` | flip the bar background between solid and transparent | +| `cycleInput` | switch to the next input method or layout (Super+I); `not running` without the input reader | | `setPluginEnabled <"true"\|…>` | flip enabled bit (`ok` / `unknown`) | | `enablePlugin ` | enable and place in one mutation | | `putBarWidget ` | place a widget only where absent (`omarchy bar put`) | diff --git a/shell/Commons/InputMethodState.qml b/shell/Commons/InputMethodState.qml index bbcc757c..7535055c 100644 --- a/shell/Commons/InputMethodState.qml +++ b/shell/Commons/InputMethodState.qml @@ -8,7 +8,9 @@ Singleton { property var state: ({}) function cycle() { - if (watcher.running) watcher.write("cycle\n") + if (!watcher.running) return false + watcher.write("cycle\n") + return true } // One reader and pending selection for every monitor's keyboard widget. diff --git a/shell/shell.qml b/shell/shell.qml index 8a7272e4..d2c9ef1d 100644 --- a/shell/shell.qml +++ b/shell/shell.qml @@ -1779,6 +1779,12 @@ ShellRoot { return "ok" } + // Super+I goes through the bar's reader, which keeps a choice made before + // any text field has focus until one does. + function cycleInput(): string { + return Commons.InputMethodState.cycle() ? "ok" : "not running" + } + function rescanPlugins(): void { shell.reloadPlugins() } diff --git a/test/shell.d/input-indicator-test.sh b/test/shell.d/input-indicator-test.sh index 8e91a79c..ac73df73 100755 --- a/test/shell.d/input-indicator-test.sh +++ b/test/shell.d/input-indicator-test.sh @@ -170,4 +170,33 @@ with patch.object(indicator.GLib, "MainLoop", return_value=loop), patch.object(i assert call.call_args.args[1] == "SetCurrentIM" print("ok - a failed compositor query leaves the bar click reader alive for the next request") +keyboards = [{"name": "physical", "layout": "us,dk", "active_layout_index": 0}] +with patch.object(indicator, "snapshot", side_effect=indicator.GLib.Error("no fcitx")), patch.object(indicator.subprocess, "run", side_effect=fake_process): + indicator.Indicator(None).select_next() + assert keyboards[0]["active_layout_index"] == 1 +print("ok - the shortcut reader cycles compositor layouts when Fcitx is unavailable") + PY + +stubs=$(mktemp -d) +trap 'rm -rf "$stubs"' EXIT +calls="$stubs/calls" +cat >"$stubs/omarchy-shell" <<'SH' +#!/bin/bash +echo "shell $*" >>"$CALLS" +echo "$SHELL_REPLY" +SH +cat >"$stubs/python" <<'SH' +#!/bin/bash +echo "python ${*##*/}" >>"$CALLS" +SH +chmod +x "$stubs/omarchy-shell" "$stubs/python" + +CALLS=$calls SHELL_REPLY=ok PATH="$stubs:$PATH" OMARCHY_PATH="$ROOT" "$ROOT/bin/omarchy-input-method" cycle +[[ $(<"$calls") == "shell shell cycleInput" ]] || fail "Super+I goes through the shell's reader" "$(<"$calls")" +pass "Super+I goes through the shell's reader, which retains a choice made without focus" + +: >"$calls" +CALLS=$calls SHELL_REPLY="" PATH="$stubs:$PATH" OMARCHY_PATH="$ROOT" "$ROOT/bin/omarchy-input-method" cycle +[[ $(<"$calls") == $'shell shell cycleInput\npython indicator.py cycle' ]] || fail "Super+I switches directly without the shell" "$(<"$calls")" +pass "Super+I switches directly when the shell does not answer"