#include "CMSWindowsPrimaryScreen.h" #include "CServer.h" #include "CMSWindowsClipboard.h" #include "CPlatform.h" #include "XScreen.h" #include "XSynergy.h" #include "CThread.h" #include "CLog.h" #include // // CMSWindowsPrimaryScreen // CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen() : m_server(NULL), m_threadID(0), m_desk(NULL), m_deskName(), m_window(NULL), m_active(false), m_mark(0), m_markReceived(0), m_nextClipboardWindow(NULL), m_clipboardOwner(NULL) { // load the hook library m_hookLibrary = LoadLibrary("synrgyhk"); if (m_hookLibrary == NULL) { log((CLOG_ERR "failed to load hook library")); throw XScreenOpenFailure(); } m_setZone = (SetZoneFunc)GetProcAddress(m_hookLibrary, "setZone"); m_setRelay = (SetRelayFunc)GetProcAddress(m_hookLibrary, "setRelay"); m_install = (InstallFunc)GetProcAddress(m_hookLibrary, "install"); m_uninstall = (UninstallFunc)GetProcAddress(m_hookLibrary, "uninstall"); if (m_setZone == NULL || m_setRelay == NULL || m_install == NULL || m_uninstall == NULL) { log((CLOG_ERR "invalid hook library")); FreeLibrary(m_hookLibrary); throw XScreenOpenFailure(); } // detect operating system m_is95Family = CPlatform::isWindows95Family(); // make sure this thread has a message queue MSG dummy; PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); } CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen() { assert(m_hookLibrary != NULL); assert(m_window == NULL); // done with hook library FreeLibrary(m_hookLibrary); } void CMSWindowsPrimaryScreen::run() { // must call run() from same thread as open() assert(m_threadID == GetCurrentThreadId()); // change our priority CThread::getCurrentThread().setPriority(-3); // poll input desktop to see if it changes (preTranslateMessage() // handles WM_TIMER) UINT timer = 0; if (!m_is95Family) { SetTimer(NULL, 0, 200, NULL); } // run event loop log((CLOG_INFO "entering event loop")); doRun(); log((CLOG_INFO "exiting event loop")); // remove timer if (!m_is95Family) { KillTimer(NULL, timer); } } void CMSWindowsPrimaryScreen::stop() { doStop(); } void CMSWindowsPrimaryScreen::open(CServer* server) { assert(m_server == NULL); assert(server != NULL); // set the server m_server = server; // open the display openDisplay(); // initialize marks m_mark = 0; m_markReceived = 0; nextMark(); // save cursor pos POINT pos; GetCursorPos(&pos); m_x = pos.x; m_y = pos.y; // send screen info SInt32 x, y, w, h; getScreenShape(x, y, w, h); m_server->setInfo(x, y, w, h, getJumpZoneSize(), m_x, m_y); // compute center pixel of primary screen m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1; m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1; // get keyboard state updateKeys(); // enter the screen enterNoWarp(); } void CMSWindowsPrimaryScreen::close() { assert(m_server != NULL); // close the display closeDisplay(); // done with server m_server = NULL; } void CMSWindowsPrimaryScreen::enter(SInt32 x, SInt32 y) { log((CLOG_INFO "entering primary at %d,%d", x, y)); assert(m_active == true); // enter the screen enterNoWarp(); // warp to requested location warpCursor(x, y); } bool CMSWindowsPrimaryScreen::leave() { log((CLOG_INFO "leaving primary")); assert(m_active == false); // all messages prior to now are invalid nextMark(); // save active window, show ours, and grab mouse/keyboard if (!onLeave()) { return false; } // relay all mouse and keyboard events m_setRelay(); // get state of keys as we leave updateKeys(); // warp mouse to center of screen warpCursorToCenter(); // ignore this many mouse motion events (not including the already // queued events). on (at least) the win2k login desktop, one // motion event is reported using a position from before the above // warpCursor(). i don't know why it does that and other desktops // don't have the same problem. anyway, simply ignoring that event // works around it. // FIXME -- is this true now that we're using mouse_event? m_mouseMoveIgnore = 1; // disable ctrl+alt+del, alt+tab, etc if (m_is95Family) { DWORD dummy = 0; SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, &dummy, 0); } // discard messages until after the warp nextMark(); // local client now active m_active = true; // if we think we own the clipboard but we don't then somebody // grabbed the clipboard on this screen without us knowing. // tell the server that this screen grabbed the clipboard. // // this works around bugs in the clipboard viewer chain. // sometimes NT will simply never send WM_DRAWCLIPBOARD // messages for no apparent reason and rebooting fixes the // problem. since we don't want a broken clipboard until the // next reboot we do this double check. clipboard ownership // won't be reflected on other screens until we leave but at // least the clipboard itself will work. HWND clipboardOwner = GetClipboardOwner(); if (m_clipboardOwner != clipboardOwner) { try { m_clipboardOwner = clipboardOwner; if (m_clipboardOwner != m_window) { m_server->grabClipboard(kClipboardClipboard); m_server->grabClipboard(kClipboardSelection); } } catch (XBadClient&) { // ignore } } return true; } void CMSWindowsPrimaryScreen::onConfigure() { if ((m_is95Family || m_desk != NULL) && !m_active) { SInt32 x, y, w, h; getScreenShape(x, y, w, h); m_setZone(m_server->getActivePrimarySides(), x, y, w, h, getJumpZoneSize()); } } void CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y) { // set the cursor position without generating an event SetCursorPos(x, y); // save position as last position m_x = x; m_y = y; } void CMSWindowsPrimaryScreen::warpCursorToCenter() { // warp to center. the extra info tells the hook DLL to send // SYNERGY_MSG_POST_WARP instead of SYNERGY_MSG_MOUSE_MOVE. SInt32 x, y, w, h; getScreenShape(x, y, w, h); mouse_event(MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, (DWORD)((65535.99 * (m_xCenter - x)) / (w - 1)), (DWORD)((65535.99 * (m_yCenter - y)) / (h - 1)), 0, 0x12345678); } void CMSWindowsPrimaryScreen::setClipboard(ClipboardID /*id*/, const IClipboard* src) { assert(m_window != NULL); CMSWindowsClipboard dst(m_window); CClipboard::copy(&dst, src); } void CMSWindowsPrimaryScreen::grabClipboard(ClipboardID /*id*/) { assert(m_window != NULL); CMSWindowsClipboard clipboard(m_window); if (clipboard.open(0)) { clipboard.close(); } } void CMSWindowsPrimaryScreen::getShape( SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { getScreenShape(x, y, w, h); } SInt32 CMSWindowsPrimaryScreen::getJumpZoneSize() const { return 1; } void CMSWindowsPrimaryScreen::getClipboard(ClipboardID /*id*/, IClipboard* dst) const { assert(m_window != NULL); CMSWindowsClipboard src(m_window); CClipboard::copy(dst, &src); } KeyModifierMask CMSWindowsPrimaryScreen::getToggleMask() const { KeyModifierMask mask = 0; if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) mask |= KeyModifierCapsLock; if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) mask |= KeyModifierNumLock; if ((GetKeyState(VK_SCROLL) & 0x01) != 0) mask |= KeyModifierScrollLock; return mask; } bool CMSWindowsPrimaryScreen::isLockedToScreen() const { // check buttons if (GetAsyncKeyState(VK_LBUTTON) < 0 || GetAsyncKeyState(VK_MBUTTON) < 0 || GetAsyncKeyState(VK_RBUTTON) < 0) { return true; } // check keys BYTE keys[256]; if (GetKeyboardState(keys)) { for (unsigned int i = 0; i < sizeof(keys); ++i) { if ((keys[i] & 0x80) != 0) { return true; } } } // not locked return false; } void CMSWindowsPrimaryScreen::onOpenDisplay() { assert(m_window == NULL); assert(m_server != NULL); // save thread id. we'll need to pass this to the hook library. m_threadID = GetCurrentThreadId(); // get the input desktop and switch to it if (m_is95Family) { if (!openDesktop()) { throw XScreenOpenFailure(); } } else { if (!switchDesktop(openInputDesktop())) { throw XScreenOpenFailure(); } } } void CMSWindowsPrimaryScreen::onCloseDisplay() { // disconnect from desktop if (m_is95Family) { closeDesktop(); } else { switchDesktop(NULL); } // clear thread id m_threadID = 0; assert(m_window == NULL); assert(m_desk == NULL); } bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg) { // handle event switch (msg->message) { case SYNERGY_MSG_MARK: m_markReceived = msg->wParam; return true; case SYNERGY_MSG_KEY: // ignore message if posted prior to last mark change if (m_markReceived == m_mark) { KeyModifierMask mask; const KeyID key = mapKey(msg->wParam, msg->lParam, &mask); if (key != kKeyNone) { if ((msg->lParam & 0x80000000) == 0) { // key press const SInt32 repeat = (SInt32)(msg->lParam & 0xffff); if (repeat >= 2) { log((CLOG_DEBUG1 "event: key repeat key=%d mask=0x%04x count=%d", key, mask, repeat)); m_server->onKeyRepeat(key, mask, repeat); } else { log((CLOG_DEBUG1 "event: key press key=%d mask=0x%04x", key, mask)); m_server->onKeyDown(key, mask); } // update key state updateKey(msg->wParam, true); } else { // key release log((CLOG_DEBUG1 "event: key release key=%d mask=0x%04x", key, mask)); m_server->onKeyUp(key, mask); // update key state updateKey(msg->wParam, false); } } else { log((CLOG_DEBUG2 "event: cannot map key wParam=%d lParam=0x%08x", msg->wParam, msg->lParam)); } } return true; case SYNERGY_MSG_MOUSE_BUTTON: // ignore message if posted prior to last mark change if (m_markReceived == m_mark) { const ButtonID button = mapButton(msg->wParam); switch (msg->wParam) { case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: log((CLOG_DEBUG1 "event: button press button=%d", button)); if (button != kButtonNone) { m_server->onMouseDown(button); } break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: log((CLOG_DEBUG1 "event: button release button=%d", button)); if (button != kButtonNone) { m_server->onMouseUp(button); } break; } } return true; case SYNERGY_MSG_MOUSE_WHEEL: // ignore message if posted prior to last mark change if (m_markReceived == m_mark) { log((CLOG_ERR "event: button wheel delta=%d %d", msg->wParam, msg->lParam)); m_server->onMouseWheel(msg->wParam); } return true; case SYNERGY_MSG_MOUSE_MOVE: // ignore message if posted prior to last mark change if (m_markReceived == m_mark) { SInt32 x = static_cast(msg->wParam); SInt32 y = static_cast(msg->lParam); if (!m_active) { m_server->onMouseMovePrimary(x, y); } else { // compute motion delta. this is relative to the // last known mouse position. x -= m_x; y -= m_y; // save position to compute delta of next motion m_x = static_cast(msg->wParam); m_y = static_cast(msg->lParam); // ignore if the mouse didn't move or we're ignoring // motion. if (m_mouseMoveIgnore == 0) { if (x != 0 || y != 0) { // back to center warpCursorToCenter(); // send motion m_server->onMouseMoveSecondary(x, y); } } else { // ignored one more motion event --m_mouseMoveIgnore; } } } return true; case SYNERGY_MSG_POST_WARP: m_x = static_cast(msg->wParam); m_y = static_cast(msg->lParam); return true; case WM_TIMER: // if current desktop is not the input desktop then switch to it if (!m_is95Family) { HDESK desk = openInputDesktop(); if (desk != NULL) { if (isCurrentDesktop(desk)) { CloseDesktop(desk); } else { switchDesktop(desk); } } } return true; } return false; } LRESULT CMSWindowsPrimaryScreen::onEvent(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_QUERYENDSESSION: if (m_is95Family) { return TRUE; } break; case WM_ENDSESSION: if (m_is95Family) { if (wParam == TRUE && lParam == 0) { stop(); } return 0; } break; case WM_PAINT: ValidateRect(hwnd, NULL); return 0; case WM_DRAWCLIPBOARD: log((CLOG_DEBUG "clipboard was taken")); // first pass it on if (m_nextClipboardWindow != NULL) { SendMessage(m_nextClipboardWindow, msg, wParam, lParam); } // now notify server that somebody changed the clipboard. // skip that if we're the new owner. try { m_clipboardOwner = GetClipboardOwner(); if (m_clipboardOwner != m_window) { m_server->grabClipboard(kClipboardClipboard); m_server->grabClipboard(kClipboardSelection); } } catch (XBadClient&) { // ignore. this can happen if we receive this event // before we've fully started up. } return 0; case WM_CHANGECBCHAIN: if (m_nextClipboardWindow == (HWND)wParam) { m_nextClipboardWindow = (HWND)lParam; } else if (m_nextClipboardWindow != NULL) { SendMessage(m_nextClipboardWindow, msg, wParam, lParam); } return 0; case WM_DISPLAYCHANGE: { // screen resolution may have changed SInt32 xOld, yOld, wOld, hOld; getScreenShape(xOld, yOld, wOld, hOld); updateScreenShape(); SInt32 x, y, w, h; getScreenShape(x, y, w, h); // do nothing if resolution hasn't changed if (x != xOld || y != yOld || w != wOld || h != hOld) { // recompute center pixel of primary screen m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1; m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1; // warp mouse to center if active if (m_active) { warpCursorToCenter(); } // tell hook about resize if not active else { onConfigure(); } // send new screen info POINT pos; GetCursorPos(&pos); m_server->setInfo(x, y, w, h, getJumpZoneSize(), pos.x, pos.y); } return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } void CMSWindowsPrimaryScreen::enterNoWarp() { // not active anymore m_active = false; // reset motion ignore count m_mouseMoveIgnore = 0; // enable ctrl+alt+del, alt+tab, etc if (m_is95Family) { DWORD dummy = 0; SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0); } // install jump zones onConfigure(); // restore active window and hide our window onEnter(); // all messages prior to now are invalid nextMark(); } void CMSWindowsPrimaryScreen::onEnter() { // restore the active window and hide our window. we can only set // the active window for another thread if we first attach our input // to that thread. ReleaseCapture(); if (m_lastActiveWindow != NULL) { DWORD myThread = GetCurrentThreadId(); if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { // FIXME -- shouldn't raise window if X-Mouse is enabled // but i have no idea how to do that or check if enabled. SetActiveWindow(m_lastActiveWindow); AttachThreadInput(myThread, m_lastActiveThread, FALSE); } } ShowWindow(m_window, SW_HIDE); } bool CMSWindowsPrimaryScreen::onLeave() { // remember the active window before we leave. GetActiveWindow() // will only return the active window for the thread's queue (i.e. // our app) but we need the globally active window. get that by // attaching input to the foreground window's thread then calling // GetActiveWindow() and then detaching our input. m_lastActiveWindow = NULL; m_lastForegroundWindow = GetForegroundWindow(); m_lastActiveThread = GetWindowThreadProcessId( m_lastForegroundWindow, NULL); if (m_lastActiveThread != 0) { DWORD myThread = GetCurrentThreadId(); if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) { m_lastActiveWindow = GetActiveWindow(); AttachThreadInput(myThread, m_lastActiveThread, FALSE); } } // show our window ShowWindow(m_window, SW_SHOW); // get keyboard input and capture mouse SetActiveWindow(m_window); SetFocus(m_window); SetCapture(m_window); return true; } void CMSWindowsPrimaryScreen::nextMark() { // next mark ++m_mark; // mark point in message queue where the mark was changed PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0); } bool CMSWindowsPrimaryScreen::openDesktop() { // install hooks m_install(m_threadID); // note -- we use a fullscreen window to grab input. it should // be possible to use a 1x1 window but i've run into problems // with losing keyboard input (focus?) in that case. // unfortunately, hiding the full screen window (when entering // the scren causes all other windows to redraw). SInt32 x, y, w, h; getScreenShape(x, y, w, h); // create the window m_window = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, (LPCTSTR)getClass(), "Synergy", WS_POPUP, x, y, w, h, NULL, NULL, getInstance(), NULL); if (m_window == NULL) { log((CLOG_ERR "failed to create window: %d", GetLastError())); m_uninstall(); return false; } // install our clipboard snooper m_nextClipboardWindow = SetClipboardViewer(m_window); return true; } void CMSWindowsPrimaryScreen::closeDesktop() { // destroy old window if (m_window != NULL) { // restore active window and hide ours if (m_active) { onEnter(); } // first remove clipboard snooper ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; // we no longer own the clipboard if (m_clipboardOwner == m_window) { m_clipboardOwner = NULL; } // now destroy window DestroyWindow(m_window); m_window = NULL; } // unhook m_uninstall(); } bool CMSWindowsPrimaryScreen::switchDesktop(HDESK desk) { // did we own the clipboard? bool ownClipboard = (m_clipboardOwner == m_window); // destroy old window if (m_window != NULL) { // restore active window and hide ours if (m_active) { onEnter(); } // first remove clipboard snooper ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; // we no longer own the clipboard if (ownClipboard) { m_clipboardOwner = NULL; } // now destroy window DestroyWindow(m_window); m_window = NULL; } // unhook if (m_desk != NULL) { m_uninstall(); CloseDesktop(m_desk); m_desk = NULL; m_deskName = ""; } // if no new desktop then we're done if (desk == NULL) { log((CLOG_INFO "disconnecting desktop")); return true; } // set the desktop. can only do this when there are no windows // and hooks on the current desktop owned by this thread. if (SetThreadDesktop(desk) == 0) { log((CLOG_ERR "failed to set desktop: %d", GetLastError())); CloseDesktop(desk); return false; } // install hooks m_install(m_threadID); // note -- we use a fullscreen window to grab input. it should // be possible to use a 1x1 window but i've run into problems // with losing keyboard input (focus?) in that case. // unfortunately, hiding the full screen window (when entering // the scren causes all other windows to redraw). SInt32 x, y, w, h; getScreenShape(x, y, w, h); // create the window m_window = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, (LPCTSTR)getClass(), "Synergy", WS_POPUP, x, y, w, h, NULL, NULL, getInstance(), NULL); if (m_window == NULL) { log((CLOG_ERR "failed to create window: %d", GetLastError())); m_uninstall(); CloseDesktop(desk); return false; } // install our clipboard snooper m_nextClipboardWindow = SetClipboardViewer(m_window); // reassert clipboard ownership if (ownClipboard) { // FIXME } // save new desktop m_desk = desk; m_deskName = getDesktopName(m_desk); log((CLOG_INFO "switched to desktop %s", m_deskName.c_str())); // get active window and show ours if (m_active) { onLeave(); } else { // set jump zones onConfigure(); // all messages prior to now are invalid nextMark(); } return true; } CString CMSWindowsPrimaryScreen::getCurrentDesktopName() const { return m_deskName; } static const KeyID g_virtualKey[] = { /* 0x00 */ kKeyNone, // reserved /* 0x01 */ kKeyNone, // VK_LBUTTON /* 0x02 */ kKeyNone, // VK_RBUTTON /* 0x03 */ 0xff6b, // VK_CANCEL XK_Break /* 0x04 */ kKeyNone, // VK_MBUTTON /* 0x05 */ kKeyNone, // undefined /* 0x06 */ kKeyNone, // undefined /* 0x07 */ kKeyNone, // undefined /* 0x08 */ 0xff08, // VK_BACK XK_Backspace /* 0x09 */ 0xff09, // VK_TAB VK_Tab /* 0x0a */ kKeyNone, // undefined /* 0x0b */ kKeyNone, // undefined /* 0x0c */ 0xff0b, // VK_CLEAR XK_Clear /* 0x0d */ 0xff0d, // VK_RETURN XK_Return /* 0x0e */ kKeyNone, // undefined /* 0x0f */ kKeyNone, // undefined /* 0x10 */ 0xffe1, // VK_SHIFT XK_Shift_L /* 0x11 */ 0xffe3, // VK_CONTROL XK_Control_L /* 0x12 */ 0xffe9, // VK_MENU XK_Alt_L /* 0x13 */ 0xff13, // VK_PAUSE XK_Pause /* 0x14 */ 0xffe5, // VK_CAPITAL XK_Caps_Lock /* 0x15 */ kKeyNone, // VK_KANA /* 0x16 */ kKeyNone, // VK_HANGUL /* 0x17 */ kKeyNone, // VK_JUNJA /* 0x18 */ kKeyNone, // VK_FINAL /* 0x19 */ kKeyNone, // VK_KANJI /* 0x1a */ kKeyNone, // undefined /* 0x1b */ 0xff1b, // VK_ESCAPE XK_Escape /* 0x1c */ kKeyNone, // VK_CONVERT /* 0x1d */ kKeyNone, // VK_NONCONVERT /* 0x1e */ kKeyNone, // VK_ACCEPT /* 0x1f */ kKeyNone, // VK_MODECHANGE /* 0x20 */ 0x0020, // VK_SPACE XK_space /* 0x21 */ 0xff55, // VK_PRIOR XK_Prior /* 0x22 */ 0xff56, // VK_NEXT XK_Next /* 0x23 */ 0xff57, // VK_END XK_End /* 0x24 */ 0xff50, // VK_HOME XK_Home /* 0x25 */ 0xff51, // VK_LEFT XK_Left /* 0x26 */ 0xff52, // VK_UP XK_Up /* 0x27 */ 0xff53, // VK_RIGHT XK_Right /* 0x28 */ 0xff54, // VK_DOWN XK_Down /* 0x29 */ 0xff60, // VK_SELECT XK_Select /* 0x2a */ kKeyNone, // VK_PRINT /* 0x2b */ 0xff62, // VK_EXECUTE XK_Execute /* 0x2c */ 0xff61, // VK_SNAPSHOT XK_Print /* 0x2d */ 0xff63, // VK_INSERT XK_Insert /* 0x2e */ 0xffff, // VK_DELETE XK_Delete /* 0x2f */ 0xff6a, // VK_HELP XK_Help /* 0x30 */ kKeyNone, // VK_0 XK_0 /* 0x31 */ kKeyNone, // VK_1 XK_1 /* 0x32 */ kKeyNone, // VK_2 XK_2 /* 0x33 */ kKeyNone, // VK_3 XK_3 /* 0x34 */ kKeyNone, // VK_4 XK_4 /* 0x35 */ kKeyNone, // VK_5 XK_5 /* 0x36 */ kKeyNone, // VK_6 XK_6 /* 0x37 */ kKeyNone, // VK_7 XK_7 /* 0x38 */ kKeyNone, // VK_8 XK_8 /* 0x39 */ kKeyNone, // VK_9 XK_9 /* 0x3a */ kKeyNone, // undefined /* 0x3b */ kKeyNone, // undefined /* 0x3c */ kKeyNone, // undefined /* 0x3d */ kKeyNone, // undefined /* 0x3e */ kKeyNone, // undefined /* 0x3f */ kKeyNone, // undefined /* 0x40 */ kKeyNone, // undefined /* 0x41 */ kKeyNone, // VK_A XK_A /* 0x42 */ kKeyNone, // VK_B XK_B /* 0x43 */ kKeyNone, // VK_C XK_C /* 0x44 */ kKeyNone, // VK_D XK_D /* 0x45 */ kKeyNone, // VK_E XK_E /* 0x46 */ kKeyNone, // VK_F XK_F /* 0x47 */ kKeyNone, // VK_G XK_G /* 0x48 */ kKeyNone, // VK_H XK_H /* 0x49 */ kKeyNone, // VK_I XK_I /* 0x4a */ kKeyNone, // VK_J XK_J /* 0x4b */ kKeyNone, // VK_K XK_K /* 0x4c */ kKeyNone, // VK_L XK_L /* 0x4d */ kKeyNone, // VK_M XK_M /* 0x4e */ kKeyNone, // VK_N XK_N /* 0x4f */ kKeyNone, // VK_O XK_O /* 0x50 */ kKeyNone, // VK_P XK_P /* 0x51 */ kKeyNone, // VK_Q XK_Q /* 0x52 */ kKeyNone, // VK_R XK_R /* 0x53 */ kKeyNone, // VK_S XK_S /* 0x54 */ kKeyNone, // VK_T XK_T /* 0x55 */ kKeyNone, // VK_U XK_U /* 0x56 */ kKeyNone, // VK_V XK_V /* 0x57 */ kKeyNone, // VK_W XK_W /* 0x58 */ kKeyNone, // VK_X XK_X /* 0x59 */ kKeyNone, // VK_Y XK_Y /* 0x5a */ kKeyNone, // VK_Z XK_Z /* 0x5b */ 0xffe7, // VK_LWIN XK_Meta_L /* 0x5c */ 0xffe8, // VK_RWIN XK_Meta_R /* 0x5d */ 0xff67, // VK_APPS XK_Menu /* 0x5e */ kKeyNone, // undefined /* 0x5f */ kKeyNone, // undefined /* 0x60 */ 0xffb0, // VK_NUMPAD0 XK_KP_0 /* 0x61 */ 0xffb1, // VK_NUMPAD1 XK_KP_1 /* 0x62 */ 0xffb2, // VK_NUMPAD2 XK_KP_2 /* 0x63 */ 0xffb3, // VK_NUMPAD3 XK_KP_3 /* 0x64 */ 0xffb4, // VK_NUMPAD4 XK_KP_4 /* 0x65 */ 0xffb5, // VK_NUMPAD5 XK_KP_5 /* 0x66 */ 0xffb6, // VK_NUMPAD6 XK_KP_6 /* 0x67 */ 0xffb7, // VK_NUMPAD7 XK_KP_7 /* 0x68 */ 0xffb8, // VK_NUMPAD8 XK_KP_8 /* 0x69 */ 0xffb9, // VK_NUMPAD9 XK_KP_9 /* 0x6a */ 0xffaa, // VK_MULTIPLY XK_KP_Multiply /* 0x6b */ 0xffab, // VK_ADD XK_KP_Add /* 0x6c */ 0xffac, // VK_SEPARATOR XK_KP_Separator /* 0x6d */ 0xffad, // VK_SUBTRACT XK_KP_Subtract /* 0x6e */ 0xffae, // VK_DECIMAL XK_KP_Decimal /* 0x6f */ 0xffaf, // VK_DIVIDE XK_KP_Divide /* 0x70 */ 0xffbe, // VK_F1 XK_F1 /* 0x71 */ 0xffbf, // VK_F2 XK_F2 /* 0x72 */ 0xffc0, // VK_F3 XK_F3 /* 0x73 */ 0xffc1, // VK_F4 XK_F4 /* 0x74 */ 0xffc2, // VK_F5 XK_F5 /* 0x75 */ 0xffc3, // VK_F6 XK_F6 /* 0x76 */ 0xffc4, // VK_F7 XK_F7 /* 0x77 */ 0xffc5, // VK_F8 XK_F8 /* 0x78 */ 0xffc6, // VK_F9 XK_F9 /* 0x79 */ 0xffc7, // VK_F10 XK_F10 /* 0x7a */ 0xffc8, // VK_F11 XK_F11 /* 0x7b */ 0xffc9, // VK_F12 XK_F12 /* 0x7c */ 0xffca, // VK_F13 XK_F13 /* 0x7d */ 0xffcb, // VK_F14 XK_F14 /* 0x7e */ 0xffcc, // VK_F15 XK_F15 /* 0x7f */ 0xffcd, // VK_F16 XK_F16 /* 0x80 */ 0xffce, // VK_F17 XK_F17 /* 0x81 */ 0xffcf, // VK_F18 XK_F18 /* 0x82 */ 0xffd0, // VK_F19 XK_F19 /* 0x83 */ 0xffd1, // VK_F20 XK_F20 /* 0x84 */ 0xffd2, // VK_F21 XK_F21 /* 0x85 */ 0xffd3, // VK_F22 XK_F22 /* 0x86 */ 0xffd4, // VK_F23 XK_F23 /* 0x87 */ 0xffd5, // VK_F24 XK_F24 /* 0x88 */ kKeyNone, // unassigned /* 0x89 */ kKeyNone, // unassigned /* 0x8a */ kKeyNone, // unassigned /* 0x8b */ kKeyNone, // unassigned /* 0x8c */ kKeyNone, // unassigned /* 0x8d */ kKeyNone, // unassigned /* 0x8e */ kKeyNone, // unassigned /* 0x8f */ kKeyNone, // unassigned /* 0x90 */ 0xff7f, // VK_NUMLOCK XK_Num_Lock /* 0x91 */ 0xff14, // VK_SCROLL XK_Scroll_Lock /* 0x92 */ kKeyNone, // unassigned /* 0x93 */ kKeyNone, // unassigned /* 0x94 */ kKeyNone, // unassigned /* 0x95 */ kKeyNone, // unassigned /* 0x96 */ kKeyNone, // unassigned /* 0x97 */ kKeyNone, // unassigned /* 0x98 */ kKeyNone, // unassigned /* 0x99 */ kKeyNone, // unassigned /* 0x9a */ kKeyNone, // unassigned /* 0x9b */ kKeyNone, // unassigned /* 0x9c */ kKeyNone, // unassigned /* 0x9d */ kKeyNone, // unassigned /* 0x9e */ kKeyNone, // unassigned /* 0x9f */ kKeyNone, // unassigned /* 0xa0 */ 0xffe1, // VK_LSHIFT XK_Shift_L /* 0xa1 */ 0xffe2, // VK_RSHIFT XK_Shift_R /* 0xa2 */ 0xffe3, // VK_LCONTROL XK_Control_L /* 0xa3 */ 0xffe4, // VK_RCONTROL XK_Control_R /* 0xa4 */ 0xffe9, // VK_LMENU XK_Alt_L /* 0xa5 */ 0xffea, // VK_RMENU XK_Alt_R /* 0xa6 */ kKeyNone, // unassigned /* 0xa7 */ kKeyNone, // unassigned /* 0xa8 */ kKeyNone, // unassigned /* 0xa9 */ kKeyNone, // unassigned /* 0xaa */ kKeyNone, // unassigned /* 0xab */ kKeyNone, // unassigned /* 0xac */ kKeyNone, // unassigned /* 0xad */ kKeyNone, // unassigned /* 0xae */ kKeyNone, // unassigned /* 0xaf */ kKeyNone, // unassigned /* 0xb0 */ kKeyNone, // unassigned /* 0xb1 */ kKeyNone, // unassigned /* 0xb2 */ kKeyNone, // unassigned /* 0xb3 */ kKeyNone, // unassigned /* 0xb4 */ kKeyNone, // unassigned /* 0xb5 */ kKeyNone, // unassigned /* 0xb6 */ kKeyNone, // unassigned /* 0xb7 */ kKeyNone, // unassigned /* 0xb8 */ kKeyNone, // unassigned /* 0xb9 */ kKeyNone, // unassigned /* 0xba */ kKeyNone, // OEM specific /* 0xbb */ kKeyNone, // OEM specific /* 0xbc */ kKeyNone, // OEM specific /* 0xbd */ kKeyNone, // OEM specific /* 0xbe */ kKeyNone, // OEM specific /* 0xbf */ kKeyNone, // OEM specific /* 0xc0 */ kKeyNone, // OEM specific /* 0xc1 */ kKeyNone, // unassigned /* 0xc2 */ kKeyNone, // unassigned /* 0xc3 */ kKeyNone, // unassigned /* 0xc4 */ kKeyNone, // unassigned /* 0xc5 */ kKeyNone, // unassigned /* 0xc6 */ kKeyNone, // unassigned /* 0xc7 */ kKeyNone, // unassigned /* 0xc8 */ kKeyNone, // unassigned /* 0xc9 */ kKeyNone, // unassigned /* 0xca */ kKeyNone, // unassigned /* 0xcb */ kKeyNone, // unassigned /* 0xcc */ kKeyNone, // unassigned /* 0xcd */ kKeyNone, // unassigned /* 0xce */ kKeyNone, // unassigned /* 0xcf */ kKeyNone, // unassigned /* 0xd0 */ kKeyNone, // unassigned /* 0xd1 */ kKeyNone, // unassigned /* 0xd2 */ kKeyNone, // unassigned /* 0xd3 */ kKeyNone, // unassigned /* 0xd4 */ kKeyNone, // unassigned /* 0xd5 */ kKeyNone, // unassigned /* 0xd6 */ kKeyNone, // unassigned /* 0xd7 */ kKeyNone, // unassigned /* 0xd8 */ kKeyNone, // unassigned /* 0xd9 */ kKeyNone, // unassigned /* 0xda */ kKeyNone, // unassigned /* 0xdb */ kKeyNone, // OEM specific /* 0xdc */ kKeyNone, // OEM specific /* 0xdd */ kKeyNone, // OEM specific /* 0xde */ kKeyNone, // OEM specific /* 0xdf */ kKeyNone, // OEM specific /* 0xe0 */ kKeyNone, // OEM specific /* 0xe1 */ kKeyNone, // OEM specific /* 0xe2 */ kKeyNone, // OEM specific /* 0xe3 */ kKeyNone, // OEM specific /* 0xe4 */ kKeyNone, // OEM specific /* 0xe5 */ kKeyNone, // unassigned /* 0xe6 */ kKeyNone, // OEM specific /* 0xe7 */ kKeyNone, // unassigned /* 0xe8 */ kKeyNone, // unassigned /* 0xe9 */ kKeyNone, // OEM specific /* 0xea */ kKeyNone, // OEM specific /* 0xeb */ kKeyNone, // OEM specific /* 0xec */ kKeyNone, // OEM specific /* 0xed */ kKeyNone, // OEM specific /* 0xee */ kKeyNone, // OEM specific /* 0xef */ kKeyNone, // OEM specific /* 0xf0 */ kKeyNone, // OEM specific /* 0xf1 */ kKeyNone, // OEM specific /* 0xf2 */ kKeyNone, // OEM specific /* 0xf3 */ kKeyNone, // OEM specific /* 0xf4 */ kKeyNone, // OEM specific /* 0xf5 */ kKeyNone, // OEM specific /* 0xf6 */ kKeyNone, // VK_ATTN /* 0xf7 */ kKeyNone, // VK_CRSEL /* 0xf8 */ kKeyNone, // VK_EXSEL /* 0xf9 */ kKeyNone, // VK_EREOF /* 0xfa */ kKeyNone, // VK_PLAY /* 0xfb */ kKeyNone, // VK_ZOOM /* 0xfc */ kKeyNone, // reserved /* 0xfd */ kKeyNone, // VK_PA1 /* 0xfe */ kKeyNone, // VK_OEM_CLEAR /* 0xff */ kKeyNone // reserved }; KeyID CMSWindowsPrimaryScreen::mapKey( WPARAM vkCode, LPARAM info, KeyModifierMask* maskOut) { // note: known microsoft bugs // Q72583 -- MapVirtualKey() maps keypad keys incorrectly // 95,98: num pad vk code -> invalid scan code // 95,98,NT4: num pad scan code -> bad vk code except // SEPARATOR, MULTIPLY, SUBTRACT, ADD static const KeyID XK_Multi_key = 0xff20; assert(maskOut != NULL); // map modifier key KeyModifierMask mask = 0; if (((m_keys[VK_LSHIFT] | m_keys[VK_RSHIFT] | m_keys[VK_SHIFT]) & 0x80) != 0) { mask |= KeyModifierShift; } if (((m_keys[VK_LCONTROL] | m_keys[VK_RCONTROL] | m_keys[VK_CONTROL]) & 0x80) != 0) { mask |= KeyModifierControl; } if (((m_keys[VK_LMENU] | m_keys[VK_RMENU] | m_keys[VK_MENU]) & 0x80) != 0) { mask |= KeyModifierAlt; } if (((m_keys[VK_LWIN] | m_keys[VK_RWIN]) & 0x80) != 0) { mask |= KeyModifierMeta; } if ((m_keys[VK_CAPITAL] & 0x01) != 0) { mask |= KeyModifierCapsLock; } if ((m_keys[VK_NUMLOCK] & 0x01) != 0) { mask |= KeyModifierNumLock; } if ((m_keys[VK_SCROLL] & 0x01) != 0) { mask |= KeyModifierScrollLock; } *maskOut = mask; log((CLOG_DEBUG2 "key in vk=%d info=0x%08x mask=0x%04x", vkCode, info, mask)); // get the scan code UINT scanCode = static_cast((info & 0xff0000) >> 16); // convert virtual key to one that distinguishes between left and // right for keys that have left/right versions. known scan codes // that don't have left/right versions are passed through unchanged. // unknown scan codes return 0. UINT vkCode2 = MapVirtualKey(scanCode, 3); // work around bug Q72583 (bad num pad conversion in MapVirtualKey()) if (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE) { vkCode2 = vkCode; } // MapVirtualKey() appears to map VK_LWIN, VK_RWIN, VK_APPS to // some other meaningless virtual key. work around that bug. else if (vkCode >= VK_LWIN && vkCode <= VK_APPS) { vkCode2 = vkCode; } // if MapVirtualKey failed then use original virtual key else if (vkCode2 == 0) { vkCode2 = vkCode; } // sadly, win32 will not distinguish between the left and right // control and alt keys using the above function. however, we // can check for those: if bit 24 of info is set then the key // is a "extended" key, such as the right control and right alt // keys. if ((info & 0x1000000) != 0) { switch (vkCode2) { case VK_CONTROL: case VK_LCONTROL: vkCode2 = VK_RCONTROL; break; case VK_MENU: case VK_LMENU: vkCode2 = VK_RMENU; break; } } // use left/right distinguishing virtual key vkCode = vkCode2; log((CLOG_DEBUG1 "key vk=%d scan=%d", vkCode, scanCode)); // handle some keys via table lookup KeyID id = g_virtualKey[vkCode]; if (id != kKeyNone) { return id; } // check for dead keys if (MapVirtualKey(vkCode, 2) >= 0x8000) { return XK_Multi_key; } // ToAscii() maps ctrl+letter to the corresponding control code // and ctrl+backspace to delete. if we've got a control code or // delete then do ToAscii() again but without the control state. // ToAscii() interprets the control modifier state which we don't // want. so save the control state then clear it. BYTE lControl = m_keys[VK_LCONTROL]; BYTE rControl = m_keys[VK_RCONTROL]; BYTE control = m_keys[VK_CONTROL]; m_keys[VK_LCONTROL] = 0; m_keys[VK_RCONTROL] = 0; m_keys[VK_CONTROL] = 0; // convert to ascii WORD ascii; int result = ToAscii(vkCode, scanCode, m_keys, &ascii, 0); // restore control state m_keys[VK_LCONTROL] = lControl; m_keys[VK_RCONTROL] = rControl; m_keys[VK_CONTROL] = control; // if result is less than zero then it was a dead key. that key // is remembered by the keyboard which we don't want. remove it // by calling ToAscii() again with arbitrary arguments. if (result < 0) { ToAscii(vkCode, scanCode, m_keys, &ascii, 0); return XK_Multi_key; } // if result is 1 then the key was succesfully converted else if (result == 1) { return static_cast(ascii & 0x00ff); } // if result is 2 then a previous dead key could not be composed. // put the old dead key back. else if (result == 2) { // get the scan code of the dead key and the shift state // required to generate it. vkCode = VkKeyScan(static_cast(ascii & 0x00ff)); // set shift state required to generate key BYTE keys[256]; memset(keys, 0, sizeof(keys)); // FIXME -- surely these masks should be different in each if expression if (vkCode & 0x0100) { keys[VK_SHIFT] = 0x80; } if (vkCode & 0x0100) { keys[VK_CONTROL] = 0x80; } if (vkCode & 0x0100) { keys[VK_MENU] = 0x80; } // strip shift state off of virtual key code vkCode &= 0x00ff; // get the scan code for the key scanCode = MapVirtualKey(vkCode, 0); // put it back ToAscii(vkCode, scanCode, keys, &ascii, 0); return XK_Multi_key; } // cannot convert key return kKeyNone; } ButtonID CMSWindowsPrimaryScreen::mapButton(WPARAM button) const { switch (button) { case WM_LBUTTONDOWN: case WM_LBUTTONUP: return kButtonLeft; case WM_MBUTTONDOWN: case WM_MBUTTONUP: return kButtonMiddle; case WM_RBUTTONDOWN: case WM_RBUTTONUP: return kButtonRight; default: return kButtonNone; } } void CMSWindowsPrimaryScreen::updateKeys() { // not using GetKeyboardState() because that doesn't seem to give // up-to-date results. i don't know why that is or why GetKeyState() // should give different results. // clear key state memset(m_keys, 0, sizeof(m_keys)); // we only care about the modifier key states m_keys[VK_LSHIFT] = static_cast(GetKeyState(VK_LSHIFT)); m_keys[VK_RSHIFT] = static_cast(GetKeyState(VK_RSHIFT)); m_keys[VK_SHIFT] = static_cast(GetKeyState(VK_SHIFT)); m_keys[VK_LCONTROL] = static_cast(GetKeyState(VK_LCONTROL)); m_keys[VK_RCONTROL] = static_cast(GetKeyState(VK_RCONTROL)); m_keys[VK_CONTROL] = static_cast(GetKeyState(VK_CONTROL)); m_keys[VK_LMENU] = static_cast(GetKeyState(VK_LMENU)); m_keys[VK_RMENU] = static_cast(GetKeyState(VK_RMENU)); m_keys[VK_MENU] = static_cast(GetKeyState(VK_MENU)); m_keys[VK_LWIN] = static_cast(GetKeyState(VK_LWIN)); m_keys[VK_RWIN] = static_cast(GetKeyState(VK_RWIN)); m_keys[VK_APPS] = static_cast(GetKeyState(VK_APPS)); m_keys[VK_CAPITAL] = static_cast(GetKeyState(VK_CAPITAL)); m_keys[VK_NUMLOCK] = static_cast(GetKeyState(VK_NUMLOCK)); m_keys[VK_SCROLL] = static_cast(GetKeyState(VK_SCROLL)); } void CMSWindowsPrimaryScreen::updateKey(UINT vkCode, bool press) { if (press) { switch (vkCode) { case VK_LSHIFT: case VK_RSHIFT: case VK_SHIFT: m_keys[vkCode] |= 0x80; m_keys[VK_SHIFT] |= 0x80; break; case VK_LCONTROL: case VK_RCONTROL: case VK_CONTROL: m_keys[vkCode] |= 0x80; m_keys[VK_CONTROL] |= 0x80; break; case VK_LMENU: case VK_RMENU: case VK_MENU: m_keys[vkCode] |= 0x80; m_keys[VK_MENU] |= 0x80; break; case VK_LWIN: case VK_RWIN: case VK_APPS: m_keys[vkCode] |= 0x80; break; case VK_CAPITAL: case VK_NUMLOCK: case VK_SCROLL: // toggle keys m_keys[vkCode] |= 0x80; break; } } else { switch (vkCode) { case VK_LSHIFT: case VK_RSHIFT: case VK_SHIFT: m_keys[vkCode] &= ~0x80; if (((m_keys[VK_LSHIFT] | m_keys[VK_RSHIFT]) & 0x80) == 0) { m_keys[VK_SHIFT] &= ~0x80; } break; case VK_LCONTROL: case VK_RCONTROL: case VK_CONTROL: m_keys[vkCode] &= ~0x80; if (((m_keys[VK_LCONTROL] | m_keys[VK_RCONTROL]) & 0x80) == 0) { m_keys[VK_CONTROL] &= ~0x80; } break; case VK_LMENU: case VK_RMENU: case VK_MENU: m_keys[vkCode] &= ~0x80; if (((m_keys[VK_LMENU] | m_keys[VK_RMENU]) & 0x80) == 0) { m_keys[VK_MENU] &= ~0x80; } break; case VK_LWIN: case VK_RWIN: case VK_APPS: m_keys[vkCode] &= ~0x80; break; case VK_CAPITAL: case VK_NUMLOCK: case VK_SCROLL: // toggle keys m_keys[vkCode] &= ~0x80; m_keys[vkCode] ^= 0x01; break; } } }