diff src/os_win32.c @ 31503:b9a4699d6a35 v9.0.1084

patch 9.0.1084: code handling low level MS-Windows events cannot be tested Commit: https://github.com/vim/vim/commit/20b795e0eba6c933868c8f7cf62fb85d4f007688 Author: Christopher Plewright <chris@createng.com> Date: Tue Dec 20 20:01:58 2022 +0000 patch 9.0.1084: code handling low level MS-Windows events cannot be tested Problem: Code handling low level MS-Windows events cannot be tested. Solution: Add test_mswin_event() and tests using it. (Christopher Plewright, closes #11622)
author Bram Moolenaar <Bram@vim.org>
date Tue, 20 Dec 2022 21:15:05 +0100
parents fa309d9af73c
children dc282943639c
line wrap: on
line diff
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -177,6 +177,25 @@ static void gotoxy(unsigned x, unsigned 
 static void standout(void);
 static int s_cursor_visible = TRUE;
 static int did_create_conin = FALSE;
+// The 'input_record_buffer' is an internal dynamic fifo queue of MS-Windows
+// console INPUT_RECORD events that are normally read from the console input
+// buffer.  This provides an injection point for testing the low-level handling
+// of INPUT_RECORDs.
+typedef struct input_record_buffer_node_S
+{
+    INPUT_RECORD ir;
+    struct input_record_buffer_node_S *next;
+} input_record_buffer_node_T;
+typedef struct input_record_buffer_S
+{
+    input_record_buffer_node_T *head;
+    input_record_buffer_node_T *tail;
+    int length;
+} input_record_buffer_T;
+static input_record_buffer_T input_record_buffer;
+static int peek_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength);
+static int read_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength);
+static int write_input_record_buffer(INPUT_RECORD* irEvents, int nLength);
 #endif
 #ifdef FEAT_GUI_MSWIN
 static int s_dont_use_vimrun = TRUE;
@@ -224,7 +243,7 @@ static int default_console_color_fg = 0x
 static void set_console_color_rgb(void);
 static void reset_console_color_rgb(void);
 static void restore_console_color_rgb(void);
-#endif
+#endif  // !FEAT_GUI_MSWIN || VIMDLL
 
 // This flag is newly created from Windows 10
 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
@@ -319,6 +338,13 @@ read_console_input(
     int i;
     static INPUT_RECORD s_irPseudo;
 
+    if (s_dwMax == 0 && input_record_buffer.length > 0)
+    {
+	dwEvents = read_input_record_buffer(s_irCache, IRSIZE);
+	s_dwIndex = 0;
+	s_dwMax = dwEvents;
+    }
+
     if (nLength == -2)
 	return (s_dwMax > 0) ? TRUE : FALSE;
 
@@ -431,7 +457,7 @@ wait_for_single_object(
     return WaitForSingleObject(hHandle, dwMilliseconds);
 }
 # endif
-#endif
+#endif   // !FEAT_GUI_MSWIN || VIMDLL
 
     static void
 get_exe_name(void)
@@ -1014,7 +1040,7 @@ win32_kbd_patch_key(
 	return 1;
     }
 
-    if (pker->uChar.UnicodeChar != 0)
+    if (pker->uChar.UnicodeChar > 0 && pker->uChar.UnicodeChar < 0xfffd)
 	return 1;
 
     CLEAR_FIELD(abKeystate);
@@ -1080,7 +1106,8 @@ decode_key_event(
 
     // special cases
     if ((nModifs & CTRL) != 0 && (nModifs & ~CTRL) == 0
-					    && pker->uChar.UnicodeChar == NUL)
+					&& (pker->uChar.UnicodeChar == NUL
+					|| pker->uChar.UnicodeChar == 0xfffd))
     {
 	// Ctrl-6 is Ctrl-^
 	if (pker->wVirtualKeyCode == '6')
@@ -1168,7 +1195,113 @@ decode_key_event(
     return (*pch != NUL);
 }
 
-#endif // FEAT_GUI_MSWIN
+# if defined(FEAT_EVAL)
+    static int
+encode_key_event(dict_T *args, INPUT_RECORD *ir)
+{
+    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)
+	{
+	    semsg(_(e_invalid_argument_nr), (long)vkCode);
+	    return FALSE;
+	}
+
+	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;
+	int mods = (int)dict_get_number(args, "modifiers");
+	// Encode the win32 console key modifiers from Vim keyboard modifiers.
+	if (mods)
+	{
+	    // If "modifiers" is explicitly set in the args, then we reset any
+	    // remembered modifer key state that may have been set from earlier
+	    // mod-key-down events, even if they are not yet unset by earlier
+	    // mod-key-up events.
+	    s_dwMods = 0;
+	    if (mods & MOD_MASK_SHIFT)
+		ker.dwControlKeyState |= SHIFT_PRESSED;
+	    if (mods & MOD_MASK_CTRL)
+		ker.dwControlKeyState |= LEFT_CTRL_PRESSED;
+	    if (mods & MOD_MASK_ALT)
+		ker.dwControlKeyState |= LEFT_ALT_PRESSED;
+	}
+
+	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 && !ker.uChar.UnicodeChar; )
+	{
+	    if (VirtKeyMap[i].wVirtKey == vkCode)
+		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;
+    }
+    return TRUE;
+}
+# endif  // FEAT_EVAL
+#endif // !FEAT_GUI_MSWIN || VIMDLL
 
 
 /*
@@ -1179,7 +1312,7 @@ decode_key_event(
 mch_setmouse(int on UNUSED)
 {
 }
-#else
+#else  // !FEAT_GUI_MSWIN || VIMDLL
 static int g_fMouseAvail = FALSE;   // mouse present
 static int g_fMouseActive = FALSE;  // mouse enabled
 static int g_nMouseClick = -1;	    // mouse status
@@ -1234,21 +1367,21 @@ mch_bevalterm_changed(void)
 
 /*
  * Win32 console mouse scroll event handler.
- * Loosely based on the _OnMouseWheel() function in gui_w32.c
+ * Console version of the _OnMouseWheel() function in gui_w32.c
  *
  * This encodes the mouse scroll direction and keyboard modifiers into
  * g_nMouseClick, and the mouse position into g_xMouse and g_yMouse
  *
  * The direction of the scroll is decoded from two fields of the win32 console
  * mouse event record;
- *    1. The axis - vertical or horizontal flag - from dwEventFlags, and
+ *    1. The orientation - vertical or horizontal flag - from dwEventFlags
  *    2. The sign - positive or negative (aka delta flag) - from dwButtonState
  *
- * When scroll axis is HORIZONTAL
+ * When scroll orientation is HORIZONTAL
  *    -  If the high word of the dwButtonState member contains a positive
  *	 value, the wheel was rotated to the right.
  *    -  Otherwise, the wheel was rotated to the left.
- * When scroll axis is VERTICAL
+ * When scroll orientation is VERTICAL
  *    -  If the high word of the dwButtonState member contains a positive value,
  *       the wheel was rotated forward, away from the user.
  *    -  Otherwise, the wheel was rotated backward, toward the user.
@@ -1594,8 +1727,231 @@ decode_mouse_event(
     return TRUE;
 }
 
-#endif // FEAT_GUI_MSWIN
-
+# ifdef FEAT_EVAL
+    static int
+encode_mouse_event(dict_T *args, INPUT_RECORD *ir)
+{
+    int		button;
+    int		row;
+    int		col;
+    int		repeated_click;
+    int_u	mods;
+    int		move;
+
+    if (!dict_has_key(args, "row") || !dict_has_key(args, "col"))
+	return FALSE;
+
+    // Note: "move" is optional, requires fewer arguments
+    move = (int)dict_get_bool(args, "move", FALSE);
+    if (!move && (!dict_has_key(args, "button")
+	    || !dict_has_key(args, "multiclick")
+	    || !dict_has_key(args, "modifiers")))
+	return FALSE;
+
+    row = (int)dict_get_number(args, "row") - 1;
+    col = (int)dict_get_number(args, "col") - 1;
+
+    ir->EventType = MOUSE_EVENT;
+    MOUSE_EVENT_RECORD mer;
+    ZeroMemory(&mer, sizeof(mer));
+    mer.dwMousePosition.X  = col;
+    mer.dwMousePosition.Y  = row;
+
+    if (move)
+    {
+	mer.dwButtonState = 0;
+	mer.dwEventFlags = MOUSE_MOVED;
+    }
+    else
+    {
+	button = (int)dict_get_number(args, "button");
+	repeated_click = (int)dict_get_number(args, "multiclick");
+	mods = (int)dict_get_number(args, "modifiers");
+	// Reset the scroll values to known values.
+	// XXX: Remove this when/if the scroll step is made configurable.
+	mouse_set_hor_scroll_step(6);
+	mouse_set_vert_scroll_step(3);
+
+	switch (button)
+	{
+	    case MOUSE_LEFT:
+		mer.dwButtonState = FROM_LEFT_1ST_BUTTON_PRESSED;
+		mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+		break;
+	    case MOUSE_MIDDLE:
+		mer.dwButtonState = FROM_LEFT_2ND_BUTTON_PRESSED;
+		mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+		break;
+	    case MOUSE_RIGHT:
+		mer.dwButtonState = RIGHTMOST_BUTTON_PRESSED;
+		mer.dwEventFlags = repeated_click == 1 ? DOUBLE_CLICK : 0;
+		break;
+	    case MOUSE_RELEASE:
+		// umm?  Assume Left Release?
+		mer.dwEventFlags = 0;
+
+	    case MOUSE_MOVE:
+		mer.dwButtonState = 0;
+		mer.dwEventFlags = MOUSE_MOVED;
+		break;
+	    case MOUSE_X1:
+		mer.dwButtonState = FROM_LEFT_3RD_BUTTON_PRESSED;
+		break;
+	    case MOUSE_X2:
+		mer.dwButtonState = FROM_LEFT_4TH_BUTTON_PRESSED;
+		break;
+	    case MOUSE_4:  // KE_MOUSEDOWN;
+		mer.dwButtonState = -1;
+		mer.dwEventFlags = MOUSE_WHEELED;
+		break;
+	    case MOUSE_5:  // KE_MOUSEUP;
+		mer.dwButtonState = +1;
+		mer.dwEventFlags = MOUSE_WHEELED;
+		break;
+	    case MOUSE_6:  // KE_MOUSELEFT;
+		mer.dwButtonState = -1;
+		mer.dwEventFlags = MOUSE_HWHEELED;
+		break;
+	    case MOUSE_7:  // KE_MOUSERIGHT;
+		mer.dwButtonState = +1;
+		mer.dwEventFlags = MOUSE_HWHEELED;
+		break;
+	    default:
+		semsg(_(e_invalid_argument_str), "button");
+		return FALSE;
+	}
+    }
+
+    mer.dwControlKeyState = 0;
+    if (mods != 0)
+    {
+	// Encode the win32 console key modifiers from Vim MOUSE modifiers.
+	if (mods & MOUSE_SHIFT)
+	    mer.dwControlKeyState |= SHIFT_PRESSED;
+	if (mods & MOUSE_CTRL)
+	    mer.dwControlKeyState |= LEFT_CTRL_PRESSED;
+	if (mods & MOUSE_ALT)
+	    mer.dwControlKeyState |= LEFT_ALT_PRESSED;
+    }
+    ir->Event.MouseEvent = mer;
+    return TRUE;
+}
+# endif  // FEAT_EVAL
+
+    static int
+write_input_record_buffer(INPUT_RECORD* irEvents, int nLength)
+{
+    int nCount = 0;
+    while (nCount < nLength)
+    {
+	input_record_buffer.length++;
+	input_record_buffer_node_T *event_node =
+				    malloc(sizeof(input_record_buffer_node_T));
+	event_node->ir = irEvents[nCount++];
+	event_node->next = NULL;
+	if (input_record_buffer.tail == NULL)
+	{
+	    input_record_buffer.head = event_node;
+	    input_record_buffer.tail = event_node;
+	}
+	else
+	{
+	    input_record_buffer.tail->next = event_node;
+	    input_record_buffer.tail = input_record_buffer.tail->next;
+	}
+    }
+    return nCount;
+}
+
+    static int
+read_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength)
+{
+    int nCount = 0;
+    while (nCount < nMaxLength && input_record_buffer.head != NULL)
+    {
+	input_record_buffer.length--;
+	input_record_buffer_node_T *pop_head = input_record_buffer.head;
+	irEvents[nCount++] = pop_head->ir;
+	input_record_buffer.head = pop_head->next;
+	vim_free(pop_head);
+	if (input_record_buffer.length == 0)
+	    input_record_buffer.tail = NULL;
+    }
+    return nCount;
+}
+    static int
+peek_input_record_buffer(INPUT_RECORD* irEvents, int nMaxLength)
+{
+    int nCount = 0;
+    input_record_buffer_node_T *temp =  input_record_buffer.head;
+    while (nCount < nMaxLength && temp != NULL)
+    {
+	irEvents[nCount++] = temp->ir;
+	temp = temp->next;
+    }
+    return nCount;
+}
+#endif // !FEAT_GUI_MSWIN || VIMDLL
+
+#ifdef FEAT_EVAL
+/*
+ * The 'test_mswin_event' function is for testing Vim's low-level handling of
+ * user input events.  ie, this manages the encoding of INPUT_RECORD events
+ * so that we have a way to test how Vim decodes INPUT_RECORD events in Windows
+ * consoles.
+ *
+ * The 'test_mswin_event' function is based on 'test_gui_event'.  In fact, when
+ * the Windows GUI is running, the arguments; 'event' and 'args', are the same.
+ * So, it acts as an alias for 'test_gui_event' for the Windows GUI.
+ *
+ * When the Windows console is running, the arguments; 'event' and 'args', are
+ * a subset of what 'test_gui_event' handles, ie, only "key" and "mouse"
+ * events are encoded as INPUT_RECORD events.
+ *
+ * Note: INPUT_RECORDs are only used by the Windows console, not the GUI.  The
+ * GUI sends MSG structs instead.
+ */
+    int
+test_mswin_event(char_u *event, dict_T *args)
+{
+    int lpEventsWritten = 0;
+
+# if defined(VIMDLL) || defined(FEAT_GUI_MSWIN)
+    if (gui.in_use)
+	return test_gui_w32_sendevent(event, args);
+# endif
+
+# if defined(VIMDLL) || !defined(FEAT_GUI_MSWIN)
+
+// Currently implemented event record types are; KEY_EVENT and MOUSE_EVENT
+// Potentially could also implement: FOCUS_EVENT and WINDOW_BUFFER_SIZE_EVENT
+// Maybe also:  MENU_EVENT
+
+    INPUT_RECORD ir;
+    BOOL input_encoded = FALSE;
+    if (STRCMP(event, "key") == 0)
+	input_encoded = encode_key_event(args, &ir);
+    else if (STRCMP(event, "mouse") == 0)
+	input_encoded = encode_mouse_event(args, &ir);
+    else
+    {
+	semsg(_(e_invalid_value_for_argument_str_str), "event", event);
+	return FALSE;
+    }
+
+    // Ideally, WriteConsoleInput would be used to inject these low-level
+    // events.  But, this doesnt work well in the CI test environment.  So
+    // implementing an input_record_buffer instead.
+    if (input_encoded)
+	lpEventsWritten = write_input_record_buffer(&ir, 1);
+
+    if (STRCMP(event, "mouse") == 0)
+	exec_normal(TRUE, TRUE, TRUE);
+
+# endif
+    return lpEventsWritten;
+}
+#endif // FEAT_EVAL
 
 #ifdef MCH_CURSOR_SHAPE
 /*