/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * Copyright (C) 2006 Knut St. Osmundsen * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "CPMScreen.h" #include "CPMClipboard.h" #include "CPMEventQueueBuffer.h" #include "CPMKeyState.h" #include "CPMScreenSaver.h" #include "CClipboard.h" #include "CKeyMap.h" #include "XScreen.h" #include "CLock.h" #include "CThread.h" #include "CFunctionJob.h" #include "CLog.h" #include "CString.h" #include "CStringUtil.h" #include "IEventQueue.h" #include "TMethodEventJob.h" #include "TMethodJob.h" #include "CArch.h" #include "CPMUtil.h" #include #include // // CPMScreen // CPMScreen* CPMScreen::s_screen = NULL; CPMScreen::CPMScreen(bool isPrimary) : m_isPrimary(isPrimary), m_isOnScreen(m_isPrimary), m_x(0), m_y(0), m_cx(0), m_cy(0), m_xCenter(0), m_yCenter(0), m_multimon(false), m_xCursor(0), m_yCursor(0), m_sequenceNumber(0), m_mark(0), m_markReceived(0), m_screensaver(NULL), m_screensaverNotify(false), m_screensaverActive(false), m_window(NULLHANDLE), m_nextClipboardWindow(NULLHANDLE), m_ownClipboard(false), m_hmodHook(NULLHANDLE), m_init(NULL), m_cleanup(NULL), m_setSides(NULL), m_setZone(NULL), m_setMode(NULL), m_keyState(NULL), m_hasMouse(WinQuerySysValue(HWND_DESKTOP, SV_MOUSEPRESENT) != FALSE), m_showingMouse(false) { assert(s_screen == NULL); s_screen = this; // create the event queue buffer first so we know there is a message queue. CPMEventQueueBuffer *eventQueue = new CPMEventQueueBuffer(); // query curren thread bits. m_threadID = _gettid(); m_hab = WinQueryAnchorBlock(HWND_DESKTOP); //if (m_hab == NULLHANDLE) { // m_hab = WinInitialize(0); //} m_hmq = WinQueueFromID(m_hab, getpid(), _gettid()); //if (m_hab != NULLHANDLE && m_hmq == NULLHANDLE) { // m_hmq = WinCreateMsgQueue(m_hab, 0); //} if (m_hab == NULLHANDLE || m_hmq == NULLHANDLE) { LOG((CLOG_CRIT "couldn't get the hab(%ld)/hmq(%ld) of the current thread! %d", m_hab, m_hmq)); delete eventQueue; s_screen = NULL; throw XScreenOpenFailure(); } try { if (m_isPrimary) m_hmodHook = openHookLibrary("synrgyhk"); m_screensaver = new CPMScreenSaver(); m_keyState = new CPMKeyState(getEventTarget()); updateScreenShape(); m_window = createWindow("Synergy"); forceShowCursor(); LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_cx, m_cy, m_multimon ? "(multi-monitor)" : "")); LOG((CLOG_DEBUG "window is 0x%08x", m_window)); } catch (...) { delete m_keyState; delete m_screensaver; destroyWindow(m_window); closeHookLibrary(m_hmodHook); delete eventQueue; s_screen = NULL; throw; } // install event handlers EVENTQUEUE->adoptHandler(CEvent::kSystem, IEventQueue::getSystemTarget(), new TMethodEventJob(this, &CPMScreen::handleSystemEvent)); // install the platform event queue EVENTQUEUE->adoptBuffer(new CPMEventQueueBuffer); } CPMScreen::~CPMScreen() { assert(s_screen != NULL); disable(); EVENTQUEUE->adoptBuffer(NULL); EVENTQUEUE->removeHandler(CEvent::kSystem, IEventQueue::getSystemTarget()); delete m_keyState; delete m_screensaver; destroyWindow(m_window); closeHookLibrary(m_hmodHook); s_screen = NULL; } void CPMScreen::enable() { assert(m_isOnScreen == m_isPrimary); // install our clipboard snooper HAB hab = CPMUtil::getHAB(); m_nextClipboardWindow = WinQueryClipbrdViewer(hab); WinSetClipbrdViewer(hab, m_window); if (m_isPrimary) { // set jump zones m_setZone(m_x, m_y, m_cx, m_cy, getJumpZoneSize()); // watch jump zones m_setMode(kHOOK_WATCH_JUMP_ZONE); } } void CPMScreen::disable() { if (m_isPrimary) { // disable hooks m_setMode(kHOOK_DISABLE); } // tell key state m_keyState->disable(); // stop snooping the clipboard WinSetClipbrdViewer(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; m_isOnScreen = m_isPrimary; forceShowCursor(); } void CPMScreen::enter() { // show the pointer (?), hide the synergy window and restore focus. WinShowPointer(HWND_DESKTOP, TRUE); WinSetWindowPos(m_window, HWND_BOTTOM, 0, 0, 0, 0, SWP_HIDE); //?? WinSetFocus( //?? WinEnableWindow if (m_isPrimary) { // watch jump zones m_setMode(kHOOK_WATCH_JUMP_ZONE); // all messages prior to now are invalid nextMark(); } // now on screen m_isOnScreen = true; forceShowCursor(); } bool CPMScreen::leave() { /// @todo save focus window. if (m_isPrimary) { // warp to center warpCursor(m_xCenter, m_yCenter); // all messages prior to now are invalid nextMark(); // remember the modifier state. this is the modifier state // reflected in the internal keyboard state. m_keyState->saveModifiers(); // capture events m_setMode(kHOOK_RELAY_EVENTS); } // now off screen m_isOnScreen = false; forceShowCursor(); return true; } bool CPMScreen::setClipboard(ClipboardID, const IClipboard* src) { CPMClipboard dst(m_window); if (src != NULL) { // save clipboard data return CClipboard::copy(&dst, src); } // assert clipboard ownership if (!dst.open(0)) { return false; } dst.empty(); dst.close(); return true; } void CPMScreen::checkClipboards() { // be careful, even if we don't have the NT bugs. if (m_ownClipboard && !CPMClipboard::isOwnedBySynergy()) { LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); m_ownClipboard = false; sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); } } void CPMScreen::openScreensaver(bool notify) { m_screensaverNotify = notify; if (!m_screensaverNotify) m_screensaver->disable(); } void CPMScreen::closeScreensaver() { if (!m_screensaverNotify) m_screensaver->enable(); } void CPMScreen::screensaver(bool activate) { if (activate) m_screensaver->activate(); else m_screensaver->deactivate(); } void CPMScreen::resetOptions() { //example m_xtestIsXineramaUnaware = true; } void CPMScreen::setOptions(const COptionsList& options) { for (UInt32 i = 0, n = options.size(); i < n; i += 2) { //example if (options[i] == kOptionXTestXineramaUnaware) { //example m_xtestIsXineramaUnaware = (options[i + 1] != 0); //example LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); //example } } } void CPMScreen::setSequenceNumber(UInt32 seqNum) { m_sequenceNumber = seqNum; } bool CPMScreen::isPrimary() const { return m_isPrimary; } void* CPMScreen::getEventTarget() const { return const_cast(this); } bool CPMScreen::getClipboard(ClipboardID, IClipboard* dst) const { CPMClipboard src(m_window); return CClipboard::copy(dst, &src); } void CPMScreen::getShape(SInt32& x, SInt32& y, SInt32& cx, SInt32& cy) const { x = m_x; y = m_y; cx= m_cx; cy = m_cy; } void CPMScreen::getCursorPos(SInt32& x, SInt32& y) const { CURSORINFO Info; if (WinQueryCursorInfo(HWND_DESKTOP, &Info)) { x = Info.x; y = m_cy - Info.y; } else { x = 0; y = 0; } } void CPMScreen::reconfigure(UInt32 activeSides) { // do nothing. } void CPMScreen::warpCursor(SInt32 x, SInt32 y) { // warp mouse warpCursorNoFlush(x, y); // remove all input events before and including warp QMSG qmsg; while (WinPeekMsg(m_hab, &qmsg, NULLHANDLE, SYNERGY_PM_MSG_INPUT_FIRST, SYNERGY_PM_MSG_INPUT_LAST, PM_REMOVE)) { // do nothing } // save position as last position m_xCursor = x; m_yCursor = y; } UInt32 CPMScreen::registerHotKey(KeyID key, KeyModifierMask mask) { // only allow certain modifiers if ((mask & ~(KeyModifierShift | KeyModifierControl | KeyModifierAlt | KeyModifierSuper)) != 0) { LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); return 0; } // fail if no keys if (key == kKeyNone && mask == 0) { return 0; } #if 1 LOG((CLOG_WARN "not implemented id=%04x mask=%04x", key, mask)); return 0; #else // convert to win32 UINT modifiers = 0; if ((mask & KeyModifierShift) != 0) { modifiers |= MOD_SHIFT; } if ((mask & KeyModifierControl) != 0) { modifiers |= MOD_CONTROL; } if ((mask & KeyModifierAlt) != 0) { modifiers |= MOD_ALT; } if ((mask & KeyModifierSuper) != 0) { modifiers |= MOD_WIN; } UINT vk = m_keyState->mapKeyToVirtualKey(key); if (key != kKeyNone && vk == 0) { // can't map key LOG((CLOG_WARN "could not map hotkey id=%04x mask=%04x", key, mask)); return 0; } // choose hotkey id UInt32 id; if (!m_oldHotKeyIDs.empty()) { id = m_oldHotKeyIDs.back(); m_oldHotKeyIDs.pop_back(); } else { id = m_hotKeys.size() + 1; } // if this hot key has modifiers only then we'll handle it specially bool err; if (key == kKeyNone) { // check if already registered err = (m_hotKeyToIDMap.count(CHotKeyItem(vk, modifiers)) > 0); } else { // register with OS err = (RegisterHotKey(NULL, id, modifiers, vk) == 0); } if (!err) { m_hotKeys.insert(std::make_pair(id, CHotKeyItem(vk, modifiers))); m_hotKeyToIDMap[CHotKeyItem(vk, modifiers)] = id; } else { m_oldHotKeyIDs.push_back(id); m_hotKeys.erase(id); LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", CKeyMap::formatKey(key, mask).c_str(), key, mask)); return 0; } LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", CKeyMap::formatKey(key, mask).c_str(), key, mask, id)); return id; #endif } void CPMScreen::unregisterHotKey(UInt32 id) { // look up hotkey HotKeyMap::iterator i = m_hotKeys.find(id); if (i == m_hotKeys.end()) { return; } // unregister with OS bool err; #if 1 err = true; #else if (i->second.getVirtualKey() != 0) { err = !UnregisterHotKey(NULL, id); } else { err = false; } #endif if (err) { LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); } else { LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); } // discard hot key from map and record old id for reuse m_hotKeyToIDMap.erase(i->second); m_hotKeys.erase(i); m_oldHotKeyIDs.push_back(id); } void CPMScreen::fakeInputBegin() { assert(m_isPrimary); if (!m_isOnScreen) { m_keyState->useSavedModifiers(true); } //m_desks->fakeInputBegin(); } void CPMScreen::fakeInputEnd() { assert(m_isPrimary); //m_desks->fakeInputEnd(); if (!m_isOnScreen) { m_keyState->useSavedModifiers(false); } } SInt32 CPMScreen::getJumpZoneSize() const { return 1; } bool CPMScreen::isAnyMouseButtonDown() const { static const char* buttonToName[] = { "", "Left Button", "Middle Button", "Right Button", "X Button 1", "X Button 2" }; for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { if (m_buttons[i]) { LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); return true; } } return false; } void CPMScreen::getCursorCenter(SInt32& x, SInt32& y) const { x = m_xCenter; y = m_yCenter; } void CPMScreen::fakeMouseButton(ButtonID id, bool press) const { //m_desks->fakeMouseButton(id, press); } void CPMScreen::fakeMouseMove(SInt32 x, SInt32 y) const { LOG((CLOG_DEBUG "fakeMouseMove: %d,%d (y=%d)", x, m_cy - y, y)); if (WinSetPointerPos(HWND_DESKTOP, x, m_cy - y)) { return; } LOG((CLOG_CRIT "fakeMouseMove: failed %#lx", WinGetLastError(CPMUtil::getHAB()))); } void CPMScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const { POINTL ptl; if (WinQueryPointerPos(HWND_DESKTOP, &ptl)) { LOG((CLOG_DEBUG "fakeMouseRelativeMove: %d,%d (%d,%d +/- %d,%d)", ptl.x + dx, ptl.y - dy, ptl.y, ptl.x, dx, dy)); if (WinSetPointerPos(HWND_DESKTOP, ptl.x + dx, ptl.y - dy)) { return; } } LOG((CLOG_CRIT "fakeMouseRelativeMove: failed %#lx", WinGetLastError(CPMUtil::getHAB()))); } void CPMScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const { //m_desks->fakeMouseWheel(xDelta, yDelta); } void CPMScreen::updateKeys() { //m_desks->updateKeys(); } void CPMScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton button) { CPlatformScreen::fakeKeyDown(id, mask, button); updateForceShowCursor(); } void CPMScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, SInt32 count, KeyButton button) { CPlatformScreen::fakeKeyRepeat(id, mask, count, button); updateForceShowCursor(); } void CPMScreen::fakeKeyUp(KeyButton button) { CPlatformScreen::fakeKeyUp(button); updateForceShowCursor(); } void CPMScreen::fakeAllKeysUp() { CPlatformScreen::fakeAllKeysUp(); updateForceShowCursor(); } HMODULE CPMScreen::openHookLibrary(const char* name) { // load the hook library HMODULE hmod; APIRET rc = DosLoadModule(NULL, 0, (PCSZ)name, &hmod); if (rc != NO_ERROR) { LOG((CLOG_ERR "Failed to load hook library (%s), rc=%d ", name, rc)); throw XScreenOpenFailure(); } // look up functions if ( (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setSides", (PPFN)&m_setSides)) != NO_ERROR || (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setZone", (PPFN)&m_setZone )) != NO_ERROR || (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_setMode", (PPFN)&m_setMode )) != NO_ERROR || (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_init", (PPFN)&m_init )) != NO_ERROR || (rc = DosQueryProcAddr(hmod, 0, (PCSZ)"_cleanup", (PPFN)&m_cleanup )) != NO_ERROR) { LOG((CLOG_ERR "Invalid hook library; use a newer %s.dll (rc=%d)", name, rc)); DosFreeModule(hmod); throw XScreenOpenFailure(); } // initialize hook library if (m_init(m_hmq, _gettid(), getpid()) == 0) { LOG((CLOG_ERR "Cannot initialize hook library; is synergy already running?")); DosFreeModule(hmod); throw XScreenOpenFailure(); } return hmod; } void CPMScreen::closeHookLibrary(HMODULE hmod) const { if (hmod != NULL) { m_cleanup(m_hab); DosFreeModule(hmod); } } HWND CPMScreen::createWindow(const char* name) { WinRegisterClass(m_hab, (PCSZ)"Synergy", CPMScreen::wndProc, CS_MOVENOTIFY, sizeof(uintptr_t)); HWND hwnd = WinCreateWindow(HWND_DESKTOP, (PCSZ)"Synergy", NULL, 0, //WS_?, 0, 0, 1, 1, NULLHANDLE, HWND_TOP, 0, this, NULL); if (hwnd == NULLHANDLE) { LOG((CLOG_ERR "failed to create window: %lx", WinGetLastError(m_hab))); throw XScreenOpenFailure(); } return hwnd; } void CPMScreen::destroyWindow(HWND hwnd) const { if (hwnd != NULL) { WinDestroyWindow(hwnd); } } void CPMScreen::sendEvent(CEvent::Type type, void* data) { EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), data)); } void CPMScreen::sendClipboardEvent(CEvent::Type type, ClipboardID id) { CClipboardInfo* info = (CClipboardInfo*)malloc(sizeof(CClipboardInfo)); info->m_id = id; info->m_sequenceNumber = m_sequenceNumber; sendEvent(type, info); } void CPMScreen::handleSystemEvent(const CEvent& event, void*) { PQMSG pqmsg = (PQMSG)event.getData(); assert(pqmsg != NULL); if (onPreDispatch(pqmsg->hwnd, pqmsg->msg, pqmsg->mp1, pqmsg->mp2)) return; WinDispatchMsg(CPMUtil::getHAB(), pqmsg); } void CPMScreen::updateButtons() { m_buttons[kButtonNone] = false; m_buttons[kButtonLeft] = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON1) & 0x8000) != 0; m_buttons[kButtonRight] = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON2) & 0x8000) != 0; m_buttons[kButtonMiddle] = (WinGetKeyState(HWND_DESKTOP, VK_BUTTON3) & 0x8000) != 0; m_buttons[kButtonExtra0 + 0] = false; m_buttons[kButtonExtra0 + 1] = false; } IKeyState* CPMScreen::getKeyState() const { return m_keyState; } bool CPMScreen::onPreDispatch(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) { // handle event switch (msg) { case SYNERGY_PM_MSG_DEBUG: LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", mp1, mp2)); return true; } if (m_isPrimary) { return onPreDispatchPrimary(hwnd, msg, mp1, mp2); } return false; } bool CPMScreen::onPreDispatchPrimary(HWND, ULONG msg, MPARAM mp1, MPARAM mp2) { // handle event switch (msg) { case SYNERGY_PM_MSG_MARK: return onMark((uintptr_t)mp1); case SYNERGY_PM_MSG_KEY: return onKey(SHORT1FROMMP(mp1), CHAR3FROMMP(mp1), CHAR4FROMMP(mp1), SHORT1FROMMP(mp2), SHORT2FROMMP(mp2)); case SYNERGY_PM_MSG_MOUSE_BUTTON: return onMouseButton(SHORT1FROMMP(mp1), (SHORT)SHORT1FROMMP(mp2), m_cy - (SHORT)SHORT2FROMMP(mp2)); case SYNERGY_PM_MSG_MOUSE_MOVE: return onMouseMove(SHORT1FROMMP(mp1), (SHORT)SHORT1FROMMP(mp2), m_cy - (SHORT)SHORT2FROMMP(mp2)); case SYNERGY_PM_MSG_PRE_WARP: { // Save position to compute delta of next motion m_xCursor = (SInt32)mp1; m_yCursor = (SInt32)mp2; // We warped the mouse. Discard events until we find the // matching post warp event. See warpCursorNoFlush() for // where the events are sent. We discard the matching // post warp event and can be sure we've skipped the warp // event. QMSG qmsg; while ( WinGetMsg(m_hab, &qmsg, NULLHANDLE, SYNERGY_PM_MSG_MOUSE_MOVE, SYNERGY_PM_MSG_POST_WARP) && qmsg.msg != SYNERGY_PM_MSG_POST_WARP) /* nothing */; } return true; case SYNERGY_PM_MSG_POST_WARP: LOG((CLOG_WARN "unmatched post warp")); return true; } return false; } bool CPMScreen::onEvent(HWND, ULONG msg, MPARAM mp1, MPARAM mp2, MRESULT *result) { switch (msg) { case WM_DRAWCLIPBOARD: //// first pass on the message //if (m_nextClipboardWindow != NULL) { // WinSendMessage(m_nextClipboardWindow, msg, mp1, mp2); //} // now handle the message return onClipboardChange(); } return false; } bool CPMScreen::onMark(UInt32 mark) { m_markReceived = mark; return true; } bool CPMScreen::onKey(USHORT fsFlags, UCHAR ucRepeat, UCHAR ucScanCode, USHORT usch, USHORT usvk) { #if 0 static const KeyModifierMask s_ctrlAlt = KeyModifierControl | KeyModifierAlt; LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, mp2=0x%08x", (mp1 & 0xff00u) >> 8, mp1 & 0xffu, (mp1 & 0x10000u) ? 1 : 0, mp2)); // get event info KeyButton button = (KeyButton)((mp2 & 0x01ff0000) >> 16); bool down = ((mp2 & 0x80000000u) == 0x00000000u); bool wasDown = isKeyDown(button); KeyModifierMask oldState = pollActiveModifiers(); // check for autorepeat if (m_keyState->testAutoRepeat(down, (mp2 & 0x40000000u) == 1, button)) { mp2 |= 0x40000000u; } // if the button is zero then guess what the button should be. // these are badly synthesized key events and logitech software // that maps mouse buttons to keys is known to do this. // alternatively, we could just throw these events out. if (button == 0) { button = m_keyState->virtualKeyToButton(mp1 & 0xffu); if (button == 0) { return true; } wasDown = isKeyDown(button); } // record keyboard state m_keyState->onKey(button, down, oldState); // windows doesn't tell us the modifier key state on mouse or key // events so we have to figure it out. most apps would use // GetKeyState() or even GetAsyncKeyState() for that but we can't // because our hook doesn't pass on key events for several modifiers. // it can't otherwise the system would interpret them normally on // the primary screen even when on a secondary screen. so tapping // alt would activate menus and tapping the windows key would open // the start menu. if you don't pass those events on in the hook // then GetKeyState() understandably doesn't reflect the effect of // the event. curiously, neither does GetAsyncKeyState(), which is // surprising. // // so anyway, we have to track the modifier state ourselves for // at least those modifiers we don't pass on. pollActiveModifiers() // does that but we have to update the keyboard state before calling // pollActiveModifiers() to get the right answer. but the only way // to set the modifier state or to set the up/down state of a key // is via onKey(). so we have to call onKey() twice. KeyModifierMask state = pollActiveModifiers(); m_keyState->onKey(button, down, state); // check for hot keys if (oldState != state) { // modifier key was pressed/released if (onHotKey(0, mp2)) { return true; } } else { // non-modifier was pressed/released if (onHotKey(mp1, mp2)) { return true; } } // ignore message if posted prior to last mark change if (!ignore()) { // check for ctrl+alt+del. we do not want to pass that to the // client. the user can use ctrl+alt+pause to emulate it. UINT virtKey = (mp1 & 0xffu); if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) { LOG((CLOG_DEBUG "discard ctrl+alt+del")); return true; } // check for ctrl+alt+del emulation if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && (state & s_ctrlAlt) == s_ctrlAlt) { LOG((CLOG_DEBUG "emulate ctrl+alt+del")); // switch mp1 and mp2 to be as if VK_DELETE was // pressed or released. when mapping the key we require that // we not use AltGr (the 0x10000 flag in mp1) and we not // use the keypad delete key (the 0x01000000 flag in mp2). mp1 = VK_DELETE | 0x00010000u; mp2 &= 0xfe000000; mp2 |= m_keyState->virtualKeyToButton(mp1 & 0xffu) << 16; mp2 |= 0x01000001; } // process key KeyModifierMask mask; KeyID key = m_keyState->mapKeyFromEvent((ULONG)mp1, (ULONG)mp2, &mask); button = static_cast((mp2 & 0x01ff0000u) >> 16); if (key != kKeyNone) { // fix key up. if the key isn't down according to // our table then we never got the key press event // for it. if it's not a modifier key then we'll // synthesize the press first. only do this on // the windows 95 family, which eats certain special // keys like alt+tab, ctrl+esc, etc. if (m_is95Family && !wasDown && !down) { switch (virtKey) { case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: case VK_MENU: case VK_LMENU: case VK_RMENU: case VK_LWIN: case VK_RWIN: case VK_CAPITAL: case VK_NUMLOCK: case VK_SCROLL: break; default: m_keyState->sendKeyEvent(getEventTarget(), true, false, key, mask, 1, button); break; } } // do it m_keyState->sendKeyEvent(getEventTarget(), ((mp2 & 0x80000000u) == 0), ((mp2 & 0x40000000u) != 0), key, mask, (SInt32)(mp2 & 0xffff), button); } else { LOG((CLOG_DEBUG1 "cannot map key")); } } #endif return true; } bool CPMScreen::onHotKey(MPARAM mp1, MPARAM mp2) { #if 0 // get the key info KeyModifierMask state = getActiveModifiers(); UINT virtKey = (mp1 & 0xffu); UINT modifiers = 0; if ((state & KeyModifierShift) != 0) { modifiers |= MOD_SHIFT; } if ((state & KeyModifierControl) != 0) { modifiers |= MOD_CONTROL; } if ((state & KeyModifierAlt) != 0) { modifiers |= MOD_ALT; } if ((state & KeyModifierSuper) != 0) { modifiers |= MOD_WIN; } // find the hot key id HotKeyToIDMap::const_iterator i = m_hotKeyToIDMap.find(CHotKeyItem(virtKey, modifiers)); if (i == m_hotKeyToIDMap.end()) { return false; } // find what kind of event CEvent::Type type; if ((mp2 & 0x80000000u) == 0u) { if ((mp2 & 0x40000000u) != 0u) { // ignore key repeats but it counts as a hot key return true; } type = getHotKeyDownEvent(); } else { type = getHotKeyUpEvent(); } // generate event EVENTQUEUE->addEvent(CEvent(type, getEventTarget(), CHotKeyInfo::alloc(i->second))); #endif return true; } bool CPMScreen::onMouseButton(ULONG msg, SInt32 ax, SInt32 ay) { // get which button bool pressed = mapPressFromEvent(msg); ButtonID button = mapButtonFromEvent(msg); // keep our shadow key state up to date if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { if (pressed) { m_buttons[button] = true; } else { m_buttons[button] = false; } } // ignore message if posted prior to last mark change if (!ignore()) { KeyModifierMask mask = m_keyState->getActiveModifiers(); if (pressed) { LOG((CLOG_DEBUG1 "event: button press button=%d", button)); if (button != kButtonNone) { sendEvent(getButtonDownEvent(), CButtonInfo::alloc(button, mask)); } } else { LOG((CLOG_DEBUG1 "event: button release button=%d", button)); if (button != kButtonNone) { sendEvent(getButtonUpEvent(), CButtonInfo::alloc(button, mask)); } } } return true; } bool CPMScreen::onMouseMove(ULONG msg, SInt32 ax, SInt32 ay) { // compute motion delta (relative to the last known // mouse position) SInt32 x = ax - m_xCursor; SInt32 y = ay - m_yCursor; // ignore if the mouse didn't move or if message posted prior // to last mark change. if (ignore() || (x == 0 && y == 0)) { return true; } // save position to compute delta of next motion m_xCursor = ax; m_yCursor = ay; if (m_isOnScreen) { // motion on primary screen sendEvent(getMotionOnPrimaryEvent(), CMotionInfo::alloc(m_xCursor, m_yCursor)); } else { // motion on secondary screen. warp mouse back to // center. warpCursorNoFlush(m_xCenter, m_yCenter); // examine the motion. if it's about the distance // from the center of the screen to an edge then // it's probably a bogus motion that we want to // ignore (see warpCursorNoFlush() for a further // description). static SInt32 bogusZoneSize = 10; if (-x + bogusZoneSize > m_xCenter - m_x || x + bogusZoneSize > m_x + m_cx - m_xCenter || -y + bogusZoneSize > m_yCenter - m_y || y + bogusZoneSize > m_y + m_cy - m_yCenter) { LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); } else { // send motion sendEvent(getMotionOnSecondaryEvent(), CMotionInfo::alloc(x, y)); } } return true; } bool CPMScreen::onClipboardChange() { // now notify client that somebody changed the clipboard (unless // we're the owner). if (!CPMClipboard::isOwnedBySynergy()) { if (m_ownClipboard) { LOG((CLOG_DEBUG "clipboard changed: lost ownership")); m_ownClipboard = false; sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardClipboard); sendClipboardEvent(getClipboardGrabbedEvent(), kClipboardSelection); } } else if (!m_ownClipboard) { LOG((CLOG_DEBUG "clipboard changed: synergy owned")); m_ownClipboard = true; } return true; } void CPMScreen::warpCursorNoFlush(SInt32 x, SInt32 y) { // send an event that we can recognize before the mouse warp WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_PRE_WARP, (MPARAM)x, (MPARAM)y); // warp mouse. hopefully this inserts a mouse motion event // between the previous message and the following message. WinSetPointerPos(HWND_DESKTOP, x, y); // yield the CPU. there's a race condition when warping: // a hardware mouse event occurs // the mouse hook is not called because that process doesn't have the CPU // we send PRE_WARP, SetCursorPos(), send POST_WARP // we process all of those events and update m_x, m_y // we finish our time slice // the hook is called // the hook sends us a mouse event from the pre-warp position // we get the CPU // we compute a bogus warp // we need the hook to process all mouse events that occur // before we warp before we do the warp but i'm not sure how // to guarantee that. yielding the CPU here may reduce the // chance of undesired behavior. we'll also check for very // large motions that look suspiciously like about half width // or height of the screen. ARCH->sleep(0.0); // send an event that we can recognize after the mouse warp WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_POST_WARP, 0, 0); } void CPMScreen::nextMark() { // next mark ++m_mark; // mark point in message queue where the mark was changed WinPostQueueMsg(m_hmq, SYNERGY_PM_MSG_MARK, (MPARAM)m_mark, 0); } bool CPMScreen::ignore() const { return (m_mark != m_markReceived); } void CPMScreen::updateScreenShape() { // get shape m_x = 0; m_y = 0; m_cx = WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN); m_cy = WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN); // get center for cursor m_xCenter = m_cx / 2; m_yCenter = m_cy / 2; // check for multiple monitors m_multimon = false; } ButtonID CPMScreen::mapButtonFromEvent(ULONG msg) const { /** @todo query which is left and which is right. */ switch (msg) { case WM_BUTTON1DOWN: case WM_BUTTON1UP: case WM_BUTTON1DBLCLK: case WM_BUTTON1CLICK: //?? return kButtonLeft; case WM_BUTTON2DOWN: case WM_BUTTON2UP: case WM_BUTTON2DBLCLK: case WM_BUTTON2CLICK: //?? return kButtonLeft; case WM_BUTTON3DOWN: case WM_BUTTON3UP: case WM_BUTTON3DBLCLK: case WM_BUTTON3CLICK: //?? return kButtonMiddle; default: return kButtonNone; } } bool CPMScreen::mapPressFromEvent(ULONG msg) const { switch (msg) { case WM_BUTTON1DOWN: case WM_BUTTON1CLICK: //?? case WM_BUTTON1DBLCLK: case WM_BUTTON2DOWN: case WM_BUTTON2DBLCLK: case WM_BUTTON2CLICK: //?? case WM_BUTTON3DOWN: case WM_BUTTON3DBLCLK: case WM_BUTTON3CLICK: //?? return true; case WM_BUTTON1UP: case WM_BUTTON2UP: case WM_BUTTON3UP: return false; default: return false; } } void CPMScreen::updateKeysCB(void*) { // record which keys we think are down bool down[IKeyState::kNumButtons]; bool sendFixes = (isPrimary() && !m_isOnScreen); if (sendFixes) { for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { down[i] = m_keyState->isKeyDown(i); } } // now update the keyboard state CPlatformScreen::updateKeyState(); // now see which keys we thought were down but now think are up. // send key releases for these keys to the active client. if (sendFixes) { KeyModifierMask mask = pollActiveModifiers(); for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { if (down[i] && !m_keyState->isKeyDown(i)) { m_keyState->sendKeyEvent(getEventTarget(), false, false, kKeyNone, mask, 1, i); } } } } void CPMScreen::forceShowCursor() { // check for mouse - probably not required. m_hasMouse = WinQuerySysValue(HWND_DESKTOP, SV_MOUSEPRESENT) != FALSE; // decide if we should show the mouse bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen); // show/hide the mouse if (showMouse != m_showingMouse) { WinShowPointer(HWND_DESKTOP, showMouse); m_showingMouse = WinQuerySysValue(HWND_DESKTOP, SV_POINTERLEVEL) != 0; } } void CPMScreen::updateForceShowCursor() { //??? } MRESULT EXPENTRY CPMScreen::wndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2) { assert(s_screen != NULL); MRESULT mr = 0; if (!s_screen->onEvent(hwnd, msg, mp1, mp2, &mr)) { mr = WinDefWindowProc(hwnd, msg, mp1, mp2); } return mr; } // // CPMScreen::CHotKeyItem // CPMScreen::CHotKeyItem::CHotKeyItem(ULONG keycode, ULONG mask) : m_keycode(keycode), m_mask(mask) { // do nothing } ULONG CPMScreen::CHotKeyItem::getVirtualKey() const { return m_keycode; } bool CPMScreen::CHotKeyItem::operator<(const CHotKeyItem& x) const { return (m_keycode < x.m_keycode || (m_keycode == x.m_keycode && m_mask < x.m_mask)); } /* * Local Variables: * mode: c * c-file-style: "k&r" * c-basic-offset: 4 * tab-width: 4 * indent-tabs-mode: t * End: */