Repository navigation
fix(hypr): resolve universal clipboard letters from primary layout #13007
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: quattro
Are you sure you want to change the base?
Changes from all commits
290d0ed
0890c28
8692342
816ab6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,18 +32,99 @@ local function active_window_is_terminal() | |
| return false | ||
| end | ||
|
|
||
| local keycode_cache = {} | ||
|
|
||
| local function add_xkb_option(command, name, value) | ||
| if value ~= "" then | ||
| table.insert(command, "--" .. name) | ||
| table.insert(command, o.shell_quote(value)) | ||
| end | ||
| end | ||
|
|
||
| -- Hyprland resolves a named key against the active XKB group. That fails when | ||
| -- the active group has no Latin C/V/X/A keysym. Resolve the logical shortcut | ||
| -- letter against the configured primary group instead, then inject that XKB | ||
| -- keycode. This keeps non-Latin secondary groups working without assuming the | ||
| -- primary layout is QWERTY (Dvorak/AZERTY included). | ||
| local function resolve_primary_keycode(key) | ||
| -- A keymap file overrides the kb_* rules, so they cannot say where the letter is. | ||
| if tostring(hl.get_config("input.kb_file") or "") ~= "" then | ||
| return key | ||
| end | ||
|
|
||
| local config = { | ||
| layout = tostring(hl.get_config("input.kb_layout") or "us"), | ||
| variant = tostring(hl.get_config("input.kb_variant") or ""), | ||
| model = tostring(hl.get_config("input.kb_model") or ""), | ||
| options = tostring(hl.get_config("input.kb_options") or ""), | ||
| rules = tostring(hl.get_config("input.kb_rules") or ""), | ||
| } | ||
| local cache_key = table.concat({ | ||
| key, | ||
| config.layout, | ||
| config.variant, | ||
| config.model, | ||
| config.options, | ||
| config.rules, | ||
| }, "\0") | ||
|
|
||
| if keycode_cache[cache_key] then | ||
| return keycode_cache[cache_key] | ||
| end | ||
|
|
||
| local command = { "xkbcli how-to-type" } | ||
| add_xkb_option(command, "layout", config.layout) | ||
| add_xkb_option(command, "variant", config.variant) | ||
| add_xkb_option(command, "model", config.model) | ||
| add_xkb_option(command, "options", config.options) | ||
| add_xkb_option(command, "rules", config.rules) | ||
| table.insert(command, "--keysym") | ||
| table.insert(command, o.shell_quote(key:lower())) | ||
|
|
||
| local pipe = io.popen(table.concat(command, " ") .. " 2>/dev/null") | ||
| if not pipe then | ||
| keycode_cache[cache_key] = key | ||
| return key | ||
| end | ||
|
|
||
| local output = pipe:read("*a") or "" | ||
| pipe:close() | ||
|
Comment on lines
+84
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Each uncached shortcut runs Move this work out of the keypress callback, or resolve asynchronously and cache the result. |
||
|
|
||
| -- xkbcli numbers layouts and levels from 1. Take the unshifted letter on the | ||
| -- primary layout even if another group is currently active. | ||
| for line in output:gmatch("[^\r\n]+") do | ||
| local keycode, layout_index, level = line:match("^%s*(%d+)%s+%S+%s+(%d+)%s.-%s(%d+)%s+%[") | ||
| if layout_index == "1" and level == "1" then | ||
| local resolved = "code:" .. keycode | ||
| keycode_cache[cache_key] = resolved | ||
| return resolved | ||
| end | ||
| end | ||
|
|
||
| -- Preserve the existing named-key behavior if xkbcli is unavailable or the | ||
| -- configured keymap cannot resolve this symbol. Do not guess a QWERTY code. | ||
| keycode_cache[cache_key] = key | ||
| return key | ||
| end | ||
|
|
||
| local function resolved_shortcut(mods, key) | ||
| return function() | ||
| send_shortcut_once(mods, resolve_primary_keycode(key))() | ||
| end | ||
| end | ||
|
|
||
| local function universal_clipboard_shortcut(default_mods, default_key, terminal_mods, terminal_key) | ||
| return function() | ||
| if active_window_is_terminal() then | ||
| send_shortcut_once(terminal_mods, terminal_key)() | ||
| send_shortcut_once(terminal_mods, resolve_primary_keycode(terminal_key))() | ||
| else | ||
| send_shortcut_once(default_mods, default_key)() | ||
| send_shortcut_once(default_mods, resolve_primary_keycode(default_key))() | ||
| end | ||
| end | ||
| end | ||
|
|
||
| o.bind("SUPER + A", "Select all", send_shortcut_once("CTRL", "A")) | ||
| o.bind("SUPER + A", "Select all", resolved_shortcut("CTRL", "A")) | ||
| o.bind("SUPER + C", "Universal copy", universal_clipboard_shortcut("CTRL", "C", "CTRL SHIFT", "C")) | ||
| o.bind("SUPER + V", "Universal paste", universal_clipboard_shortcut("CTRL", "V", "CTRL SHIFT", "V")) | ||
| o.bind("SUPER + X", "Universal cut", send_shortcut_once("CTRL", "X")) | ||
| o.bind("SUPER + X", "Universal cut", resolved_shortcut("CTRL", "X")) | ||
| o.bind("SUPER + CTRL + V", "Clipboard manager", { panel = "omarchy.clipboard" }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolve_primary_keycodereads only globalinput.kb_*settings. If a keyboard has its own Dvorak layout while the global layout isus, copy resolves tocode:54instead of Dvorak'scode:31. The focused application receives the wrong shortcut.Resolve against the keyboard used for the injected event, including its overrides, and include those settings in the cache key.