diff options
-rw-r--r-- | runtime/doc/builtin.txt | 2 | ||||
-rw-r--r-- | runtime/doc/testing.txt | 72 | ||||
-rw-r--r-- | runtime/doc/usr_41.txt | 1 | ||||
-rw-r--r-- | src/evalfunc.c | 9 | ||||
-rw-r--r-- | src/gui_w32.c | 163 | ||||
-rw-r--r-- | src/os_win32.c | 378 | ||||
-rw-r--r-- | src/proto/gui_w32.pro | 2 | ||||
-rw-r--r-- | src/proto/os_win32.pro | 1 | ||||
-rw-r--r-- | src/proto/testing.pro | 1 | ||||
-rw-r--r-- | src/term.c | 2 | ||||
-rw-r--r-- | src/testdir/Make_all.mak | 2 | ||||
-rw-r--r-- | src/testdir/mouse.vim | 196 | ||||
-rw-r--r-- | src/testdir/test_gui.vim | 6 | ||||
-rw-r--r-- | src/testdir/test_mswin_event.vim | 651 | ||||
-rw-r--r-- | src/testdir/test_termcodes.vim | 35 | ||||
-rw-r--r-- | src/testing.c | 45 | ||||
-rw-r--r-- | src/version.c | 2 |
17 files changed, 1492 insertions, 76 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 7ff969d7cb..05e8d701f8 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -666,6 +666,8 @@ test_garbagecollect_soon() none free memory soon for testing test_getvalue({string}) any get value of an internal variable test_gui_event({event}, {args}) bool generate a GUI event for testing test_ignore_error({expr}) none ignore a specific error +test_mswin_event({event}, {args}) + bool generate MS-Windows event for testing test_null_blob() Blob null value for testing test_null_channel() Channel null value for testing test_null_dict() Dict null value for testing diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt index 558553f5ac..b03726572b 100644 --- a/runtime/doc/testing.txt +++ b/runtime/doc/testing.txt @@ -94,7 +94,7 @@ test_gui_event({event}, {args}) "findrepl" search and replace text. "mouse" mouse button click event. "scrollbar" move or drag the scrollbar. - "sendevent" send a low-level GUI event. + "key" send a low-level keyboard event. "tabline" select a tab page by mouse click. "tabmenu" select a tabline menu entry. @@ -178,8 +178,8 @@ test_gui_event({event}, {args}) dragging: 1 to drag the scrollbar and 0 to click in the scrollbar. - "sendevent": - Send a low-level GUI event (e.g. key-up or down). + "key": + Send a low-level keyboard event (e.g. key-up or down). Currently only supported on MS-Windows. The supported items in {args} are: event: The supported string values are: @@ -223,6 +223,72 @@ test_ignore_error({expr}) *test_ignore_error()* Can also be used as a |method|: > GetErrorText()->test_ignore_error() + +test_mswin_event({event}, {args}) *test_mswin_event()* + Generate a low-level MS-Windows {event} with arguments {args} + for testing Vim functionality. It works for MS-Windows GUI + and for the console. + + {event} is a String and the supported values are: + "mouse" mouse event. + "key" keyboard event. + + "mouse": + Inject either a mouse button click, or a mouse move, event. + The supported items in {args} are: + button: mouse button. The supported values are: + 0 right mouse button + 1 middle mouse button + 2 left mouse button + 3 mouse button release + 4 scroll wheel down + 5 scroll wheel up + 6 scroll wheel left + 7 scroll wheel right + row: mouse click row number. The first row of the + Vim window is 1 and the last row is 'lines'. + col: mouse click column number. The maximum value + of {col} is 'columns'. + Note: row and col are always interpreted as + screen cells for the console application. + But, they may be interpreted as pixels + for the GUI, depending on "cell". + multiclick: set to 1 to inject a double-click mouse event. + modifiers: key modifiers. The supported values are: + 4 shift is pressed + 8 alt is pressed + 16 ctrl is pressed + move: Optional; if used and TRUE then a mouse move + event can be generated. + Only {args} row: and col: are used and + required. + Only results in an event when 'mousemoveevent' + is set or a popup uses mouse move events. + cell: Optional for the GUI: when present and TRUE + then "move" uses screen cells instead of pixel + positions. Not used by the console. + + "key": + Send a low-level keyboard event (e.g. keyup or keydown). + The supported items in {args} are: + 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. + modifiers: Optional; key modifiers. + The supported values are: + 2 shift is pressed + 4 ctrl is pressed + 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. + + Can also be used as a |method|: > + GetEvent()->test_mswin_event({args}) +< test_null_blob() *test_null_blob()* Return a |Blob| that is null. Only useful for testing. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index aa51fe3e96..c67bd99ef2 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1186,6 +1186,7 @@ Testing: *test-functions* test_getvalue() get value of an internal variable test_gui_event() generate a GUI event for testing test_ignore_error() ignore a specific error message + test_mswin_event() generate an MS-Windows event test_null_blob() return a null Blob test_null_channel() return a null Channel test_null_dict() return a null Dict diff --git a/src/evalfunc.c b/src/evalfunc.c index 3db4bb7b24..b96fc472c3 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -2694,6 +2694,8 @@ static funcentry_T global_functions[] = ret_bool, f_test_gui_event}, {"test_ignore_error", 1, 1, FEARG_1, arg1_string, ret_void, f_test_ignore_error}, + {"test_mswin_event", 2, 2, FEARG_1, arg2_string_dict, + ret_number, f_test_mswin_event}, {"test_null_blob", 0, 0, 0, NULL, ret_blob, f_test_null_blob}, {"test_null_channel", 0, 0, 0, NULL, @@ -4387,7 +4389,12 @@ f_feedkeys(typval_T *argvars, typval_T *rettv UNUSED) if (*keys != NUL || execute) { - if (lowlevel) + if (lowlevel +#ifdef FEAT_VTP + && (!is_term_win32() + || (keys[0] == 3 && ctrl_c_interrupts && typed)) +#endif + ) { #ifdef USE_INPUT_BUF ch_log(NULL, "feedkeys() lowlevel: %s", keys); diff --git a/src/gui_w32.c b/src/gui_w32.c index 6733ac0624..b4a33d9868 100644 --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -8643,41 +8643,176 @@ netbeans_draw_multisign_indicator(int row) #endif #if defined(FEAT_EVAL) || defined(PROTO) - int -test_gui_w32_sendevent(dict_T *args) + +// TODO: at the moment, this is just a copy of test_gui_mouse_event. +// But, we could instead generate actual Win32 mouse event messages, +// ie. to make it consistent wih test_gui_w32_sendevent_keyboard. + static int +test_gui_w32_sendevent_mouse(dict_T *args) { - char_u *event; - INPUT inputs[1]; + if (!dict_has_key(args, "row") || !dict_has_key(args, "col")) + return FALSE; - event = dict_get_string(args, "event", TRUE); - if (event == NULL) + // Note: "move" is optional, requires fewer arguments + int 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; - ZeroMemory(inputs, sizeof(inputs)); + int row = (int)dict_get_number(args, "row"); + int col = (int)dict_get_number(args, "col"); - if (STRICMP(event, "keydown") == 0 || STRICMP(event, "keyup") == 0) + if (move) + { + // the "move" argument expects row and col coordnates to be in pixels, + // unless "cell" is specified and is TRUE. + if (dict_get_bool(args, "cell", FALSE)) + { + // calculate the middle of the character cell + // Note: Cell coordinates are 1-based from vimscript + int pY = (row - 1) * gui.char_height + gui.char_height / 2; + int pX = (col - 1) * gui.char_width + gui.char_width / 2; + gui_mouse_moved(pX, pY); + } + else + gui_mouse_moved(col, row); + } + else { - WORD vkCode; + int button = (int)dict_get_number(args, "button"); + int repeated_click = (int)dict_get_number(args, "multiclick"); + int_u 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); + + gui_send_mouse_event(button, TEXT_X(col - 1), TEXT_Y(row - 1), + repeated_click, mods); + } + return TRUE; +} - vkCode = dict_get_number_def(args, "keycode", 0); + static int +test_gui_w32_sendevent_keyboard(dict_T *args) +{ + INPUT inputs[1]; + INPUT modkeys[3]; + SecureZeroMemory(inputs, sizeof(INPUT)); + SecureZeroMemory(modkeys, 3 * sizeof(INPUT)); + + 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; } + BOOL isModKey = (vkCode == VK_SHIFT || vkCode == VK_CONTROL + || vkCode == VK_MENU || vkCode == VK_LSHIFT || vkCode == VK_RSHIFT + || vkCode == VK_LCONTROL || vkCode == VK_RCONTROL + || vkCode == VK_LMENU || vkCode == VK_RMENU ); + + BOOL unwrapMods = FALSE; + int mods = (int)dict_get_number(args, "modifiers"); + + // If there are modifiers in the args, and it is not a keyup event and + // vkCode is not a modifier key, then we generate virtual modifier key + // messages before sending the actual key message. + if(mods && STRICMP(event, "keydown") == 0 && !isModKey) + { + int n = 0; + if (mods & MOD_MASK_SHIFT) + { + modkeys[n].type = INPUT_KEYBOARD; + modkeys[n].ki.wVk = VK_LSHIFT; + n++; + } + if (mods & MOD_MASK_CTRL) + { + modkeys[n].type = INPUT_KEYBOARD; + modkeys[n].ki.wVk = VK_LCONTROL; + n++; + } + if (mods & MOD_MASK_ALT) + { + modkeys[n].type = INPUT_KEYBOARD; + modkeys[n].ki.wVk = VK_LMENU; + n++; + } + if (n) + { + (void)SetForegroundWindow(s_hwnd); + SendInput(n, modkeys, sizeof(INPUT)); + } + } + inputs[0].type = INPUT_KEYBOARD; inputs[0].ki.wVk = vkCode; if (STRICMP(event, "keyup") == 0) + { inputs[0].ki.dwFlags = KEYEVENTF_KEYUP; + if(!isModKey) + unwrapMods = TRUE; + } + (void)SetForegroundWindow(s_hwnd); SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); - } - else - semsg(_(e_invalid_argument_str), event); + vim_free(event); - vim_free(event); + if (unwrapMods) + { + modkeys[0].type = INPUT_KEYBOARD; + modkeys[0].ki.wVk = VK_LSHIFT; + modkeys[0].ki.dwFlags = KEYEVENTF_KEYUP; + + modkeys[1].type = INPUT_KEYBOARD; + modkeys[1].ki.wVk = VK_LCONTROL; + modkeys[1].ki.dwFlags = KEYEVENTF_KEYUP; + modkeys[2].type = INPUT_KEYBOARD; + modkeys[2].ki.wVk = VK_LMENU; + modkeys[2].ki.dwFlags = KEYEVENTF_KEYUP; + + (void)SetForegroundWindow(s_hwnd); + SendInput(3, modkeys, sizeof(INPUT)); + } + } + 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; } + + int +test_gui_w32_sendevent(char_u *event, dict_T *args) +{ + if (STRICMP(event, "key") == 0) + return test_gui_w32_sendevent_keyboard(args); + else if (STRICMP(event, "mouse") == 0) + return test_gui_w32_sendevent_mouse(args); + else + { + semsg(_(e_invalid_value_for_argument_str_str), "event", event); + return FALSE; + } +} #endif 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 /* diff --git a/src/proto/gui_w32.pro b/src/proto/gui_w32.pro index cab3343249..c5c6585dbc 100644 --- a/src/proto/gui_w32.pro +++ b/src/proto/gui_w32.pro @@ -96,5 +96,5 @@ void gui_mch_post_balloon(BalloonEval *beval, char_u *mesg); BalloonEval *gui_mch_create_beval_area(void *target, char_u *mesg, void (*mesgCB)(BalloonEval *, int), void *clientData); void gui_mch_destroy_beval_area(BalloonEval *beval); void netbeans_draw_multisign_indicator(int row); -int test_gui_w32_sendevent(dict_T *args); +int test_gui_w32_sendevent(char_u *event, dict_T *args); /* vim: set ft=c : */ diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro index a6bbd33526..9f8b969583 100644 --- a/src/proto/os_win32.pro +++ b/src/proto/os_win32.pro @@ -9,6 +9,7 @@ void dyn_libintl_end(void); void PlatformId(void); void mch_setmouse(int on); void mch_bevalterm_changed(void); +int test_mswin_event(char_u *event, dict_T *args); void mch_update_cursor(void); int mch_char_avail(void); int mch_check_messages(void); diff --git a/src/proto/testing.pro b/src/proto/testing.pro index 2192e91ec7..dea4f75352 100644 --- a/src/proto/testing.pro +++ b/src/proto/testing.pro @@ -33,6 +33,7 @@ void f_test_null_string(typval_T *argvars, typval_T *rettv); void f_test_unknown(typval_T *argvars, typval_T *rettv); void f_test_void(typval_T *argvars, typval_T *rettv); void f_test_setmouse(typval_T *argvars, typval_T *rettv); +void f_test_mswin_event(typval_T *argvars, typval_T *rettv); void f_test_gui_event(typval_T *argvars, typval_T *rettv); void f_test_settime(typval_T *argvars, typval_T *rettv); /* vim: set ft=c : */ diff --git a/src/term.c b/src/term.c index cd6b3b9cf6..a91813d7aa 100644 --- a/src/term.c +++ b/src/term.c @@ -827,7 +827,7 @@ static tcap_entry_T builtin_pcansi[] = { }; /* - * These codes are valid for the Win32 Console . The entries that start with + * These codes are valid for the Win32 Console. The entries that start with * ESC | are translated into console calls in os_win32.c. The function keys * are also translated in os_win32.c. */ diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak index 886bb08ad8..fdca9f4193 100644 --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -210,6 +210,7 @@ NEW_TESTS = \ test_modeless \ test_modeline \ test_move \ + test_mswin_event \ test_mzscheme \ test_nested_function \ test_netbeans \ @@ -454,6 +455,7 @@ NEW_TESTS_RES = \ test_mksession.res \ test_modeless.res \ test_modeline.res \ + test_mswin_event.res \ test_mzscheme.res \ test_nested_function.res \ test_netbeans.res \ diff --git a/src/testdir/mouse.vim b/src/testdir/mouse.vim index d59ad0eab6..e2979b771c 100644 --- a/src/testdir/mouse.vim +++ b/src/testdir/mouse.vim @@ -20,6 +20,27 @@ else let g:Ttymouse_netterm = [] endif +" Vim Mouse Codes. +" Used by the GUI and by MS-Windows Consoles. +" Keep these in sync with vim.h +let s:MOUSE_CODE = { + \ 'BTN_LEFT' : 0x00, + \ 'BTN_MIDDLE' : 0x01, + \ 'BTN_RIGHT' : 0x02, + \ 'BTN_RELEASE' : 0x03, + \ 'BTN_X1' : 0x300, + \ 'BTN_X2' : 0x400, + \ 'SCRL_DOWN' : 0x100, + \ 'SCRL_UP' : 0x200, + \ 'SCRL_LEFT' : 0x500, + \ 'SCRL_RIGHT' : 0x600, + \ 'MOVE' : 0x700, + \ 'MOD_SHIFT' : 0x04, + \ 'MOD_ALT' : 0x08, + \ 'MOD_CTRL' : 0x10, + \ } + + " Helper function to emit a terminal escape code. func TerminalEscapeCode(code, row, col, m) if &ttymouse ==# 'xterm2' @@ -47,6 +68,31 @@ func NettermEscapeCode(row, col) return printf("\<Esc>}%d,%d\r", a:row, a:col) endfunc +" Send low level mouse event to MS-Windows consoles or GUI +func MSWinMouseEvent(button, row, col, move, multiclick, modifiers) + let args = { } + let args.button = a:button + " Scroll directions are inverted in the GUI, no idea why. + if has('gui_running') + if a:button == s:MOUSE_CODE.SCRL_UP + let args.button = s:MOUSE_CODE.SCRL_DOWN + elseif a:button == s:MOUSE_CODE.SCRL_DOWN + let args.button = s:MOUSE_CODE.SCRL_UP + elseif a:button == s:MOUSE_CODE.SCRL_LEFT + let args.button = s:MOUSE_CODE.SCRL_RIGHT + elseif a:button == s:MOUSE_CODE.SCRL_RIGHT + let args.button = s:MOUSE_CODE.SCRL_LEFT + endif + endif + let args.row = a:row + let args.col = a:col + let args.move = a:move + let args.multiclick = a:multiclick + let args.modifiers = a:modifiers + call test_mswin_event("mouse", args) + unlet args +endfunc + func MouseLeftClickCode(row, col) if &ttymouse ==# 'dec' return DecEscapeCode(2, 4, a:row, a:col) @@ -58,7 +104,11 @@ func MouseLeftClickCode(row, col) endfunc func MouseLeftClick(row, col) - call feedkeys(MouseLeftClickCode(a:row, a:col), 'Lx!') + if has('win32') + call MSWinMouseEvent(s:MOUSE_CODE.BTN_LEFT, a:row, a:col, 0, 0, 0) + else + call feedkeys(MouseLeftClickCode(a:row, a:col), 'Lx!') + endif endfunc func MouseMiddleClickCode(row, col) @@ -70,7 +120,11 @@ func MouseMiddleClickCode(row, col) endfunc func MouseMiddleClick(row, col) - call feedkeys(MouseMiddleClickCode(a:row, a:col), 'Lx!') + if has('win32') + call MSWinMouseEvent(s:MOUSE_CODE.BTN_MIDDLE, a:row, a:col, 0, 0, 0) + else + call feedkeys(MouseMiddleClickCode(a:row, a:col), 'Lx!') + endif endfunc func MouseRightClickCode(row, col) @@ -82,7 +136,11 @@ func MouseRightClickCode(row, col) endfunc func MouseRightClick(row, col) - call feedkeys(MouseRightClickCode(a:row, a:col), 'Lx!') + if |