Hi, This issue was brought up in bug 4685: http://bugs.winehq.org/show_bug.cgi?id=4685 with some comments from Vitaliy Margolen at http://www.winehq.org/pipermail/wine-devel/2006-March/045771.html Basically, using SendInput() to simulate VK_LMENU/VK_RMENU would generate WM_KEYUP instead of WM_SYSKEYUP, which created problems in bringing up menus using an automated test framework. Thanks, Thomas Kho 2006-04-05 Thomas Kho * dlls/user/tests/input.c: user: add black box test for SendInput() * dlls/x11drv/keyboard.c: x11drv: flag additional keystate for CTRL, ALT, SHIFT and send correct messages * server/queue.c: server: flag additional keystate for CTRL, ALT, SHIFT dlls/user/tests/input.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++- dlls/x11drv/keyboard.c | 22 ++++++- server/queue.c | 12 +++ 3 files changed, 178 insertions(+), 4 deletions(-) Signed-off-by: Thomas Kho Index: dlls/user/tests/input.c =================================================================== RCS file: /home/wine/wine/dlls/user/tests/input.c,v retrieving revision 1.7 diff -u -r1.7 input.c --- dlls/user/tests/input.c 6 Dec 2004 11:51:29 -0000 1.7 +++ dlls/user/tests/input.c 5 Apr 2006 23:35:20 -0000 @@ -336,7 +336,7 @@ return 0; } -START_TEST(input) +static void test_Input_whitebox(void) { MSG msg; WNDCLASSA wclass; @@ -366,3 +366,149 @@ SendMessageA(hWndTest, WM_USER, 0, 0); DestroyWindow(hWndTest); } + +static void empty_message_queue(void) { + MSG msg; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +struct transition_s { + BYTE wVk; + BYTE before_state; + BYTE _todo_wine; +}; + +struct sendinput_test_s { + WORD wVk; + DWORD dwFlags; + struct transition_s expected_transitions[MAXKEYEVENTS+1]; + UINT expected_messages[MAXKEYMESSAGES+1]; +} sendinput_test[] = { + /* test ALT+F */ + {VK_LMENU, 0, {{0x12, 0x00, 0}, {0xa4, 0x00, 0}, {0}}, {0x104, 0}}, + {'F', 0, {{0x46, 0x00, 0}, {0}}, {0x104, 0x106, 0x112, 0}}, + {'F', KEYEVENTF_KEYUP, {{0x46, 0x80, 0}, {0}}, {0x105, 0}}, + {VK_LMENU, KEYEVENTF_KEYUP, {{0x12, 0x80, 0}, {0xa4, 0x80, 0}, {0}}, + {0x101, 0}}, + + /* test CTRL+O */ + {VK_LCONTROL, 0, {{0x11, 0x00, 0}, {0xa2, 0x00, 0}, {0}}, {0x100, 0}}, + {'O', 0, {{0x4f, 0x00, 0}, {0}}, {0x100, 0x102, 0}}, + {'O', KEYEVENTF_KEYUP, {{0x4f, 0x80, 0}, {0}}, {0x101, 0}}, + {VK_LCONTROL, KEYEVENTF_KEYUP, + {{0x11, 0x80, 0}, {0xa2, 0x80, 0}, {0}}, {0x101, 0}}, + + /* test ALT+CTRL+X */ + {VK_LMENU, 0, {{0x12, 0x00, 0}, {0xa4, 0x00, 0}, {0}}, {0x104, 0}}, + {VK_LCONTROL, 0, {{0x11, 0x00, 0}, {0xa2, 0x00, 0}, {0}}, {0x100, 0}}, + {'X', 0, {{0x58, 0x00, 0}, {0}}, {0x100, 0}}, + {'X', KEYEVENTF_KEYUP, {{0x58, 0x80, 0}, {0}}, {0x101, 0}}, + {VK_LCONTROL, KEYEVENTF_KEYUP, + {{0x11, 0x80, 0}, {0xa2, 0x80, 0}, {0}}, {0x105, 0}}, + {VK_LMENU, KEYEVENTF_KEYUP, {{0x12, 0x80, 0}, {0xa4, 0x80, 0}, {0}}, + {0x101, 0}}, + + {0, 0, {{0}}, {0}} /* end */ +}; + +static struct sendinput_test_s *pTest = sendinput_test; +static UINT *pMsg = sendinput_test[0].expected_messages; + +/* Verify that only specified key state transitions occur */ +void compare_and_check(int id, BYTE *ks1, BYTE *ks2, struct transition_s *t) { + int i; + while (t->wVk) { + int matched = ((ks1[t->wVk]&0x80) == (t->before_state&0x80) + && (ks2[t->wVk]&0x80) == (~t->before_state&0x80)); + if (t->_todo_wine) { + todo_wine { + ok(matched, "%02d: %02x from %02x -> %02x " + "instead of %02x -> %02x\n", id, t->wVk, + ks1[t->wVk]&0x80, ks2[t->wVk]&0x80, t->before_state, + ~t->before_state&0x80); + } + } else { + ok(matched, "%02d: %02x from %02x -> %02x " + "instead of %02x -> %02x\n", id, t->wVk, + ks1[t->wVk]&0x80, ks2[t->wVk]&0x80, t->before_state, + ~t->before_state&0x80); + } + ks2[t->wVk] = ks1[t->wVk]; /* clear the match */ + t++; + } + for (i = 0; i < 256; i++) + ok(ks2[i] == ks1[i], "%02d: %02x from %02x -> %02x unexpected\n", + id, i, ks1[i], ks2[i]); +} + +/* WndProc2 checks that we get at least the messages specified */ +static LRESULT CALLBACK WndProc2(HWND hWnd, UINT Msg, WPARAM wParam, + LPARAM lParam) +{ + if (pTest->wVk != 0) { /* not end */ + while(pTest->wVk != 0 && *pMsg == 0) { + pTest++; + pMsg = pTest->expected_messages; + } + if (Msg == *pMsg) + pMsg++; + } + return DefWindowProc(hWnd, Msg, wParam, lParam); +} + +static void test_Input_blackbox(void) +{ + INPUT i; + int ii; + BYTE ks1[256], ks2[256]; + LONG_PTR prevWndProc; + + HWND window; + window = CreateWindow("Static", NULL, WS_POPUP|WS_HSCROLL|WS_VSCROLL + |WS_VISIBLE, 0, 0, 200, 60, NULL, NULL, + NULL, NULL); + ok(window != NULL, "error: %d\n", (int) GetLastError()); + + /* must process all initial messages, otherwise X11DRV_KeymapNotify unsets + * key state set by SendInput(). */ + empty_message_queue(); + + prevWndProc = SetWindowLongPtr(window, GWLP_WNDPROC, (LONG_PTR) WndProc2); + ok(prevWndProc != 0 || (prevWndProc == 0 && GetLastError() == 0), + "error: %d\n", (int) GetLastError()); + + i.type = INPUT_KEYBOARD; + i.ki.wScan = 0; + i.ki.time = 0; + i.ki.dwExtraInfo = 0; + + for (ii = 0; ii < sizeof(sendinput_test)/sizeof(struct sendinput_test_s)-1; + ii++) { + GetKeyboardState(ks1); + i.ki.dwFlags = sendinput_test[ii].dwFlags;; + i.ki.wVk = sendinput_test[ii].wVk; + SendInput(1, &i, sizeof(INPUT)); + empty_message_queue(); + GetKeyboardState(ks2); + compare_and_check(ii, ks1, ks2, + sendinput_test[ii].expected_transitions); + } + + /* *pMsg should be 0 and (++pTest)->wVk should be 0 */ + if (pTest->wVk && *pMsg == 0) pTest++; + while(pTest->wVk && pTest->expected_messages[0] == 0) { + ++pTest; + } + ok(*pMsg == 0 && pTest->wVk == 0, + "not enough messages found; looking for %x\n", *pMsg); + DestroyWindow(window); +} + +START_TEST(input) +{ + test_Input_whitebox(); + test_Input_blackbox(); +} Index: dlls/x11drv/keyboard.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/keyboard.c,v retrieving revision 1.87 diff -u -r1.87 keyboard.c --- dlls/x11drv/keyboard.c 4 Apr 2006 10:38:56 -0000 1.87 +++ dlls/x11drv/keyboard.c 5 Apr 2006 23:35:20 -0000 @@ -1103,6 +1103,19 @@ static BOOL NumState=FALSE, CapsState=FALSE; +/*********************************************************************** + * VkStripLR + */ +static WORD VkStripLR(WORD wVk) +{ + if (wVk == VK_LMENU || wVk == VK_RMENU) + return VK_MENU; + else if (wVk == VK_LCONTROL || wVk == VK_RCONTROL) + return VK_CONTROL; + else if (wVk == VK_LSHIFT || wVk == VK_RSHIFT) + return VK_SHIFT; + return wVk; +} /*********************************************************************** * X11DRV_send_keyboard_input @@ -1128,14 +1141,16 @@ { message = WM_KEYUP; if ((key_state_table[VK_MENU] & 0x80) && - ((wVk == VK_MENU) || (wVk == VK_CONTROL) || !(key_state_table[VK_CONTROL] & 0x80))) + ((VkStripLR(wVk) == VK_MENU) || (VkStripLR(wVk) == VK_CONTROL) + || !(key_state_table[VK_CONTROL] & 0x80))) { if( TrackSysKey == VK_MENU || /* -down/-up sequence */ - (wVk != VK_MENU)) /* -down...-up */ + (VkStripLR(wVk) != VK_MENU)) /* -down...-up */ message = WM_SYSKEYUP; TrackSysKey = 0; } key_state_table[wVk] &= ~0x80; + key_state_table[VkStripLR(wVk)] &= ~0x80; keylp.lp1.previous = 1; keylp.lp1.transition = 1; } @@ -1145,12 +1160,13 @@ keylp.lp1.transition = 0; if (!(key_state_table[wVk] & 0x80)) key_state_table[wVk] ^= 0x01; key_state_table[wVk] |= 0xc0; + key_state_table[VkStripLR(wVk)] |= 0xc0; message = WM_KEYDOWN; if ((key_state_table[VK_MENU] & 0x80) && !(key_state_table[VK_CONTROL] & 0x80)) { message = WM_SYSKEYDOWN; - TrackSysKey = wVk; + TrackSysKey = VkStripLR(wVk); } } Index: server/queue.c =================================================================== RCS file: /home/wine/wine/server/queue.c,v retrieving revision 1.76 diff -u -r1.76 queue.c --- server/queue.c 14 Feb 2006 13:31:10 -0000 1.76 +++ server/queue.c 5 Apr 2006 23:35:21 -0000 @@ -1131,6 +1131,18 @@ case VK_MENU: set_input_key_state( input, extended ? VK_RMENU : VK_LMENU, down ); break; + case VK_LCONTROL: + case VK_RCONTROL: + set_input_key_state( input, VK_CONTROL, down ); + break; + case VK_LMENU: + case VK_RMENU: + set_input_key_state( input, VK_MENU, down ); + break; + case VK_LSHIFT: + case VK_RSHIFT: + set_input_key_state( input, VK_SHIFT, down ); + break; } break; }