changeset 31559:a3dca61f3ba3 v9.0.1112

patch 9.0.1112: test_mswin_event() can hang Commit: https://github.com/vim/vim/commit/7b0afc1d7698a79423c7b066a5d8d20dbb8a295a Author: Christopher Plewright <chris@createng.com> Date: Fri Dec 30 16:54:58 2022 +0000 patch 9.0.1112: test_mswin_event() can hang Problem: test_mswin_event() can hang. Solution: Add the "execute" argument to process events right away. (Christopher Plewright, closes #11760)
author Bram Moolenaar <Bram@vim.org>
date Fri, 30 Dec 2022 18:00:05 +0100
parents 1ccb7045f528
children 422447367a85
files runtime/doc/testing.txt src/gui_w32.c src/os_win32.c src/testdir/test_gui.vim src/testdir/test_mswin_event.vim src/version.c
diffstat 6 files changed, 596 insertions(+), 256 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/testing.txt
+++ b/runtime/doc/testing.txt
@@ -274,7 +274,7 @@ test_mswin_event({event}, {args})		*test
 		    event:	The supported string values are:
 				    keyup   generate a keyup event
 				    keydown generate a keydown event
-		    keycode:    Keycode to use for a keyup or a keydown event.
+		    keycode:	Keycode to use for a keyup or a keydown event.
 		    modifiers:	Optional; key modifiers.
 				The supported values are:
 				    2	shift is pressed
@@ -282,9 +282,14 @@ test_mswin_event({event}, {args})		*test
 				    8	alt is pressed
 				Note: These values are different from the
 				mouse modifiers.
-								*E1291*
-		Returns TRUE if the event is successfully added, FALSE if
-		there is a failure.
+		    execute:	Optional. Similar to |feedkeys()| mode x.
+				When this is included and set to true
+				(non-zero) then Vim will process any buffered
+				unprocessed key events.  All other {args}
+				items are optional when this is set and true.
+
+		Returns TRUE if the event is successfully added or executed,
+		FALSE if there is a failure.
 
 		Can also be used as a |method|: >
 			GetEvent()->test_mswin_event({args})
--- a/src/gui_w32.c
+++ b/src/gui_w32.c
@@ -888,6 +888,12 @@ get_active_modifiers(void)
     modifiers = get_active_modifiers();
 
     ch = simplify_key(ch, &modifiers);
+    
+    // Some keys need adjustment when the Ctrl modifier is used.
+    ++no_reduce_keys;
+    ch = may_adjust_key_for_ctrl(modifiers, ch);
+    --no_reduce_keys;
+
     // remove the SHIFT modifier for keys where it's already included, e.g.,
     // '(' and '*'
     modifiers = may_remove_shift_modifier(modifiers, ch);
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -1106,31 +1106,6 @@ decode_key_event(
 	break;
     }
 
-    // special cases
-    if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0
-					&& (pker->uChar.UnicodeChar == NUL
-					|| pker->uChar.UnicodeChar == 0xfffd))
-    {
-	// Ctrl-6 is Ctrl-^
-	if (pker->wVirtualKeyCode == '6')
-	{
-	    *pch = Ctrl_HAT;
-	    return TRUE;
-	}
-	// Ctrl-2 is Ctrl-@
-	else if (pker->wVirtualKeyCode == '2')
-	{
-	    *pch = NUL;
-	    return TRUE;
-	}
-	// Ctrl-- is Ctrl-_
-	else if (pker->wVirtualKeyCode == 0xBD)
-	{
-	    *pch = Ctrl__;
-	    return TRUE;
-	}
-    }
-
     // Shift-TAB
     if (pker->wVirtualKeyCode == VK_TAB && (nModifs & SHIFT_PRESSED))
     {
@@ -1277,12 +1252,22 @@ encode_key_event(dict_T *args, INPUT_REC
 	ker.wVirtualKeyCode = vkCode;
 	win32_kbd_patch_key(&ker);
 
-	for (int i = ARRAY_LENGTH(VirtKeyMap);
-	     --i >= 0 && !ker.uChar.UnicodeChar; )
+	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);
@@ -1919,10 +1904,23 @@ test_mswin_event(char_u *event, dict_T *
 
     INPUT_RECORD ir;
     BOOL input_encoded = FALSE;
+    BOOL execute = FALSE;
     if (STRCMP(event, "key") == 0)
-	input_encoded = encode_key_event(args, &ir);
+    {
+	execute = dict_get_bool(args, "execute", FALSE);
+	if (dict_has_key(args, "event"))
+	    input_encoded = encode_key_event(args, &ir);
+	else if (!execute)
+	{
+	    semsg(_(e_missing_argument_str), "event");
+	    return FALSE;
+	}
+    }
     else if (STRCMP(event, "mouse") == 0)
+    {
+	execute = TRUE;
 	input_encoded = encode_mouse_event(args, &ir);
+    }
     else
     {
 	semsg(_(e_invalid_value_for_argument_str_str), "event", event);
@@ -1935,8 +1933,16 @@ test_mswin_event(char_u *event, dict_T *
     if (input_encoded)
 	lpEventsWritten = write_input_record_buffer(&ir, 1);
 
-    if (STRCMP(event, "mouse") == 0)
+    // Set flags to execute the event, ie. like feedkeys mode X.
+    if (execute)
+    {
+	int save_msg_scroll = msg_scroll;
+	// Avoid a 1 second delay when the keys start Insert mode.
+	msg_scroll = FALSE;
+	ch_log(NULL, "test_mswin_event() executing");
 	exec_normal(TRUE, TRUE, TRUE);
+	msg_scroll |= save_msg_scroll;
+    }
 
 # endif
     return lpEventsWritten;
@@ -2426,6 +2432,15 @@ mch_inchar(
 
 	    c = tgetch(&modifiers, &ch2);
 
+	    // Some chars need adjustment when the Ctrl modifier is used.
+	    ++no_reduce_keys;
+	    c = may_adjust_key_for_ctrl(modifiers, c);
+	    --no_reduce_keys;
+
+	    // remove the SHIFT modifier for keys where it's already included,
+	    // e.g., '(' and '*'
+	    modifiers = may_remove_shift_modifier(modifiers, c);
+
 	    if (typebuf_changed(tb_change_cnt))
 	    {
 		// "buf" may be invalid now if a client put something in the
--- a/src/testdir/test_gui.vim
+++ b/src/testdir/test_gui.vim
@@ -1694,7 +1694,7 @@ func Test_gui_lowlevel_keyevent()
     \ [[0x11, 0x10, 0x28], "C-S-Down", 4],
     \ [[0x11, 0x30], "C-0", 4],
     \ [[0x11, 0x31], "C-1", 4],
-    \ [[0x11, 0x32], "C-2", 4],
+    \ [[0x11, 0x32], "C-@", 0],
     \ [[0x11, 0x33], "C-3", 4],
     \ [[0x11, 0x34], "C-4", 4],
     \ [[0x11, 0x35], "C-5", 4],
@@ -1715,6 +1715,7 @@ func Test_gui_lowlevel_keyevent()
     \ [[0x11, 0x6A], "C-*", 4],
     \ [[0x11, 0x6B], "C-+", 4],
     \ [[0x11, 0x6D], "C--", 4],
+    \ [[0x11, 0xBD], "C-_", 0],
     \ [[0x11, 0x70], "C-F1", 4],
     \ [[0x11, 0x10, 0x70], "C-S-F1", 4],
     \ [[0x11, 0x71], "C-F2", 4],
--- a/src/testdir/test_mswin_event.vim
+++ b/src/testdir/test_mswin_event.vim
@@ -1,25 +1,27 @@
-" Test MS-Windows console event handling.
+" Test MS-Windows input event handling.
+" Most of this works the same in Windows GUI as well as Windows console.
 
 source check.vim
 CheckMSWindows
-" The mswin events should also work in gui
 
 source mouse.vim
 
-" Helper function for sending a sequence of low level key presses
-" The modifer key(s) can be included as normal key presses in the sequence
-func SendKeys(keylist)
-  for k in a:keylist
-    call test_mswin_event("key", #{event: "keydown", keycode: k})
+" Helper function for sending a grouped sequence of low level key presses
+" The modifer key(s) can be included as VK Key Codes in the sequence
+" Keydown events will be sent, to to the end of the group, then keyup events
+" will be sent in reverse order to release the keys.
+func SendKeyGroup(keygroup)
+  for k in a:keygroup
+    call test_mswin_event("key", {'event': "keydown", 'keycode': k})
   endfor
-  for k in reverse(copy(a:keylist))
-    call test_mswin_event("key", #{event: "keyup", keycode: k})
+  for k in reverse(copy(a:keygroup))
+    call test_mswin_event("key", {'event': "keyup", 'keycode': k})
   endfor
 endfunc
 
-" Send an individual key press
+" Send individual key press and release events.
 " the modifers for the key press can be specified in the modifiers arg.
-func SendKey(key, modifiers)
+func SendKeyWithModifiers(key, modifiers)
   let args = { }
   let args.keycode = a:key
   let args.modifiers = a:modifiers
@@ -30,231 +32,399 @@ func SendKey(key, modifiers)
   unlet args
 endfunc
 
-" Test MS-Windows console key events
-func Test_mswin_key_event()
-  CheckMSWindows
-  new
+" Send an individual key press, without modifiers.
+func SendKey(key)
+  call SendKeyWithModifiers(a:key, 0)
+endfunc
+
+" Send a string of individual key-press events, without modifiers.
+func SendKeyStr(keystring)
+  for k in a:keystring
+    call SendKey(k)
+  endfor
+endfunc
+
+" This tells Vim to execute the buffered keys as user commands, 
+" ie. same as feekdeys with mode X would do.
+func ExecuteBufferedKeys()
+  if has('gui_running')
+    call feedkeys("\<Esc>", 'Lx!')
+  else
+    call test_mswin_event("key", {'execute': v:true})
+  endif
+endfunc
+
+
+let s:VK = {
+    \ 'ENTER'      : 0x0D,
+    \ 'SPACE'      : 0x20,
+    \ 'SHIFT'      : 0x10,
+    \ 'LSHIFT'     : 0xA0,
+    \ 'RSHIFT'     : 0xA1,
+    \ 'CONTROL'    : 0x11,
+    \ 'LCONTROL'   : 0xA2,
+    \ 'RCONTROL'   : 0xA3,
+    \ 'MENU'       : 0x12,
+    \ 'ALT'        : 0x12,
+    \ 'LMENU'      : 0xA4,
+    \ 'LALT'       : 0xA4,
+    \ 'RMENU'      : 0xA5,
+    \ 'RALT'       : 0xA5,
+    \ 'OEM_1'      : 0xBA,
+    \ 'OEM_2'      : 0xBF,
+    \ 'OEM_3'      : 0xC0,
+    \ 'OEM_4'      : 0xDB,
+    \ 'OEM_5'      : 0xDC,
+    \ 'OEM_6'      : 0xDD,
+    \ 'OEM_7'      : 0xDE,
+    \ 'OEM_PLUS'   : 0xBB,
+    \ 'OEM_COMMA'  : 0xBC,
+    \ 'OEM_MINUS'  : 0xBD,
+    \ 'OEM_PERIOD' : 0xBE,
+    \ 'PRIOR'      : 0x21,
+    \ 'NEXT'       : 0x22,
+    \ 'END'        : 0x23,
+    \ 'HOME'       : 0x24,
+    \ 'LEFT'       : 0x25,
+    \ 'UP'         : 0x26,
+    \ 'RIGHT'      : 0x27,
+    \ 'DOWN'       : 0x28,
+    \ 'KEY_0'      : 0x30,
+    \ 'KEY_1'      : 0x31,
+    \ 'KEY_2'      : 0x32,
+    \ 'KEY_3'      : 0x33,
+    \ 'KEY_4'      : 0x34,
+    \ 'KEY_5'      : 0x35,
+    \ 'KEY_6'      : 0x36,
+    \ 'KEY_7'      : 0x37,
+    \ 'KEY_8'      : 0x38,
+    \ 'KEY_9'      : 0x39,
+    \ 'KEY_A'      : 0x41,
+    \ 'KEY_B'      : 0x42,
+    \ 'KEY_C'      : 0x43,
+    \ 'KEY_D'      : 0x44,
+    \ 'KEY_E'      : 0x45,
+    \ 'KEY_F'      : 0x46,
+    \ 'KEY_G'      : 0x47,
+    \ 'KEY_H'      : 0x48,
+    \ 'KEY_I'      : 0x49,
+    \ 'KEY_J'      : 0x4A,
+    \ 'KEY_K'      : 0x4B,
+    \ 'KEY_L'      : 0x4C,
+    \ 'KEY_M'      : 0x4D,
+    \ 'KEY_N'      : 0x4E,
+    \ 'KEY_O'      : 0x4F,
+    \ 'KEY_P'      : 0x50,
+    \ 'KEY_Q'      : 0x51,
+    \ 'KEY_R'      : 0x52,
+    \ 'KEY_S'      : 0x53,
+    \ 'KEY_T'      : 0x54,
+    \ 'KEY_U'      : 0x55,
+    \ 'KEY_V'      : 0x56,
+    \ 'KEY_W'      : 0x57,
+    \ 'KEY_X'      : 0x58,
+    \ 'KEY_Y'      : 0x59,
+    \ 'KEY_Z'      : 0x5A,
+    \ 'NUMPAD0'    : 0x60,
+    \ 'NUMPAD1'    : 0x61,
+    \ 'NUMPAD2'    : 0x62,
+    \ 'NUMPAD3'    : 0x63,
+    \ 'NUMPAD4'    : 0x64,
+    \ 'NUMPAD5'    : 0x65,
+    \ 'NUMPAD6'    : 0x66,
+    \ 'NUMPAD7'    : 0x67,
+    \ 'NUMPAD8'    : 0x68,
+    \ 'NUMPAD9'    : 0x69,
+    \ 'MULTIPLY'   : 0x6A,
+    \ 'ADD'        : 0x6B,
+    \ 'SUBTRACT'   : 0x6D,
+    \ 'F1'         : 0x70,
+    \ 'F2'         : 0x71,
+    \ 'F3'         : 0x72,
+    \ 'F4'         : 0x73,
+    \ 'F5'         : 0x74,
+    \ 'F6'         : 0x75,
+    \ 'F7'         : 0x76,
+    \ 'F8'         : 0x77,
+    \ 'F9'         : 0x78,
+    \ 'F10'        : 0x79,
+    \ 'F11'        : 0x7A,
+    \ 'F12'        : 0x7B,
+    \ 'DELETE'     : 0x2E,
+    \ 'BACK'       : 0x08,
+    \ 'ESCAPE'     : 0x1B
+    \ }
+
+  let s:vim_MOD_MASK_SHIFT = 0x02
+  let s:vim_MOD_MASK_CTRL  = 0x04
+  let s:vim_MOD_MASK_ALT   = 0x08
+  
+  let s:vim_key_modifiers = [
+    \ ["",       0,   []],
+    \ ["S-",     2,   [s:VK.SHIFT]],
+    \ ["C-",     4,   [s:VK.CONTROL]],
+    \ ["C-S-",   6,   [s:VK.CONTROL, s:VK.SHIFT]],
+    \ ["A-",     8,   [s:VK.MENU]],
+    \ ["A-S-",   10,  [s:VK.MENU, s:VK.SHIFT]],
+    \ ["A-C-",   12,  [s:VK.MENU, s:VK.CONTROL]],
+    \ ["A-C-S-", 14,  [s:VK.MENU, s:VK.CONTROL, s:VK.SHIFT]],
+    \]
 
-  " flush out any garbage left in the buffer
+  " Assuming Standard US PC Keyboard layout
+  let s:test_ascii_key_chars = [
+    \ [[s:VK.SPACE], ' '],
+    \ [[s:VK.OEM_1], ';'],
+    \ [[s:VK.OEM_2], '/'],
+    \ [[s:VK.OEM_3], '`'],
+    \ [[s:VK.OEM_4], '['],
+    \ [[s:VK.OEM_5], '\'],
+    \ [[s:VK.OEM_6], ']'],
+    \ [[s:VK.OEM_7], ''''],
+    \ [[s:VK.OEM_PLUS], '='],
+    \ [[s:VK.OEM_COMMA], ','],
+    \ [[s:VK.OEM_MINUS], '-'],
+    \ [[s:VK.OEM_PERIOD], '.'],
+    \ [[s:VK.SHIFT, s:VK.OEM_1], ':'],
+    \ [[s:VK.SHIFT, s:VK.OEM_2], '?'],
+    \ [[s:VK.SHIFT, s:VK.OEM_3], '~'],
+    \ [[s:VK.SHIFT, s:VK.OEM_4], '{'],
+    \ [[s:VK.SHIFT, s:VK.OEM_5], '|'],
+    \ [[s:VK.SHIFT, s:VK.OEM_6], '}'],
+    \ [[s:VK.SHIFT, s:VK.OEM_7], '"'],
+    \ [[s:VK.SHIFT, s:VK.OEM_PLUS], '+'],
+    \ [[s:VK.SHIFT, s:VK.OEM_COMMA], '<'],
+    \ [[s:VK.SHIFT, s:VK.OEM_MINUS], '_'],
+    \ [[s:VK.SHIFT, s:VK.OEM_PERIOD], '>'],
+    \ [[s:VK.KEY_1], '1'],
+    \ [[s:VK.KEY_2], '2'],
+    \ [[s:VK.KEY_3], '3'],
+    \ [[s:VK.KEY_4], '4'],
+    \ [[s:VK.KEY_5], '5'],
+    \ [[s:VK.KEY_6], '6'],
+    \ [[s:VK.KEY_7], '7'],
+    \ [[s:VK.KEY_8], '8'],
+    \ [[s:VK.KEY_9], '9'],
+    \ [[s:VK.KEY_0], '0'],
+    \ [[s:VK.SHIFT, s:VK.KEY_1], '!'],
+    \ [[s:VK.SHIFT, s:VK.KEY_2], '@'],
+    \ [[s:VK.SHIFT, s:VK.KEY_3], '#'],
+    \ [[s:VK.SHIFT, s:VK.KEY_4], '$'],
+    \ [[s:VK.SHIFT, s:VK.KEY_5], '%'],
+    \ [[s:VK.SHIFT, s:VK.KEY_6], '^'],
+    \ [[s:VK.SHIFT, s:VK.KEY_7], '&'],
+    \ [[s:VK.SHIFT, s:VK.KEY_8], '*'],
+    \ [[s:VK.SHIFT, s:VK.KEY_9], '('],
+    \ [[s:VK.SHIFT, s:VK.KEY_0], ')'],
+    \ [[s:VK.KEY_A], 'a'],
+    \ [[s:VK.KEY_B], 'b'],
+    \ [[s:VK.KEY_C], 'c'],
+    \ [[s:VK.KEY_D], 'd'],
+    \ [[s:VK.KEY_E], 'e'],
+    \ [[s:VK.KEY_F], 'f'],
+    \ [[s:VK.KEY_G], 'g'],
+    \ [[s:VK.KEY_H], 'h'],
+    \ [[s:VK.KEY_I], 'i'],
+    \ [[s:VK.KEY_J], 'j'],
+    \ [[s:VK.KEY_K], 'k'],
+    \ [[s:VK.KEY_L], 'l'],
+    \ [[s:VK.KEY_M], 'm'],
+    \ [[s:VK.KEY_N], 'n'],
+    \ [[s:VK.KEY_O], 'o'],
+    \ [[s:VK.KEY_P], 'p'],
+    \ [[s:VK.KEY_Q], 'q'],
+    \ [[s:VK.KEY_R], 'r'],
+    \ [[s:VK.KEY_S], 's'],
+    \ [[s:VK.KEY_T], 't'],
+    \ [[s:VK.KEY_U], 'u'],
+    \ [[s:VK.KEY_V], 'v'],
+    \ [[s:VK.KEY_W], 'w'],
+    \ [[s:VK.KEY_X], 'x'],
+    \ [[s:VK.KEY_Y], 'y'],
+    \ [[s:VK.KEY_Z], 'z'],
+    \ [[s:VK.SHIFT, s:VK.KEY_A], 'A'],
+    \ [[s:VK.SHIFT, s:VK.KEY_B], 'B'],
+    \ [[s:VK.SHIFT, s:VK.KEY_C], 'C'],
+    \ [[s:VK.SHIFT, s:VK.KEY_D], 'D'],
+    \ [[s:VK.SHIFT, s:VK.KEY_E], 'E'],
+    \ [[s:VK.SHIFT, s:VK.KEY_F], 'F'],
+    \ [[s:VK.SHIFT, s:VK.KEY_G], 'G'],
+    \ [[s:VK.SHIFT, s:VK.KEY_H], 'H'],
+    \ [[s:VK.SHIFT, s:VK.KEY_I], 'I'],
+    \ [[s:VK.SHIFT, s:VK.KEY_J], 'J'],
+    \ [[s:VK.SHIFT, s:VK.KEY_K], 'K'],
+    \ [[s:VK.SHIFT, s:VK.KEY_L], 'L'],
+    \ [[s:VK.SHIFT, s:VK.KEY_M], 'M'],
+    \ [[s:VK.SHIFT, s:VK.KEY_N], 'N'],
+    \ [[s:VK.SHIFT, s:VK.KEY_O], 'O'],
+    \ [[s:VK.SHIFT, s:VK.KEY_P], 'P'],
+    \ [[s:VK.SHIFT, s:VK.KEY_Q], 'Q'],
+    \ [[s:VK.SHIFT, s:VK.KEY_R], 'R'],
+    \ [[s:VK.SHIFT, s:VK.KEY_S], 'S'],
+    \ [[s:VK.SHIFT, s:VK.KEY_T], 'T'],
+    \ [[s:VK.SHIFT, s:VK.KEY_U], 'U'],
+    \ [[s:VK.SHIFT, s:VK.KEY_V], 'V'],
+    \ [[s:VK.SHIFT, s:VK.KEY_W], 'W'],
+    \ [[s:VK.SHIFT, s:VK.KEY_X], 'X'],
+    \ [[s:VK.SHIFT, s:VK.KEY_Y], 'Y'],
+    \ [[s:VK.SHIFT, s:VK.KEY_Z], 'Z'],
+    \ [[s:VK.CONTROL, s:VK.KEY_A], 0x01],
+    \ [[s:VK.CONTROL, s:VK.KEY_B], 0x02],
+    \ [[s:VK.CONTROL, s:VK.KEY_C], 0x03],
+    \ [[s:VK.CONTROL, s:VK.KEY_D], 0x04],
+    \ [[s:VK.CONTROL, s:VK.KEY_E], 0x05],
+    \ [[s:VK.CONTROL, s:VK.KEY_F], 0x06],
+    \ [[s:VK.CONTROL, s:VK.KEY_G], 0x07],
+    \ [[s:VK.CONTROL, s:VK.KEY_H], 0x08],
+    \ [[s:VK.CONTROL, s:VK.KEY_I], 0x09],
+    \ [[s:VK.CONTROL, s:VK.KEY_J], 0x0A],
+    \ [[s:VK.CONTROL, s:VK.KEY_K], 0x0B],
+    \ [[s:VK.CONTROL, s:VK.KEY_L], 0x0C],
+    \ [[s:VK.CONTROL, s:VK.KEY_M], 0x0D],
+    \ [[s:VK.CONTROL, s:VK.KEY_N], 0x0E],
+    \ [[s:VK.CONTROL, s:VK.KEY_O], 0x0F],
+    \ [[s:VK.CONTROL, s:VK.KEY_P], 0x10],
+    \ [[s:VK.CONTROL, s:VK.KEY_Q], 0x11],
+    \ [[s:VK.CONTROL, s:VK.KEY_R], 0x12],
+    \ [[s:VK.CONTROL, s:VK.KEY_S], 0x13],
+    \ [[s:VK.CONTROL, s:VK.KEY_T], 0x14],
+    \ [[s:VK.CONTROL, s:VK.KEY_U], 0x15],
+    \ [[s:VK.CONTROL, s:VK.KEY_V], 0x16],
+    \ [[s:VK.CONTROL, s:VK.KEY_W], 0x17],
+    \ [[s:VK.CONTROL, s:VK.KEY_X], 0x18],
+    \ [[s:VK.CONTROL, s:VK.KEY_Y], 0x19],
+    \ [[s:VK.CONTROL, s:VK.KEY_Z], 0x1A],
+    \ [[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], '±'],
+    \ [[s:VK.ALT, s:VK.KEY_2], '²'],
+    \ [[s:VK.ALT, s:VK.KEY_3], '³'],
+    \ [[s:VK.ALT, s:VK.KEY_4], '´'],
+    \ [[s:VK.ALT, s:VK.KEY_5], 'µ'],
+    \ [[s:VK.ALT, s:VK.KEY_6], '¶'],
+    \ [[s:VK.ALT, s:VK.KEY_7], '·'],
+    \ [[s:VK.ALT, s:VK.KEY_8], '¸'],
+    \ [[s:VK.ALT, s:VK.KEY_9], '¹'],
+    \ [[s:VK.ALT, s:VK.KEY_0], '°'],
+    \ [[s:VK.ALT, s:VK.KEY_A], 'á'],
+    \ [[s:VK.ALT, s:VK.KEY_B], 'â'],
+    \ [[s:VK.ALT, s:VK.KEY_C], 'ã'],
+    \ [[s:VK.ALT, s:VK.KEY_D], 'ä'],
+    \ [[s:VK.ALT, s:VK.KEY_E], 'å'],
+    \ [[s:VK.ALT, s:VK.KEY_F], 'æ'],
+    \ [[s:VK.ALT, s:VK.KEY_G], 'ç'],
+    \ [[s:VK.ALT, s:VK.KEY_H], 'è'],
+    \ [[s:VK.ALT, s:VK.KEY_I], 'é'],
+    \ [[s:VK.ALT, s:VK.KEY_J], 'ê'],
+    \ [[s:VK.ALT, s:VK.KEY_K], 'ë'],
+    \ [[s:VK.ALT, s:VK.KEY_L], 'ì'],
+    \ [[s:VK.ALT, s:VK.KEY_M], 'í'],
+    \ [[s:VK.ALT, s:VK.KEY_N], 'î'],
+    \ [[s:VK.ALT, s:VK.KEY_O], 'ï'],
+    \ [[s:VK.ALT, s:VK.KEY_P], 'ð'],
+    \ [[s:VK.ALT, s:VK.KEY_Q], 'ñ'],
+    \ [[s:VK.ALT, s:VK.KEY_R], 'ò'],
+    \ [[s:VK.ALT, s:VK.KEY_S], 'ó'],
+    \ [[s:VK.ALT, s:VK.KEY_T], 'ô'],
+    \ [[s:VK.ALT, s:VK.KEY_U], 'õ'],
+    \ [[s:VK.ALT, s:VK.KEY_V], 'ö'],
+    \ [[s:VK.ALT, s:VK.KEY_W], '÷'],
+    \ [[s:VK.ALT, s:VK.KEY_X], 'ø'],
+    \ [[s:VK.ALT, s:VK.KEY_Y], 'ù'],
+    \ [[s:VK.ALT, s:VK.KEY_Z], 'ú'],
+    \ ]
+
+func s:LoopTestKeyArray(arr)
+" flush out any garbage left in the buffer
   while getchar(0)
   endwhile
 
-  let VK = #{
-	\ SPACE	     : 0x20,
-	\ SHIFT      : 0x10,
-	\ LSHIFT     : 0xA0,
-	\ RSHIFT     : 0xA1,
-	\ CONTROL    : 0x11,
-	\ LCONTROL   : 0xA2,
-	\ RCONTROL   : 0xA3,
-	\ MENU	     : 0x12,
-	\ ALT	     : 0x12,
-	\ LMENU      : 0xA4,
-	\ LALT	     : 0xA4,
-	\ RMENU      : 0xA5,
-	\ RALT	     : 0xA5,
-	\ OEM_1      : 0xBA,
-	\ OEM_2      : 0xBF,
-	\ OEM_3      : 0xC0,
-	\ OEM_4      : 0xDB,
-	\ OEM_5      : 0xDC,
-	\ OEM_6      : 0xDD,
-	\ OEM_7      : 0xDE,
-	\ OEM_PLUS   : 0xBB,
-	\ OEM_COMMA  : 0xBC,
-	\ OEM_MINUS  : 0xBD,
-	\ OEM_PERIOD : 0xBE,
-	\ PRIOR      : 0x21,
-	\ NEXT	     : 0x22,
-	\ END	     : 0x23,
-	\ HOME	     : 0x24,
-	\ LEFT	     : 0x25,
-	\ UP	     : 0x26,
-	\ RIGHT      : 0x27,
-	\ DOWN	     : 0x28,
-	\ KEY_0      : 0x30,
-	\ KEY_1      : 0x31,
-	\ KEY_2      : 0x32,
-	\ KEY_3      : 0x33,
-	\ KEY_4      : 0x34,
-	\ KEY_5      : 0x35,
-	\ KEY_6      : 0x36,
-	\ KEY_7      : 0x37,
-	\ KEY_8      : 0x38,
-	\ KEY_9      : 0x39,
-	\ NUMPAD0    : 0x60,
-	\ NUMPAD1    : 0x61,
-	\ NUMPAD2    : 0x62,
-	\ NUMPAD3    : 0x63,
-	\ NUMPAD4    : 0x64,
-	\ NUMPAD5    : 0x65,
-	\ NUMPAD6    : 0x66,
-	\ NUMPAD7    : 0x67,
-	\ NUMPAD8    : 0x68,
-	\ NUMPAD9    : 0x69,
-	\ MULTIPLY   : 0x6A,
-	\ ADD	     : 0x6B,
-	\ SUBTRACT   : 0x6D,
-	\ F1	     : 0x70,
-	\ F2	     : 0x71,
-	\ F3	     : 0x72,
-	\ F4	     : 0x73,
-	\ F5	     : 0x74,
-	\ F6	     : 0x75,
-	\ F7	     : 0x76,
-	\ F8	     : 0x77,
-	\ F9	     : 0x78,
-	\ F10	     : 0x79,
-	\ F11	     : 0x7A,
-	\ F12	     : 0x7B,
-	\ KEY_A      : 0x41,
-	\ KEY_B      : 0x42,
-	\ KEY_C      : 0x43,
-	\ KEY_D      : 0x44,
-	\ KEY_E      : 0x45,
-	\ KEY_F      : 0x46,
-	\ KEY_G      : 0x47,
-	\ KEY_H      : 0x48,
-	\ KEY_I      : 0x49,
-	\ KEY_J      : 0x4A,
-	\ KEY_K      : 0x4B,
-	\ KEY_L      : 0x4C,
-	\ KEY_M      : 0x4D,
-	\ KEY_N      : 0x4E,
-	\ KEY_O      : 0x4F,
-	\ KEY_P      : 0x50,
-	\ KEY_Q      : 0x51,
-	\ KEY_R      : 0x52,
-	\ KEY_S      : 0x53,
-	\ KEY_T      : 0x54,
-	\ KEY_U      : 0x55,
-	\ KEY_V      : 0x56,
-	\ KEY_W      : 0x57,
-	\ KEY_X      : 0x58,
-	\ KEY_Y      : 0x59,
-	\ KEY_Z      : 0x5A	
-	\ }
-
-  let vim_MOD_MASK_SHIFT = 0x02
-  let vim_MOD_MASK_CTRL  = 0x04
-  let vim_MOD_MASK_ALT   = 0x08
-  
-  let vim_key_modifiers = [
-    \ ["",       0,   []],
-    \ ["S-",     2,   [VK.SHIFT]],
-    \ ["C-",     4,   [VK.CONTROL]],
-    \ ["C-S-",   6,   [VK.CONTROL, VK.SHIFT]],
-    \ ["A-",     8,   [VK.MENU]],
-    \ ["A-S-",   10,  [VK.MENU, VK.SHIFT]],
-    \ ["A-C-",   12,  [VK.MENU, VK.CONTROL]],
-    \ ["A-C-S-", 14,  [VK.MENU, VK.CONTROL, VK.SHIFT]],
-    \]
-
-  " Some punctuation characters
-  " Assuming Standard US PC Keyboard layout
-  let test_punctuation_keys = [
-	\ [[VK.SPACE], ' '],
-	\ [[VK.OEM_1], ';'],
-	\ [[VK.OEM_2], '/'],
-	\ [[VK.OEM_3], '`'],
-	\ [[VK.OEM_4], '['],
-	\ [[VK.OEM_5], '\'],
-	\ [[VK.OEM_6], ']'],
-	\ [[VK.OEM_7], ''''],
-	\ [[VK.OEM_PLUS], '='],
-	\ [[VK.OEM_COMMA], ','],
-	\ [[VK.OEM_MINUS], '-'],
-	\ [[VK.OEM_PERIOD], '.'],
-	\ [[VK.SHIFT, VK.OEM_1], ':'],
-	\ [[VK.SHIFT, VK.OEM_2], '?'],
-	\ [[VK.SHIFT, VK.OEM_3], '~'],
-	\ [[VK.SHIFT, VK.OEM_4], '{'],
-	\ [[VK.SHIFT, VK.OEM_5], '|'],
-	\ [[VK.SHIFT, VK.OEM_6], '}'],
-	\ [[VK.SHIFT, VK.OEM_7], '"'],
-	\ [[VK.SHIFT, VK.OEM_PLUS], '+'],
-	\ [[VK.SHIFT, VK.OEM_COMMA], '<'],
-	\ [[VK.SHIFT, VK.OEM_MINUS], '_'],
-	\ [[VK.SHIFT, VK.OEM_PERIOD], '>'],
-	\ [[VK.SHIFT, VK.KEY_1], '!'],
-	\ [[VK.SHIFT, VK.KEY_2], '@'],
-	\ [[VK.SHIFT, VK.KEY_3], '#'],
-	\ [[VK.SHIFT, VK.KEY_4], '$'],
-	\ [[VK.SHIFT, VK.KEY_5], '%'],
-	\ [[VK.SHIFT, VK.KEY_6], '^'],
-	\ [[VK.SHIFT, VK.KEY_7], '&'],
-	\ [[VK.SHIFT, VK.KEY_8], '*'],
-	\ [[VK.SHIFT, VK.KEY_9], '('],
-	\ [[VK.SHIFT, VK.KEY_0], ')'],
-	\ [[VK.LSHIFT, VK.KEY_9], '('],
-	\ [[VK.RSHIFT, VK.KEY_0], ')']
-	\ ]
-
-  for [kcodes, kstr] in test_punctuation_keys
-    call SendKeys(kcodes)
+  for [kcodes, kstr] in a:arr
+    " Send as a sequence of key presses.
+    call SendKeyGroup(kcodes)
     let ch = getcharstr(0)
-    call assert_equal($"{kstr}", $"{ch}")
+    " 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}")
+    endif
     let mod_mask = getcharmod()
     " the mod_mask is zero when no modifiers are used
-    " and when the virtual termcap maps shift the character
+    " and when the virtual termcap maps the character
     call assert_equal(0, mod_mask, $"key = {kstr}")
-  endfor
-  
-  " flush out any garbage left in the buffer
-  while getchar(0)
-  endwhile
 
-  for [kcodes, kstr] in test_punctuation_keys
+    " Send as a single key press with a modifers mask.
     let modifiers = 0
     let key = kcodes[0]
-
     for key in kcodes
-      if index([VK.SHIFT, VK.LSHIFT, VK.RSHIFT], key) >= 0
-        let modifiers = modifiers + vim_MOD_MASK_SHIFT
+      if index([s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT], key) >= 0
+        let modifiers = modifiers + s:vim_MOD_MASK_SHIFT
       endif
-      if index([VK.CONTROL, VK.LCONTROL, VK.RCONTROL], key) >= 0
-        let modifiers = modifiers + vim_MOD_MASK_CTRL
+      if index([s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL], key) >= 0
+        let modifiers = modifiers + s:vim_MOD_MASK_CTRL
       endif
-      if index([VK.ALT, VK.LALT, VK.RALT], key) >= 0
-        let modifiers = modifiers + vim_MOD_MASK_ALT
+      if index([s:VK.ALT, s:VK.LALT, s:VK.RALT], key) >= 0
+        let modifiers = modifiers + s:vim_MOD_MASK_ALT
       endif
     endfor
-
-    call SendKey(key, modifiers)
+    call SendKeyWithModifiers(key, modifiers)
     let ch = getcharstr(0)
-    call assert_equal($"{kstr}", $"{ch}")
+    " 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}")
+    endif
     let mod_mask = getcharmod()
-    " workaround for the virtual termcap maps changing the character instead
-    " of sending Shift
-    if index([VK.SHIFT, VK.LSHIFT, VK.RSHIFT], kcodes[0]) >= 0
-      let modifiers = modifiers - vim_MOD_MASK_SHIFT
-    endif
-    call assert_equal(modifiers, mod_mask, $"key = {kstr}")
+    " the mod_mask is zero when no modifiers are used
+    " and when the virtual termcap maps the character
+    call assert_equal(0, mod_mask, $"key = {kstr}")
   endfor
 
   " flush out any garbage left in the buffer
   while getchar(0)
   endwhile
 
+endfunc
+
+" Test MS-Windows key events
+func Test_mswin_key_event()
+  CheckMSWindows
+  new
+
+  call s:LoopTestKeyArray(s:test_ascii_key_chars)
+
+  if !has('gui_running')
+    call s:LoopTestKeyArray(s:test_extra_key_chars)
+  endif
+
 " Test keyboard codes for digits
 " (0x30 - 0x39) : VK_0 - VK_9 are the same as ASCII '0' - '9'
   for kc in range(48, 57)
-    call SendKeys([kc])
+    call SendKey(kc)
     let ch = getcharstr(0)
     call assert_equal(nr2char(kc), ch)
-    call SendKey(kc, 0)
+    call SendKeyWithModifiers(kc, 0)
     let ch = getcharstr(0)
     call assert_equal(nr2char(kc), ch)
   endfor
 
 " Test keyboard codes for Alt-0 to Alt-9
 " Expect +128 from the digit char codes
-  for modkey in [VK.ALT, VK.LALT, VK.RALT]
+  for modkey in [s:VK.ALT, s:VK.LALT, s:VK.RALT]
     for kc in range(48, 57)
-      call SendKeys([modkey, kc])
+      call SendKeyGroup([modkey, kc])
       let ch = getchar(0)
       call assert_equal(kc+128, ch)
-      call SendKey(kc, vim_MOD_MASK_ALT)
+      call SendKeyWithModifiers(kc, s:vim_MOD_MASK_ALT)
       let ch = getchar(0)
       call assert_equal(kc+128, ch)
     endfor
@@ -268,68 +438,209 @@ func Test_mswin_key_event()
 " char 'a' (91) as the output.  The ASCII codes for the lowercase letters are
 " numbered 32 higher than their uppercase versions.
   for kc in range(65, 90)
-    call SendKeys([kc])
+    call SendKey(kc)
     let ch = getcharstr(0)
     call assert_equal(nr2char(kc + 32), ch)
-    call SendKey(kc, 0)
+    call SendKeyWithModifiers(kc, 0)
     let ch = getcharstr(0)
     call assert_equal(nr2char(kc + 32), ch)
   endfor
 
 "  Test for Uppercase 'A' - 'Z' keys
 "  ie. with VK_SHIFT, expect the keycode = character code.
-  for kc in range(65, 90)
-    call SendKeys([VK.SHIFT, kc])
-    let ch = getcharstr(0)
-    call assert_equal(nr2char(kc), ch)
-    call SendKey(kc, vim_MOD_MASK_SHIFT)
-    let ch = getcharstr(0)
-    call assert_equal(nr2char(kc), ch)
+  for modkey in [s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT]
+    for kc in range(65, 90)
+      call SendKeyGroup([modkey, kc])
+      let ch = getcharstr(0)
+      call assert_equal(nr2char(kc), ch)
+      call SendKeyWithModifiers(kc, s:vim_MOD_MASK_SHIFT)
+      let ch = getcharstr(0)
+      call assert_equal(nr2char(kc), ch)
+    endfor
   endfor
 
   " Test for <Ctrl-A> to <Ctrl-Z> keys
- "  Same as for lowercase, except with Ctrl Key
  "  Expect the unicode characters 0x01 to 0x1A
-   for modkey in [VK.CONTROL, VK.LCONTROL, VK.RCONTROL]
+   for modkey in [s:VK.CONTROL, s:VK.LCONTROL, s:VK.RCONTROL]
     for kc in range(65, 90)
-      call SendKeys([modkey, kc])
+      call SendKeyGroup([modkey, kc])
       let ch = getcharstr(0)
       call assert_equal(nr2char(kc - 64), ch)
-      call SendKey(kc, vim_MOD_MASK_CTRL)
+      call SendKeyWithModifiers(kc, s:vim_MOD_MASK_CTRL)
       let ch = getcharstr(0)
       call assert_equal(nr2char(kc - 64), ch)
     endfor
   endfor
 
+  "  Windows intercepts some of these keys in the GUI.
   if !has("gui_running")
-  " Test for <Alt-A> to <Alt-Z> keys
+  "  Test for <Alt-A> to <Alt-Z> keys
   "  Expect the unicode characters 0xE1 to 0xFA
   "  ie. 160 higher than the lowercase equivalent
-    for kc in range(65, 90)
-      call SendKeys([VK.LMENU, kc])
-      let ch = getchar(0)
-      call assert_equal(kc+160, ch)
-      call SendKey(kc, vim_MOD_MASK_ALT)
-      let ch = getchar(0)
-      call assert_equal(kc+160, ch)
+    for modkey in [s:VK.ALT, s:VK.LALT, s:VK.RALT]
+      for kc in range(65, 90)
+        call SendKeyGroup([modkey, kc])
+        let ch = getchar(0)
+        call assert_equal(kc+160, ch)
+        call SendKeyWithModifiers(kc, s:vim_MOD_MASK_ALT)
+        let ch = getchar(0)
+        call assert_equal(kc+160, ch)
+      endfor
     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 SendKeys([111+n])
+      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
+          if index([s:VK.SHIFT, s:VK.LSHIFT, s:VK.RSHIFT], mod_key) >= 0
+            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],
+    \ [[s:VK.RSHIFT,   s:VK.PRIOR], "S-Pageup", 2],
+    \ [[s:VK.CONTROL,  s:VK.PRIOR], "C-Pageup", 4],
+    \ [[s:VK.LCONTROL, s:VK.PRIOR], "C-Pageup", 4],
+    \ [[s:VK.RCONTROL, s:VK.PRIOR], "C-Pageup", 4],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.PRIOR], "C-S-Pageup", 6],
+    \ [[s:VK.SHIFT,    s:VK.NEXT], "S-PageDown", 2],
+    \ [[s:VK.LSHIFT,   s:VK.NEXT], "S-PageDown", 2],
+    \ [[s:VK.RSHIFT,   s:VK.NEXT], "S-PageDown", 2],
+    \ [[s:VK.CONTROL,  s:VK.NEXT], "C-PageDown", 4],
+    \ [[s:VK.LCONTROL, s:VK.NEXT], "C-PageDown", 4],
+    \ [[s:VK.RCONTROL, s:VK.NEXT], "C-PageDown", 4],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.NEXT], "C-S-PageDown", 6],
+    \ [[s:VK.SHIFT,    s:VK.END], "S-End", 0],
+    \ [[s:VK.CONTROL,  s:VK.END], "C-End", 0],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.END], "C-S-End", 4],
+    \ [[s:VK.SHIFT,    s:VK.HOME], "S-Home", 0],
+    \ [[s:VK.CONTROL,  s:VK.HOME], "C-Home", 0],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.HOME], "C-S-Home", 4],
+    \ [[s:VK.SHIFT,    s:VK.LEFT], "S-Left", 0],
+    \ [[s:VK.CONTROL,  s:VK.LEFT], "C-Left", 0],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.LEFT], "C-S-Left", 4],
+    \ [[s:VK.SHIFT,    s:VK.UP], "S-Up", 0],
+    \ [[s:VK.CONTROL,  s:VK.UP], "C-Up", 4],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.UP], "C-S-Up", 4],
+    \ [[s:VK.SHIFT,    s:VK.RIGHT], "S-Right", 0],
+    \ [[s:VK.CONTROL,  s:VK.RIGHT], "C-Right", 0],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.RIGHT], "C-S-Right", 4],
+    \ [[s:VK.SHIFT,    s:VK.DOWN], "S-Down", 0],
+    \ [[s:VK.CONTROL,  s:VK.DOWN], "C-Down", 4],
+    \ [[s:VK.CONTROL,  s:VK.SHIFT, s:VK.DOWN], "C-S-Down", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_0], "C-0", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_1], "C-1", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_2], "C-@", 0],
+    \ [[s:VK.CONTROL,  s:VK.KEY_3], "C-3", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_4], "C-4", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_5], "C-5", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_6], "C-^", 0],
+    \ [[s:VK.CONTROL,  s:VK.KEY_7], "C-7", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_8], "C-8", 4],
+    \ [[s:VK.CONTROL,  s:VK.KEY_9], "C-9", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD0], "C-0", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD1], "C-1", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD2], "C-2", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD3], "C-3", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD4], "C-4", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD5], "C-5", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD6], "C-6", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD7], "C-7", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD8], "C-8", 4],
+    \ [[s:VK.CONTROL,  s:VK.NUMPAD9], "C-9", 4],
+    \ [[s:VK.CONTROL,  s:VK.MULTIPLY], "C-*", 4],
+    \ [[s:VK.CONTROL,  s:VK.ADD], "C-+", 4],
+    \ [[s:VK.CONTROL,  s:VK.SUBTRACT], "C--", 4],
+    \ [[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!
 endfunc
 
-"  Test MS-Windows console mouse events
+
+" Test for QWERTY Ctrl+- which should result in ^_
+" issue #10817
+func Test_QWERTY_Ctrl_minus()
+  CheckMSWindows
+  new
+
+  call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+  let ch = getcharstr(0)
+  call assert_equal(nr2char(0x1f),ch)
+
+  call SendKey(s:VK.KEY_I)
+  call SendKeyGroup([s:VK.CONTROL, s:VK.SUBTRACT])
+  call SendKey(s:VK.ESCAPE)
+  call ExecuteBufferedKeys()
+  call assert_equal('-', getline('$'))
+
+  %d _
+  imapclear
+  imap <C-_> BINGO
+  call SendKey(s:VK.KEY_I)
+  call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+  call SendKey(s:VK.ESCAPE)
+  call ExecuteBufferedKeys()
+  call assert_equal('BINGO', getline('$'))
+
+  %d _
+  imapclear
+  exec "imap \x1f BILBO"
+  call SendKey(s:VK.KEY_I)
+  call SendKeyGroup([s:VK.CONTROL, s:VK.OEM_MINUS])
+  call SendKey(s:VK.ESCAPE)
+  call ExecuteBufferedKeys()
+  call assert_equal('BILBO', getline('$'))
+
+
+
+  imapclear
+  bw!
+endfunc
+
+"  Test MS-Windows mouse events
 func Test_mswin_mouse_event()
   CheckMSWindows
   new
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1112,
+/**/
     1111,
 /**/
     1110,