Cycle input methods back with Super+Shift+I
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3aa498a6d7
commit
d579157881
9 files changed
+53
-27
No files matched your search
@@ -1,16 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Configure, inspect, or switch input methods
|
||||
# omarchy:args=<status|cycle|configure [--seed|--defaults] [none|mozc|hangul|pinyin|chewing]>
|
||||
# omarchy:args=<status|cycle [back]|configure [--seed|--defaults] [none|mozc|hangul|pinyin|chewing]>
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ ${1:-} == "cycle" ]]; then
|
||||
direction=${2:-next}
|
||||
if [[ $direction != "next" && $direction != "back" ]]; then
|
||||
echo "Usage: omarchy-input-method cycle [back]" >&2
|
||||
exit 1
|
||||
fi
|
||||
# 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
|
||||
if [[ $(omarchy-shell shell cycleInput "$direction" 2>/dev/null) != "ok" ]]; then
|
||||
python "$OMARCHY_PATH/default/input-methods/indicator.py" cycle "$direction"
|
||||
fi
|
||||
else
|
||||
python "$OMARCHY_PATH/default/input-methods/configure.py" "$@"
|
||||
|
||||
@@ -36,6 +36,7 @@ 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
|
||||
|
||||
@@ -51,24 +51,24 @@ def layout_keyboard(keyboards):
|
||||
return keyboard
|
||||
|
||||
|
||||
def layout_switches(keyboards, index=None):
|
||||
def layout_switches(keyboards, index=None, step=1):
|
||||
keyboard = layout_keyboard(keyboards)
|
||||
if keyboard is None:
|
||||
return []
|
||||
layout = keyboard["layout"]
|
||||
following = index if index is not None else (keyboard.get("active_layout_index", 0) + 1) % len(layout.split(","))
|
||||
following = index if index is not None else (keyboard.get("active_layout_index", 0) + step) % len(layout.split(","))
|
||||
return [["hyprctl", "switchxkblayout", item["name"], str(following)]
|
||||
for item in keyboards if item.get("layout") == layout and item.get("name")]
|
||||
|
||||
|
||||
def cycle_choice(state, keyboards):
|
||||
def cycle_choice(state, keyboards, step=1):
|
||||
methods = state.get("methods", [])
|
||||
keyboard = layout_keyboard(keyboards)
|
||||
primary = next((method for method in methods if method.startswith("keyboard-")), None)
|
||||
if keyboard is None or primary is None:
|
||||
current = state.get("current")
|
||||
if len(methods) > 1 and current in methods:
|
||||
return methods[(methods.index(current) + 1) % len(methods)], None
|
||||
return methods[(methods.index(current) + step) % len(methods)], None
|
||||
return None, None
|
||||
# One direct-input method represents all compositor layouts. Composition
|
||||
# engines follow them in the user's configured order.
|
||||
@@ -82,10 +82,10 @@ def cycle_choice(state, keyboards):
|
||||
position = (current, keyboard.get("active_layout_index", 0) if current == primary else None)
|
||||
if position not in choices:
|
||||
return None, None
|
||||
return choices[(choices.index(position) + 1) % len(choices)]
|
||||
return choices[(choices.index(position) + step) % len(choices)]
|
||||
|
||||
|
||||
def cycle_input():
|
||||
def cycle_input(step=1):
|
||||
try:
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
state = snapshot(bus)
|
||||
@@ -94,9 +94,9 @@ def cycle_input():
|
||||
devices = subprocess.run(["hyprctl", "-j", "devices"], check=True, text=True, capture_output=True)
|
||||
keyboards = json.loads(devices.stdout).get("keyboards", [])
|
||||
if not state.get("methods"):
|
||||
switches = layout_switches(keyboards)
|
||||
switches = layout_switches(keyboards, step=step)
|
||||
else:
|
||||
method, index = cycle_choice(state, keyboards)
|
||||
method, index = cycle_choice(state, keyboards, step)
|
||||
if method is not None and method != state.get("current"):
|
||||
call(bus, "SetCurrentIM", GLib.Variant("(s)", (method,)))
|
||||
switches = layout_switches(keyboards, index) if index is not None else []
|
||||
@@ -111,20 +111,20 @@ class Indicator:
|
||||
self.last = ""
|
||||
self.entries = {}
|
||||
|
||||
def select_next(self):
|
||||
def select_next(self, step=1):
|
||||
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):
|
||||
for command in layout_switches(keyboards, step=step):
|
||||
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), "")
|
||||
following, index = cycle_choice(state, keyboards)
|
||||
following, index = cycle_choice(state, keyboards, step)
|
||||
if following is None:
|
||||
return
|
||||
self.pending = following
|
||||
@@ -191,9 +191,9 @@ def watch(bus):
|
||||
buffer += data.decode()
|
||||
while "\n" in buffer:
|
||||
line, buffer = buffer.split("\n", 1)
|
||||
if line == "cycle":
|
||||
if line in ("cycle", "cycle back"):
|
||||
try:
|
||||
indicator.select_next()
|
||||
indicator.select_next(-1 if line == "cycle back" else 1)
|
||||
except (GLib.Error, OSError, ValueError, subprocess.CalledProcessError):
|
||||
indicator.pending = ""
|
||||
refresh()
|
||||
@@ -211,8 +211,8 @@ def watch(bus):
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if sys.argv[1:] == ["cycle"]:
|
||||
cycle_input()
|
||||
if sys.argv[1:] in (["cycle"], ["cycle", "next"], ["cycle", "back"]):
|
||||
cycle_input(-1 if sys.argv[2:] == ["back"] else 1)
|
||||
else:
|
||||
watch(Gio.bus_get_sync(Gio.BusType.SESSION, None))
|
||||
except (GLib.Error, OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||
|
||||
@@ -122,7 +122,7 @@ or `omarchy.power`. There is no `bar` target.
|
||||
| `reloadConfig` | reload shell.json |
|
||||
| `applyTheme <colorsB64> <shellB64>` | 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 |
|
||||
| `cycleInput <next\|back>` | switch to the next or previous input method or layout (Super+I, Super+Shift+I); `not running` without the input reader |
|
||||
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
|
||||
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
|
||||
| `putBarWidget <id> <placementJson>` | place a widget only where absent (`omarchy bar put`) |
|
||||
|
||||
@@ -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. 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; 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. 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; 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.
|
||||
|
||||
@@ -69,7 +69,7 @@ Use _Setup > Typing > Keyboard Layouts_ to select layouts such as English and Fr
|
||||
|
||||
Run `omarchy setup keyboard` or `omarchy setup input` to open the same pickers. To append one language from the command line, use `omarchy setup input mozc`, `hangul`, `pinyin`, or `chewing`. Your custom switching keys are preserved. For alternative engine layouts or keys, install `fcitx5-configtool` with `omarchy pkg add fcitx5-configtool` and launch it from a terminal. Selecting an input language does not change the desktop language.
|
||||
|
||||
Use `Super + I` to cycle through input methods or keyboard layouts. The input indicator appears on the right of the bar only when more than one input method or layout is configured. This desktop binding works alongside your existing Fcitx switching keys; you can override it in `~/.config/hypr/bindings.lua`. Tapping Shift alone is not an input switch: Omarchy uses both Shift keys for CapsLock, which changes how input methods see Shift release. Input engines and the CapsLock compose sequences use the [Fcitx5](https://fcitx-im.org/) service already running in every session.
|
||||
Use `Super + I` to cycle through input methods or keyboard layouts, and `Super + Shift + I` to go back the other way, so with three configured you can move between two neighbours. The input indicator appears on the right of the bar only when more than one input method or layout is configured. This desktop binding works alongside your existing Fcitx switching keys; you can override it in `~/.config/hypr/bindings.lua`. Tapping Shift alone is not an input switch: Omarchy uses both Shift keys for CapsLock, which changes how input methods see Shift release. Input engines and the CapsLock compose sequences use the [Fcitx5](https://fcitx-im.org/) service already running in every session.
|
||||
|
||||
### Use ALT as SUPER
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ Singleton {
|
||||
id: root
|
||||
property var state: ({})
|
||||
|
||||
function cycle() {
|
||||
function cycle(back) {
|
||||
if (!watcher.running) return false
|
||||
watcher.write("cycle\n")
|
||||
watcher.write(back ? "cycle back\n" : "cycle\n")
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1781,8 +1781,8 @@ ShellRoot {
|
||||
|
||||
// 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 cycleInput(direction: string): string {
|
||||
return Commons.InputMethodState.cycle(direction === "back") ? "ok" : "not running"
|
||||
}
|
||||
|
||||
function rescanPlugins(): void {
|
||||
|
||||
@@ -133,5 +133,7 @@ TEST_KEYBOARD_ONLY=true TEST_FCITX5_CONFIG=$freed assert_fires "keyboard-only pr
|
||||
|
||||
assert_fires "Super + I cycles configured input independently of custom Fcitx keys" \
|
||||
"exec omarchy-input-method cycle" "SUPER + I" "chromium"
|
||||
assert_fires "Super + Shift + I cycles configured input back" \
|
||||
"exec omarchy-input-method cycle back" "SUPER + SHIFT + I" "chromium"
|
||||
TEST_KEYBOARD_ONLY=true assert_fires "Super + I cycles keyboard layouts without an input engine" \
|
||||
"exec omarchy-input-method cycle" "SUPER + I" "foot" "terminal"
|
||||
@@ -176,6 +176,16 @@ with patch.object(indicator, "snapshot", side_effect=indicator.GLib.Error("no fc
|
||||
assert keyboards[0]["active_layout_index"] == 1
|
||||
print("ok - the shortcut reader cycles compositor layouts when Fcitx is unavailable")
|
||||
|
||||
keyboards = [{"name": "physical", "layout": "us,dk", "active_layout_index": 0}]
|
||||
state = {"methods": ["keyboard-us", "mozc", "hangul"], "current": "keyboard-us"}
|
||||
assert indicator.cycle_choice(state, keyboards) == ("keyboard-us", 1)
|
||||
assert indicator.cycle_choice(state, keyboards, -1) == ("hangul", None)
|
||||
state["current"] = "mozc"
|
||||
assert indicator.cycle_choice(state, keyboards, -1) == ("keyboard-us", 1)
|
||||
assert indicator.cycle_choice({"methods": ["keyboard-us", "mozc", "hangul"], "current": "keyboard-us"}, [], -1) == ("hangul", None)
|
||||
assert indicator.layout_switches(keyboards, step=-1)[0][-1] == "1"
|
||||
print("ok - cycling back walks the same choices in reverse")
|
||||
|
||||
PY
|
||||
|
||||
stubs=$(mktemp -d)
|
||||
@@ -193,10 +203,18 @@ 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")"
|
||||
[[ $(<"$calls") == "shell shell cycleInput next" ]] || 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")"
|
||||
[[ $(<"$calls") == $'shell shell cycleInput next\npython indicator.py cycle next' ]] || fail "Super+I switches directly without the shell" "$(<"$calls")"
|
||||
pass "Super+I switches directly when the shell does not answer"
|
||||
|
||||
: >"$calls"
|
||||
CALLS=$calls SHELL_REPLY="" PATH="$stubs:$PATH" OMARCHY_PATH="$ROOT" "$ROOT/bin/omarchy-input-method" cycle back
|
||||
[[ $(<"$calls") == $'shell shell cycleInput back\npython indicator.py cycle back' ]] || fail "Super+Shift+I cycles back" "$(<"$calls")"
|
||||
if CALLS=$calls PATH="$stubs:$PATH" OMARCHY_PATH="$ROOT" "$ROOT/bin/omarchy-input-method" cycle sideways 2>/dev/null; then
|
||||
fail "an unknown cycle direction is rejected"
|
||||
fi
|
||||
pass "Super+Shift+I cycles back through the shell or directly, and other directions are rejected"
|
||||
Reference in new issue
Block a user