1
0
forked from aniani/vim

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)
This commit is contained in:
Christopher Plewright
2022-12-20 20:01:58 +00:00
committed by Bram Moolenaar
parent 418b547881
commit 20b795e0eb
17 changed files with 1490 additions and 74 deletions

View File

@@ -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
/*