修复 Windows 中德语和俄语键盘布局之间撤消 (Ctrl-Z) 和重做 (Ctrl-Y) 快捷键的物理位置

修复 Windows 中德语和俄语键盘布局之间撤消 (Ctrl-Z) 和重做 (Ctrl-Y) 快捷键的物理位置

我正在模仿另一个键盘布局混乱的受害者,他遭遇类似但略有不同的问题。

我在 Windows 10 工作站上广泛使用德语和俄语键盘布局,并经常在它们之间切换。问题在于,与标准英语 QWERTY 布局相比,德语键盘布局中的字母 Y 和 Z 被切换了。

如果我使用德文布局,则撤消快捷键(Ctrl-Z)将绑定到键 Z,该键位于 T 和 U 之间第一个字母行的中间。重做(Ctrl-Y)将绑定到键 Y,该键位于德文 QWERTZ 布局下行字母中 X 的左侧。

如果您将布局切换到俄语,突然发生了一件坏事。撤消和重做快捷键在键盘上的位置会相互改变。撤消(Ctrl-Z)现在位于 Y(下排,X 左侧),而重做位于 Z(上排,T 和 U 之间)。此位置与标准英语键盘布局上 Ctrl-Z 和 Ctrl-Y 的位置相同。

这意味着,每当我切换键盘布局时,撤销和重做的快捷键的位置都会改变。你可以想象我总是按错快捷键,因为不可能学习两种相反的模式,至少我的脊椎大脑是抗议的。由于我经常使用撤销和重做的快捷键,这种行为让我很恼火。

我想将整个 Windows 应用程序中重做和撤消快捷方式的物理位置(键)修复为德语或更好的英语布局。

虽然对所有类型的拉丁文本永久使用英语布局可以解决跳过撤消/重做快捷方式的问题,但对我来说不是一个选择,因为我需要输入带有所有变音符号和ß的德语文本(不建议使用国际布局来输入重音符号,因为德语是我的母语,我想用一个键输入它的母语字母)。

正如引用问题中的同事已经提到的那样,互联网上没有明显的答案。通过创建自定义键盘布局,在 Ubuntu 上解决了同样的问题。我找不到有关为 Windows 创建自定义布局或修补当前布局的任何信息。

这种行为在我所知道的所有 Windows 版本中都是一致的,并且与最初的键盘布局设计有着密切的关系,只是对于像我一样恰好使用德语-俄语语言对的用户来说不兼容。

好的解决方案应该直接在 Windows 中实现,无需任何附加软件,或者仅使用免费的开源软件。欢迎使用涉及 AutoHotKey 的工作解决方案。

在@miroxlav 的帮助下我想出了这个半工作的 AHK 脚本:

; Undo
$^z::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4070409)   ; revert undo-redo hotkey mapping if in German layout
        Send ^y
    Else
        Send ^н            ; pass the keystroke through
Return

; Redo
$^y::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4070409)   ; revert undo-redo hotkey mapping if in German layout
        Send ^z
    Else
        Send ^я            ; pass the keystroke through
Return

此脚本完美地重新映射了德语布局中的 Ctrl-Y <-> Ctrl-Z,但破坏了俄语布局中的两个热键。我使用 ^н 和 ^я 希望模拟俄语布局中的 Ctrl-Y 或 Ctrl-Z 扫描码等效项,但它不起作用(为 Ctrl-Y 生成 н,而为 Ctrl-Z 则不可见)。在俄语中使用 ^z 和 ^y 也不起作用,分别生成字母 z 和 y。

与 Miroxlav 的回答不同,我不得不使用键盘钩子 ($) 来避免循环并删除标签(可能是复制粘贴错误)

Williams 建议的解决方案:

^SC02c::Send ^y
^SC015::Send ^z

不幸的是,它也不能正常工作。它在俄文版面中生成纯字母“y”和“z”,而在德文版面中则不发生任何变化(原始映射中的 Ctrl-Z 和 Ctrl-Y 被传递)。

仍在寻找解决方案。

答案1

您可以使用以下方式轻松创建自己的键盘布局Microsoft 键盘布局创建器 1.4 来自 Microsoft 官方下载中心

Youtube 提供了大量如何使用它的视频教程(例如带有俄语语言环境)-只需搜索其名称即可。但基本上,您可以克隆现有键盘布局之一并根据您的需要进行修改。

AutoHotKey解决方案:

(注意:0x4090409用适合您的布局的值替换常量(美国键盘))

$^z::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4090409)
        Send ^z
    Else
        Send ^y
Return

$^y::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4090409)
        Send ^y
    Else
        Send ^z
Return

需要 Hook ($) 来避免递归,即Send ^z调用Send ^y另一个 AHK 宏。

我测试了键盘检测条件,它运行良好。

当然,您可以将键盘检测放入函数中,从而优化代码等。我懒得这么做。:)

答案2

我终于找到了一个适合我自己的解决方案(AutoHotKey 脚本):

; Undo Ctrl-Z
^sc02C::
    Send, ^{sc015}
Return

; Redo Ctrl-Y
^sc015::
    Send, ^{sc02C}
Return

它完美地将撤消键(Ctrl-Z)保持在其逻辑位置 - 左下行 - 独立于键盘布局。

感谢@Miroxlav 和@Williams 提供解决方案。

答案3

根据 Anton 的回答,我已将代码更新为 AHK v2,并添加了对 Ctrl+Shift 变体的支持,因为现在许多程序都使用它来重做。

#Requires AutoHotkey v2.0

;Remapping upper mid Y/Z key (Y in EN layout)
^sc015::Send "^{sc02C}"
^+sc015::Send "^+{sc02C}"

;Remapping lower left Y/Z key (Z in EN layout)
^sc02C::Send "^{sc015}"
^+sc02C::Send "^+{sc015}"

此脚本的目的是将“撤消”操作固定到左下角的 Y/Z 键,因为不同的布局(QWERTZ 与 QWERTY)将 Z 移到中上部,这是不切实际的。“重做”也是如此,现在已固定到中上部 Y/Z 键。

由于撤销/重做通常由 Ctrl+Z/Y 执行,因此可以通过捕获按下的键并重新发出“正确”键来实现。这些就是这些^sc0**行。这些^+sc0**行用于重新发出 Ctrl+Shift+Z/Y 事件,因为许多程序使用 Ctrl+Shift+Z 来重做。

“正确”键之所以用引号引起来,是因为如果你看一下脚本,它实际上应该只是到处翻转 Ctrl(+Shift)+Y/Z...因此什么也没实现。然而事实并非如此,它就是有效...尽管我和据我所知原作者都不知道为什么。

如果你对为什么这种方法有效或无效有任何见解...请大胆说出来

答案4

尽管接受的答案有效,但它只有当当时选择的布局是德式布局时才有效,否则它会翻转所有其他布局中的撤消功能。

要解决这个问题,你只需要用0x4090409德国代码替换美国代码0x4070407。你还可以像这样更改 if 语句:

$^z::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4070407)
        Send ^y
    Else
        Send ^z
Return

$^y::
    hWnd := WinExist("A")
    ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0)
    hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt")
    If (hKL = 0x4070407)
        Send ^z
    Else
        Send ^y
Return

相关内容