summaryrefslogtreecommitdiffstats
path: root/src/os_win32.c
diff options
context:
space:
mode:
authorChristopher Plewright <chris@createng.com>2022-12-20 20:01:58 +0000
committerBram Moolenaar <Bram@vim.org>2022-12-20 20:01:58 +0000
commit20b795e0eba6c933868c8f7cf62fb85d4f007688 (patch)
treeac278d8ec958a58ba6c5a90fba8aef7270abc72f /src/os_win32.c
parent418b54788106efd94bbc71a4b100afae1080cbfc (diff)
patch 9.0.1084: code handling low level MS-Windows events cannot be testedv9.0.1084
Problem: Code handling low level MS-Windows events cannot be tested. Solution: Add test_mswin_event() and tests using it. (Christopher Plewright, closes #11622)
Diffstat (limited to 'src/os_win32.c')
-rw-r--r--src/os_win32.c378
1 files changed, 367 insertions, 11 deletions
diff --git a/src/os_win32.c b/src/os_win32.c
index 334d9735ee..f32d486a52 100644
--- a/src/os_win32.c
+++ b/src/os_win32.c
@@ -177,6 +177,25 @@ static void gotoxy(unsigned x, unsigned y);
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 = 0xc0c0c0; // white
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
/*