To: vim_dev@googlegroups.com Subject: Patch 9.0.1146 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.1146 Problem: MS-Windows: various special keys and modifiers are not mappable. Solution: Adjust the handling of keys with modifiers. (Christian Plewright, closes #11768) Files: .appveyor.yml, nsis/README.txt, src/os_win32.c, src/testdir/test_mswin_event.vim *** ../vim-9.0.1145/.appveyor.yml 2022-12-30 17:28:08.409002765 +0000 --- .appveyor.yml 2023-01-04 17:50:22.581409157 +0000 *************** *** 1,5 **** --- 1,7 ---- version: "{build}" + image: Visual Studio 2015 + skip_tags: true environment: *** ../vim-9.0.1145/nsis/README.txt 2022-10-08 17:12:16.735701562 +0100 --- nsis/README.txt 2023-01-04 17:50:22.581409157 +0000 *************** *** 29,40 **** 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 ! "diff.exe" put it in the "../.." directory (above the "vim82" directory, it's the same for all Vim versions). You can find one in previous Vim versions or in this archive: http://www.mossbayeng.com/~ron/vim/diffutils.tar.gz ! 5 Also put winpty32.dll and winpty-agent.exe in "../.." (above the "vim82" directory). This is required for the terminal window. 6. Do "make uganda.nsis.txt" in runtime/doc. This requires sed, you may have --- 29,40 ---- 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 ! "diff.exe" put it in the "../.." directory (above the "vim90" directory, it's the same for all Vim versions). You can find one in previous Vim versions or in this archive: http://www.mossbayeng.com/~ron/vim/diffutils.tar.gz ! 5 Also put winpty32.dll and winpty-agent.exe in "../.." (above the "vim90" directory). This is required for the terminal window. 6. Do "make uganda.nsis.txt" in runtime/doc. This requires sed, you may have *** ../vim-9.0.1145/src/os_win32.c 2022-12-30 16:54:53.452987924 +0000 --- src/os_win32.c 2023-01-04 18:02:16.868357513 +0000 *************** *** 1042,1048 **** return 1; } ! if (pker->uChar.UnicodeChar > 0 && pker->uChar.UnicodeChar < 0xfffd) return 1; CLEAR_FIELD(abKeystate); --- 1042,1049 ---- return 1; } ! // check if it already has a valid unicode character. ! if (pker->uChar.UnicodeChar > 0 && pker->uChar.UnicodeChar < 0xFFFD) return 1; CLEAR_FIELD(abKeystate); *************** *** 1118,1130 **** { if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode) { ! if (nModifs == 0) ! *pch = VirtKeyMap[i].chAlone; ! else if ((nModifs & SHIFT) != 0 && (nModifs & ~SHIFT) == 0) *pch = VirtKeyMap[i].chShift; else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0) *pch = VirtKeyMap[i].chCtrl; ! else if ((nModifs & ALT) != 0 && (nModifs & ~ALT) == 0) *pch = VirtKeyMap[i].chAlt; if (*pch != 0) --- 1119,1130 ---- { if (VirtKeyMap[i].wVirtKey == pker->wVirtualKeyCode) { ! *pch = VirtKeyMap[i].chAlone; ! if ((nModifs & SHIFT) != 0) *pch = VirtKeyMap[i].chShift; else if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0) *pch = VirtKeyMap[i].chCtrl; ! else if ((nModifs & ALT) != 0) *pch = VirtKeyMap[i].chAlt; if (*pch != 0) *************** *** 1133,1138 **** --- 1133,1206 ---- { *pch2 = *pch; *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; *************** *** 1178,1187 **** { static int s_dwMods = 0; ! char_u *event = dict_get_string(args, "event", TRUE); ! if (event && (STRICMP(event, "keydown") == 0 ! || STRICMP(event, "keyup") == 0)) { WORD vkCode = dict_get_number_def(args, "keycode", 0); if (vkCode <= 0 || vkCode >= 0xFF) { --- 1246,1256 ---- { static int s_dwMods = 0; ! char_u *action = dict_get_string(args, "event", TRUE); ! if (action && (STRICMP(action, "keydown") == 0 ! || STRICMP(action, "keyup") == 0)) { + BOOL isKeyDown = STRICMP(action, "keydown") == 0; WORD vkCode = dict_get_number_def(args, "keycode", 0); if (vkCode <= 0 || vkCode >= 0xFF) { *************** *** 1192,1198 **** ir->EventType = KEY_EVENT; KEY_EVENT_RECORD ker; ZeroMemory(&ker, sizeof(ker)); ! ker.bKeyDown = STRICMP(event, "keydown") == 0; ker.wRepeatCount = 1; ker.wVirtualScanCode = 0; ker.dwControlKeyState = 0; --- 1261,1267 ---- ir->EventType = KEY_EVENT; KEY_EVENT_RECORD ker; ZeroMemory(&ker, sizeof(ker)); ! ker.bKeyDown = isKeyDown; ker.wRepeatCount = 1; ker.wVirtualScanCode = 0; ker.dwControlKeyState = 0; *************** *** 1215,1287 **** if (vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_SHIFT) { ! if (STRICMP(event, "keydown") == 0) s_dwMods |= SHIFT_PRESSED; else s_dwMods &= ~SHIFT_PRESSED; } else if (vkCode == VK_LCONTROL || vkCode == VK_CONTROL) { ! if (STRICMP(event, "keydown") == 0) s_dwMods |= LEFT_CTRL_PRESSED; else s_dwMods &= ~LEFT_CTRL_PRESSED; } else if (vkCode == VK_RCONTROL) { ! if (STRICMP(event, "keydown") == 0) s_dwMods |= RIGHT_CTRL_PRESSED; else s_dwMods &= ~RIGHT_CTRL_PRESSED; } else if (vkCode == VK_LMENU || vkCode == VK_MENU) { ! if (STRICMP(event, "keydown") == 0) s_dwMods |= LEFT_ALT_PRESSED; else s_dwMods &= ~LEFT_ALT_PRESSED; } else if (vkCode == VK_RMENU) { ! if (STRICMP(event, "keydown") == 0) s_dwMods |= RIGHT_ALT_PRESSED; else s_dwMods &= ~RIGHT_ALT_PRESSED; } ker.dwControlKeyState |= s_dwMods; ker.wVirtualKeyCode = vkCode; ! win32_kbd_patch_key(&ker); ! ! 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; ! vim_free(event); } else { ! if (event == NULL) { semsg(_(e_missing_argument_str), "event"); } else { ! semsg(_(e_invalid_value_for_argument_str_str), "event", event); ! vim_free(event); } return FALSE; } --- 1284,1338 ---- if (vkCode == VK_LSHIFT || vkCode == VK_RSHIFT || vkCode == VK_SHIFT) { ! if (isKeyDown) s_dwMods |= SHIFT_PRESSED; else s_dwMods &= ~SHIFT_PRESSED; } else if (vkCode == VK_LCONTROL || vkCode == VK_CONTROL) { ! if (isKeyDown) s_dwMods |= LEFT_CTRL_PRESSED; else s_dwMods &= ~LEFT_CTRL_PRESSED; } else if (vkCode == VK_RCONTROL) { ! if (isKeyDown) s_dwMods |= RIGHT_CTRL_PRESSED; else s_dwMods &= ~RIGHT_CTRL_PRESSED; } else if (vkCode == VK_LMENU || vkCode == VK_MENU) { ! if (isKeyDown) s_dwMods |= LEFT_ALT_PRESSED; else s_dwMods &= ~LEFT_ALT_PRESSED; } else if (vkCode == VK_RMENU) { ! if (isKeyDown) s_dwMods |= RIGHT_ALT_PRESSED; else s_dwMods &= ~RIGHT_ALT_PRESSED; } ker.dwControlKeyState |= s_dwMods; ker.wVirtualKeyCode = vkCode; ! ker.uChar.UnicodeChar = 0xFFFD; // UNICODE REPLACEMENT CHARACTER ir->Event.KeyEvent = ker; ! vim_free(action); } else { ! if (action == NULL) { semsg(_(e_missing_argument_str), "event"); } else { ! semsg(_(e_invalid_value_for_argument_str_str), "event", action); ! vim_free(action); } return FALSE; } *************** *** 2432,2437 **** --- 2483,2490 ---- c = tgetch(&modifiers, &ch2); + c = simplify_key(c, &modifiers); + // Some chars need adjustment when the Ctrl modifier is used. ++no_reduce_keys; c = may_adjust_key_for_ctrl(modifiers, c); *** ../vim-9.0.1145/src/testdir/test_mswin_event.vim 2022-12-30 16:54:53.456987927 +0000 --- src/testdir/test_mswin_event.vim 2023-01-04 18:03:22.788504616 +0000 *************** *** 3,9 **** source check.vim CheckMSWindows - source mouse.vim " Helper function for sending a grouped sequence of low level key presses --- 3,8 ---- *************** *** 54,60 **** endif endfunc ! let s:VK = { \ 'ENTER' : 0x0D, \ 'SPACE' : 0x20, --- 53,60 ---- endif 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 = { \ 'ENTER' : 0x0D, \ 'SPACE' : 0x20, *************** *** 296,306 **** \ [[s:VK.CONTROL, s:VK.OEM_4], 0x1B], \ [[s:VK.CONTROL, s:VK.OEM_5], 0x1C], \ [[s:VK.CONTROL, s:VK.OEM_6], 0x1D], \ ] - " 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 = [ \ [[s:VK.ALT, s:VK.KEY_1], '±'], --- 296,304 ---- \ [[s:VK.CONTROL, s:VK.OEM_4], 0x1B], \ [[s:VK.CONTROL, s:VK.OEM_5], 0x1C], \ [[s:VK.CONTROL, s:VK.OEM_6], 0x1D], + \ [[s:VK.CONTROL, s:VK.KEY_6], 0x1E], + \ [[s:VK.CONTROL, s:VK.OEM_MINUS], 0x1F], \ ] let s:test_extra_key_chars = [ \ [[s:VK.ALT, s:VK.KEY_1], '±'], *************** *** 342,348 **** \ ] func s:LoopTestKeyArray(arr) ! " flush out any garbage left in the buffer while getchar(0) endwhile --- 340,346 ---- \ ] func s:LoopTestKeyArray(arr) ! " flush out anything in the typeahead buffer while getchar(0) endwhile *************** *** 351,357 **** call SendKeyGroup(kcodes) let ch = getcharstr(0) " 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 call assert_equal(nr2char(kstr), $"{ch}") else call assert_equal(kstr, $"{ch}") --- 349,355 ---- call SendKeyGroup(kcodes) let ch = getcharstr(0) " 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 call assert_equal(nr2char(kstr), $"{ch}") else call assert_equal(kstr, $"{ch}") *************** *** 389,395 **** call assert_equal(0, mod_mask, $"key = {kstr}") endfor ! " flush out any garbage left in the buffer while getchar(0) endwhile --- 387,393 ---- call assert_equal(0, mod_mask, $"key = {kstr}") endfor ! " flush out anything in the typeahead buffer while getchar(0) endwhile *************** *** 489,517 **** endfor endif ! " Windows intercepts some of these keys in the GUI 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 n in range(1, 12) let kstr = $"{mod_str}F{n}" let keycode = eval('"\<' .. kstr .. '>"') " call SendKeyGroup(mod_keycodes + [111+n]) call SendKeyWithModifiers(111+n, vim_mod_mask) let ch = getcharstr(0) let mod_mask = getcharmod() ! """""" call assert_equal(keycode, $"{ch}", $"key = {kstr}") " workaround for the virtual termcap maps changing the character instead " of sending Shift for mod_key in mod_keycodes --- 487,509 ---- endfor endif ! " 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") for [mod_str, vim_mod_mask, mod_keycodes] in s:vim_key_modifiers for n in range(1, 12) let kstr = $"{mod_str}F{n}" let keycode = eval('"\<' .. kstr .. '>"') + " flush out anything in the typeahead buffer + while getchar(0) + endwhile " call SendKeyGroup(mod_keycodes + [111+n]) call SendKeyWithModifiers(111+n, vim_mod_mask) let ch = getcharstr(0) let mod_mask = getcharmod() ! call assert_equal(keycode, $"{ch}", $"key = {kstr}") " workaround for the virtual termcap maps changing the character instead " of sending Shift for mod_key in mod_keycodes *************** *** 519,532 **** let mod_mask = mod_mask + s:vim_MOD_MASK_SHIFT endif endfor ! """"""call assert_equal(vim_mod_mask, mod_mask, $"mod = {vim_mod_mask} for key = {kstr}") endfor endfor endif " 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 = [ \ [[s:VK.SHIFT, s:VK.PRIOR], "S-Pageup", 2], \ [[s:VK.LSHIFT, s:VK.PRIOR], "S-Pageup", 2], --- 511,522 ---- let mod_mask = mod_mask + s:vim_MOD_MASK_SHIFT endif endfor ! call assert_equal(vim_mod_mask, mod_mask, $"mod = {vim_mod_mask} for key = {kstr}") endfor endfor endif " Test for the various Ctrl and Shift key combinations. let keytests = [ \ [[s:VK.SHIFT, s:VK.PRIOR], "S-Pageup", 2], \ [[s:VK.LSHIFT, s:VK.PRIOR], "S-Pageup", 2], *************** *** 586,599 **** \ [[s:VK.CONTROL, s:VK.OEM_MINUS], "C-_", 0] \ ] - " Not working in CI Testing yet!? for [kcodes, kstr, kmod] in keytests call SendKeyGroup(kcodes) let ch = getcharstr(0) let mod = getcharmod() let keycode = eval('"\<' .. kstr .. '>"') ! " call assert_equal(keycode, ch, $"key = {kstr}") ! " call assert_equal(kmod, mod, $"mod = {kmod} key = {kstr}") endfor bw! --- 576,588 ---- \ [[s:VK.CONTROL, s:VK.OEM_MINUS], "C-_", 0] \ ] for [kcodes, kstr, kmod] in keytests call SendKeyGroup(kcodes) let ch = getcharstr(0) let mod = getcharmod() let keycode = eval('"\<' .. kstr .. '>"') ! call assert_equal(keycode, ch, $"key = {kstr}") ! call assert_equal(kmod, mod, $"mod = {kmod} key = {kstr}") endfor bw! *************** *** 634,641 **** call ExecuteBufferedKeys() call assert_equal('BILBO', getline('$')) - - imapclear bw! endfunc --- 623,628 ---- *************** *** 953,959 **** call assert_fails("sandbox call test_mswin_event('key', {'event': 'keydown', 'keycode': 61 })", 'E48:') ! " flush out any garbage left in the buffer. while getchar(0) endwhile endfunc --- 940,946 ---- call assert_fails("sandbox call test_mswin_event('key', {'event': 'keydown', 'keycode': 61 })", 'E48:') ! " flush out anything in the typeahead buffer while getchar(0) endwhile endfunc *** ../vim-9.0.1145/src/version.c 2023-01-04 17:17:49.121005598 +0000 --- src/version.c 2023-01-04 17:50:52.893615244 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 1146, /**/ -- SOLDIER: Where did you get the coconuts? ARTHUR: Through ... We found them. SOLDIER: Found them? In Mercea. The coconut's tropical! "Monty Python and the Holy Grail" PYTHON (MONTY) PICTURES LTD /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///