patch 9.0.1146: MS-Windows: various special keys/modifiers are not mappable

Problem:    MS-Windows: various special keys and modifiers are not mappable.
Solution:   Adjust the handling of keys with modifiers. (Christian Plewright,
            closes #11768)
This commit is contained in:
Christopher Plewright
2023-01-04 18:06:00 +00:00
committed by Bram Moolenaar
parent 3ac1d97a1d
commit c8b204952f
5 changed files with 115 additions and 71 deletions

View File

@ -1,5 +1,7 @@
version: "{build}" version: "{build}"
image: Visual Studio 2015
skip_tags: true skip_tags: true
environment: environment:

View File

@ -29,12 +29,12 @@ To build the installable .exe:
4. Get a "diff.exe" program. If you skip this the built-in diff will always 4. Get a "diff.exe" program. If you skip this the built-in diff will always
be used (which is fine for most users). If you do have your own be used (which is fine for most users). If you do have your own
"diff.exe" put it in the "../.." directory (above the "vim82" directory, "diff.exe" put it in the "../.." directory (above the "vim90" directory,
it's the same for all Vim versions). it's the same for all Vim versions).
You can find one in previous Vim versions or in this archive: You can find one in previous Vim versions or in this archive:
http://www.mossbayeng.com/~ron/vim/diffutils.tar.gz http://www.mossbayeng.com/~ron/vim/diffutils.tar.gz
5 Also put winpty32.dll and winpty-agent.exe in "../.." (above the "vim82" 5 Also put winpty32.dll and winpty-agent.exe in "../.." (above the "vim90"
directory). This is required for the terminal window. directory). This is required for the terminal window.
6. Do "make uganda.nsis.txt" in runtime/doc. This requires sed, you may have 6. Do "make uganda.nsis.txt" in runtime/doc. This requires sed, you may have

View File

@ -1042,7 +1042,8 @@ win32_kbd_patch_key(
return 1; return 1;
} }
if (pker->uChar.UnicodeChar > 0 && pker->uChar.UnicodeChar < 0xfffd) // check if it already has a valid unicode character.
if (pker->uChar.UnicodeChar > 0 && pker->uChar.UnicodeChar < 0xFFFD)
return 1; return 1;
CLEAR_FIELD(abKeystate); CLEAR_FIELD(abKeystate);
@ -1118,13 +1119,12 @@ decode_key_event(
{ {
if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode) if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode)
{ {
if (nModifs == 0) *pch = VirtKeyMap[i].chAlone;
*pch = VirtKeyMap[i].chAlone; if ((nModifs & SHIFT) != 0)
else if ((nModifs & SHIFT) != 0 && (nModifs & ~SHIFT) == 0)
*pch = VirtKeyMap[i].chShift; *pch = VirtKeyMap[i].chShift;
else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0) else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0)
*pch = VirtKeyMap[i].chCtrl; *pch = VirtKeyMap[i].chCtrl;
else if ((nModifs & ALT) != 0 && (nModifs & ~ALT) == 0) else if ((nModifs & ALT) != 0)
*pch = VirtKeyMap[i].chAlt; *pch = VirtKeyMap[i].chAlt;
if (*pch != 0) if (*pch != 0)
@ -1133,6 +1133,74 @@ decode_key_event(
{ {
*pch2 = *pch; *pch2 = *pch;
*pch = K_NUL; *pch = K_NUL;
if (pmodifiers)
{
if (pker->wVirtualKeyCode >= VK_F1
&& pker->wVirtualKeyCode <= VK_F12)
{
if ((nModifs & ALT) != 0)
{
*pmodifiers |= MOD_MASK_ALT;
if ((nModifs & SHIFT) == 0)
*pch2 = VirtKeyMap[i].chAlone;
}
if ((nModifs & CTRL) != 0)
{
*pmodifiers |= MOD_MASK_CTRL;
if ((nModifs & SHIFT) == 0)
*pch2 = VirtKeyMap[i].chAlone;
}
}
else if (pker->wVirtualKeyCode >= VK_END
&& pker->wVirtualKeyCode <= VK_DOWN)
{
// VK_END 0x23
// VK_HOME 0x24
// VK_LEFT 0x25
// VK_UP 0x26
// VK_RIGHT 0x27
// VK_DOWN 0x28
*pmodifiers = 0;
*pch2 = VirtKeyMap[i].chAlone;
if ((nModifs & SHIFT) != 0
&& (nModifs & ~SHIFT) == 0)
{
*pch2 = VirtKeyMap[i].chShift;
}
else if ((nModifs & CTRL) != 0
&& (nModifs & ~CTRL) == 0)
{
*pch2 = VirtKeyMap[i].chCtrl;
if (pker->wVirtualKeyCode == VK_UP
|| pker->wVirtualKeyCode == VK_DOWN)
{
*pmodifiers |= MOD_MASK_CTRL;
*pch2 = VirtKeyMap[i].chAlone;
}
}
else if ((nModifs & ALT) != 0
&& (nModifs & ~ALT) == 0)
{
*pch2 = VirtKeyMap[i].chAlt;
}
else if ((nModifs & SHIFT) != 0
&& (nModifs & CTRL) != 0)
{
*pmodifiers |= MOD_MASK_CTRL;
*pch2 = VirtKeyMap[i].chShift;
}
}
else
{
*pch2 = VirtKeyMap[i].chAlone;
if ((nModifs & SHIFT) != 0)
*pmodifiers |= MOD_MASK_SHIFT;
if ((nModifs & CTRL) != 0)
*pmodifiers |= MOD_MASK_CTRL;
if ((nModifs & ALT) != 0)
*pmodifiers |= MOD_MASK_ALT;
}
}
} }
return TRUE; return TRUE;
@ -1178,10 +1246,11 @@ encode_key_event(dict_T *args, INPUT_RECORD *ir)
{ {
static int s_dwMods = 0; static int s_dwMods = 0;
char_u *event = dict_get_string(args, "event", TRUE); char_u *action = dict_get_string(args, "event", TRUE);
if (event && (STRICMP(event, "keydown") == 0 if (action && (STRICMP(action, "keydown") == 0
|| STRICMP(event, "keyup") == 0)) || STRICMP(action, "keyup") == 0))
{ {
BOOL isKeyDown = STRICMP(action, "keydown") == 0;
WORD vkCode = dict_get_number_def(args, "keycode", 0); WORD vkCode = dict_get_number_def(args, "keycode", 0);
if (vkCode <= 0 || vkCode >= 0xFF) if (vkCode <= 0 || vkCode >= 0xFF)
{ {
@ -1192,7 +1261,7 @@ encode_key_event(dict_T *args, INPUT_RECORD *ir)
ir->EventType = KEY_EVENT; ir->EventType = KEY_EVENT;
KEY_EVENT_RECORD ker; KEY_EVENT_RECORD ker;
ZeroMemory(&ker, sizeof(ker)); ZeroMemory(&ker, sizeof(ker));
ker.bKeyDown = STRICMP(event, "keydown") == 0; ker.bKeyDown = isKeyDown;
ker.wRepeatCount = 1; ker.wRepeatCount = 1;
ker.wVirtualScanCode = 0; ker.wVirtualScanCode = 0;
ker.dwControlKeyState = 0; ker.dwControlKeyState = 0;
@ -1215,73 +1284,55 @@ encode_key_event(dict_T *args, INPUT_RECORD *ir)
if (vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_SHIFT) if (vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_SHIFT)
{ {
if (STRICMP(event, "keydown") == 0) if (isKeyDown)
s_dwMods |= SHIFT_PRESSED; s_dwMods |= SHIFT_PRESSED;
else else
s_dwMods &= ~SHIFT_PRESSED; s_dwMods &= ~SHIFT_PRESSED;
} }
else if (vkCode == VK_LCONTROL || vkCode == VK_CONTROL) else if (vkCode == VK_LCONTROL || vkCode == VK_CONTROL)
{ {
if (STRICMP(event, "keydown") == 0) if (isKeyDown)
s_dwMods |= LEFT_CTRL_PRESSED; s_dwMods |= LEFT_CTRL_PRESSED;
else else
s_dwMods &= ~LEFT_CTRL_PRESSED; s_dwMods &= ~LEFT_CTRL_PRESSED;
} }
else if (vkCode == VK_RCONTROL) else if (vkCode == VK_RCONTROL)
{ {
if (STRICMP(event, "keydown") == 0) if (isKeyDown)
s_dwMods |= RIGHT_CTRL_PRESSED; s_dwMods |= RIGHT_CTRL_PRESSED;
else else
s_dwMods &= ~RIGHT_CTRL_PRESSED; s_dwMods &= ~RIGHT_CTRL_PRESSED;
} }
else if (vkCode == VK_LMENU || vkCode == VK_MENU) else if (vkCode == VK_LMENU || vkCode == VK_MENU)
{ {
if (STRICMP(event, "keydown") == 0) if (isKeyDown)
s_dwMods |= LEFT_ALT_PRESSED; s_dwMods |= LEFT_ALT_PRESSED;
else else
s_dwMods &= ~LEFT_ALT_PRESSED; s_dwMods &= ~LEFT_ALT_PRESSED;
} }
else if (vkCode == VK_RMENU) else if (vkCode == VK_RMENU)
{ {
if (STRICMP(event, "keydown") == 0) if (isKeyDown)
s_dwMods |= RIGHT_ALT_PRESSED; s_dwMods |= RIGHT_ALT_PRESSED;
else else
s_dwMods &= ~RIGHT_ALT_PRESSED; s_dwMods &= ~RIGHT_ALT_PRESSED;
} }
ker.dwControlKeyState |= s_dwMods; ker.dwControlKeyState |= s_dwMods;
ker.wVirtualKeyCode = vkCode; ker.wVirtualKeyCode = vkCode;
win32_kbd_patch_key(&ker); ker.uChar.UnicodeChar = 0xFFFD; // UNICODE REPLACEMENT CHARACTER
for (int i = ARRAY_LENGTH(VirtKeyMap); i >= 0; --i)
{
if (VirtKeyMap[i].wVirtKey == vkCode)
{
ker.uChar.UnicodeChar = 0xfffd; // REPLACEMENT CHARACTER
break;
}
}
// The following are treated specially in Vim.
// Ctrl-6 is Ctrl-^
// Ctrl-2 is Ctrl-@
// Ctrl-- is Ctrl-_
if ((vkCode == 0xBD || vkCode == '2' || vkCode == '6')
&& (ker.dwControlKeyState & CTRL))
ker.uChar.UnicodeChar = 0xfffd; // REPLACEMENT CHARACTER
ir->Event.KeyEvent = ker; ir->Event.KeyEvent = ker;
vim_free(event); vim_free(action);
} }
else else
{ {
if (event == NULL) if (action == NULL)
{ {
semsg(_(e_missing_argument_str), "event"); semsg(_(e_missing_argument_str), "event");
} }
else else
{ {
semsg(_(e_invalid_value_for_argument_str_str), "event", event); semsg(_(e_invalid_value_for_argument_str_str), "event", action);
vim_free(event); vim_free(action);
} }
return FALSE; return FALSE;
} }
@ -2432,6 +2483,8 @@ mch_inchar(
c = tgetch(&modifiers, &ch2); c = tgetch(&modifiers, &ch2);
c = simplify_key(c, &modifiers);
// Some chars need adjustment when the Ctrl modifier is used. // Some chars need adjustment when the Ctrl modifier is used.
++no_reduce_keys; ++no_reduce_keys;
c = may_adjust_key_for_ctrl(modifiers, c); c = may_adjust_key_for_ctrl(modifiers, c);

View File

@ -3,7 +3,6 @@
source check.vim source check.vim
CheckMSWindows CheckMSWindows
source mouse.vim source mouse.vim
" Helper function for sending a grouped sequence of low level key presses " Helper function for sending a grouped sequence of low level key presses
@ -54,7 +53,8 @@ func ExecuteBufferedKeys()
endif endif
endfunc endfunc
" Refer to the following page for the virtual key codes:
" https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
let s:VK = { let s:VK = {
\ 'ENTER' : 0x0D, \ 'ENTER' : 0x0D,
\ 'SPACE' : 0x20, \ 'SPACE' : 0x20,
@ -296,11 +296,9 @@ let s:VK = {
\ [[s:VK.CONTROL, s:VK.OEM_4], 0x1B], \ [[s:VK.CONTROL, s:VK.OEM_4], 0x1B],
\ [[s:VK.CONTROL, s:VK.OEM_5], 0x1C], \ [[s:VK.CONTROL, s:VK.OEM_5], 0x1C],
\ [[s:VK.CONTROL, s:VK.OEM_6], 0x1D], \ [[s:VK.CONTROL, s:VK.OEM_6], 0x1D],
\ [[s:VK.CONTROL, s:VK.KEY_6], 0x1E],
\ [[s:VK.CONTROL, s:VK.OEM_MINUS], 0x1F],
\ ] \ ]
" The following non-printable ascii chars fail in the GUI, but work in the
" console. 0x1e [^^] Record separator (RS), and 0x1f [^_] Unit separator (US)
" \ [[s:VK.CONTROL, s:VK.SHIFT, s:VK.KEY_6], 0x1E],
" \ [[s:VK.CONTROL, s:VK.SHIFT, s:VK.OEM_MINUS], 0x1F],
let s:test_extra_key_chars = [ let s:test_extra_key_chars = [
\ [[s:VK.ALT, s:VK.KEY_1], '±'], \ [[s:VK.ALT, s:VK.KEY_1], '±'],
@ -342,7 +340,7 @@ let s:test_extra_key_chars = [
\ ] \ ]
func s:LoopTestKeyArray(arr) func s:LoopTestKeyArray(arr)
" flush out any garbage left in the buffer " flush out anything in the typeahead buffer
while getchar(0) while getchar(0)
endwhile endwhile
@ -351,7 +349,7 @@ func s:LoopTestKeyArray(arr)
call SendKeyGroup(kcodes) call SendKeyGroup(kcodes)
let ch = getcharstr(0) let ch = getcharstr(0)
" need to deal a bit differently with the non-printable ascii chars < 0x20 " need to deal a bit differently with the non-printable ascii chars < 0x20
if kstr < 0x20 && index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], kcodes[0]) >= 0 if kstr < 0x20 && index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], kcodes[0]) >= 0
call assert_equal(nr2char(kstr), $"{ch}") call assert_equal(nr2char(kstr), $"{ch}")
else else
call assert_equal(kstr, $"{ch}") call assert_equal(kstr, $"{ch}")
@ -389,7 +387,7 @@ func s:LoopTestKeyArray(arr)
call assert_equal(0, mod_mask, $"key = {kstr}") call assert_equal(0, mod_mask, $"key = {kstr}")
endfor endfor
" flush out any garbage left in the buffer " flush out anything in the typeahead buffer
while getchar(0) while getchar(0)
endwhile endwhile
@ -489,29 +487,23 @@ func Test_mswin_key_event()
endfor endfor
endif endif
" Windows intercepts some of these keys in the GUI " Test for Function Keys 'F1' to 'F12'
" VK codes 112(0x70) - 123(0x7B)
" Also with ALL permutatios of modifiers; Shift, Ctrl & Alt
" NOTE: Windows intercepts some of these keys in the GUI
if !has("gui_running") if !has("gui_running")
" Test for Function Keys 'F1' to 'F12'
for n in range(1, 12)
let kstr = $"F{n}"
let keycode = eval('"\<' .. kstr .. '>"')
call SendKey(111+n)
let ch = getcharstr(0)
call assert_equal(keycode, $"{ch}", $"key = <{kstr}>")
endfor
" NOTE: mod + Fn Keys not working in CI Testing!?
" Test for Function Keys 'F1' to 'F12'
" VK codes 112(0x70) - 123(0x7B)
" With ALL permutatios of modifiers; Shift, Ctrl & Alt
for [mod_str, vim_mod_mask, mod_keycodes] in s:vim_key_modifiers for [mod_str, vim_mod_mask, mod_keycodes] in s:vim_key_modifiers
for n in range(1, 12) for n in range(1, 12)
let kstr = $"{mod_str}F{n}" let kstr = $"{mod_str}F{n}"
let keycode = eval('"\<' .. kstr .. '>"') let keycode = eval('"\<' .. kstr .. '>"')
" flush out anything in the typeahead buffer
while getchar(0)
endwhile
" call SendKeyGroup(mod_keycodes + [111+n]) " call SendKeyGroup(mod_keycodes + [111+n])
call SendKeyWithModifiers(111+n, vim_mod_mask) call SendKeyWithModifiers(111+n, vim_mod_mask)
let ch = getcharstr(0) let ch = getcharstr(0)
let mod_mask = getcharmod() let mod_mask = getcharmod()
"""""" call assert_equal(keycode, $"{ch}", $"key = {kstr}") call assert_equal(keycode, $"{ch}", $"key = {kstr}")
" workaround for the virtual termcap maps changing the character instead " workaround for the virtual termcap maps changing the character instead
" of sending Shift " of sending Shift
for mod_key in mod_keycodes for mod_key in mod_keycodes
@ -519,14 +511,12 @@ func Test_mswin_key_event()
let mod_mask = mod_mask + s:vim_MOD_MASK_SHIFT let mod_mask = mod_mask + s:vim_MOD_MASK_SHIFT
endif endif
endfor endfor
""""""call assert_equal(vim_mod_mask, mod_mask, $"mod = {vim_mod_mask} for key = {kstr}") call assert_equal(vim_mod_mask, mod_mask, $"mod = {vim_mod_mask} for key = {kstr}")
endfor endfor
endfor endfor
endif endif
" Test for the various Ctrl and Shift key combinations. " Test for the various Ctrl and Shift key combinations.
" Refer to the following page for the virtual key codes:
" https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
let keytests = [ let keytests = [
\ [[s:VK.SHIFT, s:VK.PRIOR], "S-Pageup", 2], \ [[s:VK.SHIFT, s:VK.PRIOR], "S-Pageup", 2],
\ [[s:VK.LSHIFT, s:VK.PRIOR], "S-Pageup", 2], \ [[s:VK.LSHIFT, s:VK.PRIOR], "S-Pageup", 2],
@ -586,14 +576,13 @@ func Test_mswin_key_event()
\ [[s:VK.CONTROL, s:VK.OEM_MINUS], "C-_", 0] \ [[s:VK.CONTROL, s:VK.OEM_MINUS], "C-_", 0]
\ ] \ ]
" Not working in CI Testing yet!?
for [kcodes, kstr, kmod] in keytests for [kcodes, kstr, kmod] in keytests
call SendKeyGroup(kcodes) call SendKeyGroup(kcodes)
let ch = getcharstr(0) let ch = getcharstr(0)
let mod = getcharmod() let mod = getcharmod()
let keycode = eval('"\<' .. kstr .. '>"') let keycode = eval('"\<' .. kstr .. '>"')
" call assert_equal(keycode, ch, $"key = {kstr}") call assert_equal(keycode, ch, $"key = {kstr}")
" call assert_equal(kmod, mod, $"mod = {kmod} key = {kstr}") call assert_equal(kmod, mod, $"mod = {kmod} key = {kstr}")
endfor endfor
bw! bw!
@ -634,8 +623,6 @@ func Test_QWERTY_Ctrl_minus()
call ExecuteBufferedKeys() call ExecuteBufferedKeys()
call assert_equal('BILBO', getline('$')) call assert_equal('BILBO', getline('$'))
imapclear imapclear
bw! bw!
endfunc endfunc
@ -953,7 +940,7 @@ func Test_mswin_event_error_handling()
call assert_fails("sandbox call test_mswin_event('key', {'event': 'keydown', 'keycode': 61 })", 'E48:') call assert_fails("sandbox call test_mswin_event('key', {'event': 'keydown', 'keycode': 61 })", 'E48:')
" flush out any garbage left in the buffer. " flush out anything in the typeahead buffer
while getchar(0) while getchar(0)
endwhile endwhile
endfunc endfunc

View File

@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1146,
/**/ /**/
1145, 1145,
/**/ /**/