diff --git a/lib/server/CConfig.cpp b/lib/server/CConfig.cpp index 09633313..b9024d6b 100644 --- a/lib/server/CConfig.cpp +++ b/lib/server/CConfig.cpp @@ -79,7 +79,7 @@ CConfig::renameScreen(const CString& oldName, // update connections for (index = m_map.begin(); index != m_map.end(); ++index) { - for (UInt32 i = 0; i <= kLastDirection - kFirstDirection; ++i) { + for (UInt32 i = 0; i < kNumDirections; ++i) { if (CStringUtil::CaselessCmp::equal(getCanonicalName( index->second.m_neighbor[i]), oldCanonical)) { index->second.m_neighbor[i] = newName; @@ -117,7 +117,7 @@ CConfig::removeScreen(const CString& name) // disconnect for (index = m_map.begin(); index != m_map.end(); ++index) { CCell& cell = index->second; - for (UInt32 i = 0; i <= kLastDirection - kFirstDirection; ++i) { + for (UInt32 i = 0; i < kNumDirections; ++i) { if (getCanonicalName(cell.m_neighbor[i]) == canonical) { cell.m_neighbor[i].erase(); } @@ -200,6 +200,8 @@ bool CConfig::connect(const CString& srcName, EDirection srcSide, const CString& dstName) { + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + // find source cell CCellMap::iterator index = m_map.find(getCanonicalName(srcName)); if (index == m_map.end()) { @@ -217,6 +219,8 @@ CConfig::connect(const CString& srcName, bool CConfig::disconnect(const CString& srcName, EDirection srcSide) { + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + // find source cell CCellMap::iterator index = m_map.find(srcName); if (index == m_map.end()) { @@ -406,6 +410,8 @@ CConfig::getCanonicalName(const CString& name) const CString CConfig::getNeighbor(const CString& srcName, EDirection srcSide) const { + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + // find source cell CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); if (index == m_map.end()) { @@ -485,7 +491,7 @@ CConfig::operator==(const CConfig& x) const } // compare neighbors - for (UInt32 i = 0; i <= kLastDirection - kFirstDirection; ++i) { + for (UInt32 i = 0; i < kNumDirections; ++i) { if (!CStringUtil::CaselessCmp::equal(index1->second.m_neighbor[i], index2->second.m_neighbor[i])) { return false; @@ -516,6 +522,9 @@ const char* CConfig::dirName(EDirection dir) { static const char* s_name[] = { "left", "right", "top", "bottom" }; + + assert(dir >= kFirstDirection && dir <= kLastDirection); + return s_name[dir - kFirstDirection]; } @@ -627,6 +636,9 @@ CConfig::getOptionName(OptionID id) if (id == kOptionScreenSwitchDelay) { return "switchDelay"; } + if (id == kOptionScreenSwitchTwoTap) { + return "switchDoubleTap"; + } return NULL; } @@ -663,7 +675,8 @@ CConfig::getOptionValue(OptionID id, OptionValue value) } } if (id == kOptionHeartbeat || - id == kOptionScreenSwitchDelay) { + id == kOptionScreenSwitchDelay || + id == kOptionScreenSwitchTwoTap) { return CStringUtil::print("%d", value); } @@ -774,6 +787,9 @@ CConfig::readSectionOptions(std::istream& s) else if (name == "switchDelay") { addOption("", kOptionScreenSwitchDelay, parseInt(value)); } + else if (name == "switchDoubleTap") { + addOption("", kOptionScreenSwitchTwoTap, parseInt(value)); + } else { throw XConfigRead("unknown argument"); } diff --git a/lib/server/CServer.cpp b/lib/server/CServer.cpp index ce554f9b..37c43133 100644 --- a/lib/server/CServer.cpp +++ b/lib/server/CServer.cpp @@ -60,8 +60,13 @@ CServer::CServer(const CString& serverName) : m_activeSaver(NULL), m_httpServer(NULL), m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests), + m_switchDir(kNoDirection), + m_switchScreen(NULL), m_switchWaitDelay(0.0), - m_switchWaitScreen(NULL) + m_switchWaitEngaged(false), + m_switchTwoTapDelay(0.0), + m_switchTwoTapEngaged(false), + m_switchTwoTapArmed(false) { // do nothing } @@ -231,7 +236,14 @@ CServer::setConfig(const CConfig& config) if (m_switchWaitDelay < 0.0) { m_switchWaitDelay = 0.0; } - clearSwitchWait(); + m_switchWaitEngaged = false; + } + else if (id == kOptionScreenSwitchTwoTap) { + m_switchTwoTapDelay = 1.0e-3 * static_cast(value); + if (m_switchTwoTapDelay < 0.0) { + m_switchTwoTapDelay = 0.0; + } + m_switchTwoTapEngaged = false; } } } @@ -508,29 +520,20 @@ CServer::onOneShotTimerExpired(UInt32 id) { CLock lock(&m_mutex); - // ignore old timer or if there's no jump screen anymore - if (m_switchWaitScreen == NULL || id != m_switchWaitTimer) { - clearSwitchWait(); + // ignore if it's an old timer or if switch wait isn't engaged anymore + if (!m_switchWaitEngaged || id != m_switchWaitTimer) { return; } // ignore if mouse is locked to screen if (isLockedToScreenNoLock()) { - clearSwitchWait(); + LOG((CLOG_DEBUG1 "locked to screen")); + clearSwitchState(); return; } // switch screen - switchScreen(m_switchWaitScreen, m_switchWaitX, m_switchWaitY, false); -} - -void -CServer::clearSwitchWait() -{ - if (m_switchWaitScreen != NULL) { - LOG((CLOG_DEBUG1 "cancel switch wait")); - m_switchWaitScreen = NULL; - } + switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); } void @@ -629,60 +632,37 @@ CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y) if (x < ax + zoneSize) { x -= zoneSize; dir = kLeft; - LOG((CLOG_DEBUG1 "switch to left")); } else if (x >= ax + aw - zoneSize) { x += zoneSize; dir = kRight; - LOG((CLOG_DEBUG1 "switch to right")); } else if (y < ay + zoneSize) { y -= zoneSize; dir = kTop; - LOG((CLOG_DEBUG1 "switch to top")); } else if (y >= ay + ah - zoneSize) { y += zoneSize; dir = kBottom; - LOG((CLOG_DEBUG1 "switch to bottom")); } else { // still on local screen - clearSwitchWait(); + onNoSwitch(); return false; } - // get jump destination and, if no screen in jump direction, - // then ignore the move. + // get jump destination IClient* newScreen = getNeighbor(m_active, dir, x, y); - if (newScreen == NULL) { - clearSwitchWait(); + + // should we switch or not? + if (isSwitchOkay(newScreen, dir, x, y)) { + // switch screen + switchScreen(newScreen, x, y, false); + return true; + } + else { return false; } - - // if waiting before a switch then prepare to switch later - if (m_switchWaitDelay > 0.0) { - if (m_switchWaitScreen == NULL || dir != m_switchWaitDir) { - m_switchWaitDir = dir; - m_switchWaitScreen = newScreen; - m_switchWaitX = x; - m_switchWaitY = y; - m_switchWaitTimer = m_primaryClient->addOneShotTimer( - m_switchWaitDelay); - LOG((CLOG_DEBUG1 "waiting to switch")); - } - return false; - } - - // ignore if mouse is locked to screen - if (isLockedToScreenNoLock()) { - LOG((CLOG_DEBUG1 "locked to screen")); - return false; - } - - // switch screen - switchScreen(newScreen, x, y, false); - return true; } void @@ -721,93 +701,75 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy) SInt32 ax, ay, aw, ah; m_active->getShape(ax, ay, aw, ah); - // find direction of neighbor - EDirection dir; - IClient* newScreen = NULL; - if (m_x < ax) { - dir = kLeft; - } - else if (m_x > ax + aw - 1) { - dir = kRight; - } - else if (m_y < ay) { - dir = kTop; - } - else if (m_y > ay + ah - 1) { - dir = kBottom; - } - else { - newScreen = m_active; + // find direction of neighbor and get the neighbor + IClient* newScreen; + do { + EDirection dir; + if (m_x < ax) { + dir = kLeft; + } + else if (m_x > ax + aw - 1) { + dir = kRight; + } + else if (m_y < ay) { + dir = kTop; + } + else if (m_y > ay + ah - 1) { + dir = kBottom; + } + else { + // we haven't left the screen + newScreen = m_active; - // keep compiler quiet about unset variable - dir = kLeft; - } + // if waiting and mouse is not on the border we're waiting + // on then stop waiting. also if it's not on the border + // then arm the double tap. + if (m_switchScreen != NULL) { + bool clearWait; + SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); + switch (m_switchDir) { + case kLeft: + clearWait = (m_x >= ax + zoneSize); + break; - // switch screens if the mouse is outside the screen and not - // locked to the screen - bool clamp = false; - if (newScreen == NULL) { - // get neighbor we should switch to + case kRight: + clearWait = (m_x <= ax + aw - 1 - zoneSize); + break; + + case kTop: + clearWait = (m_y >= ay + zoneSize); + break; + + case kBottom: + clearWait = (m_y <= ay + ah - 1 + zoneSize); + break; + } + if (clearWait) { + onNoSwitch(); + } + } + + // skip rest of block + break; + } + + // try to switch screen. get the neighbor. newScreen = getNeighbor(m_active, dir, m_x, m_y); - LOG((CLOG_DEBUG1 "leave \"%s\" on %s", m_active->getName().c_str(), CConfig::dirName(dir))); - if (newScreen == NULL) { - LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir))); - clamp = true; - } - else if (m_switchWaitDelay > 0.0) { - // wait to switch; prepare to switch later - if (m_switchWaitScreen == NULL || dir != m_switchWaitDir) { - m_switchWaitDir = dir; - m_switchWaitScreen = newScreen; - m_switchWaitX = m_x; - m_switchWaitY = m_y; - m_switchWaitTimer = m_primaryClient->addOneShotTimer( - m_switchWaitDelay); - LOG((CLOG_DEBUG1 "waiting to switch")); - } - // don't try to switch screen now - m_x = xOld + dx; - m_y = yOld + dy; - clamp = true; - } - else if (isLockedToScreenNoLock()) { - // clamp to edge when locked to screen - LOG((CLOG_DEBUG1 "locked to screen")); - clamp = true; + // see if we should switch + if (!isSwitchOkay(newScreen, dir, m_x, m_y)) { + newScreen = m_active; } + } while (false); + + if (newScreen != m_active) { + // switch screens + switchScreen(newScreen, m_x, m_y, false); } else { - // on same screen. if waiting and mouse is not on the border - // we're waiting on then stop waiting. - if (m_switchWaitScreen != NULL) { - bool clearWait; - SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); - switch (m_switchWaitDir) { - case kLeft: - clearWait = (m_x >= ax + zoneSize); - break; - - case kRight: - clearWait = (m_x <= ax + aw - 1 - zoneSize); - break; - - case kTop: - clearWait = (m_y >= ay + zoneSize); - break; - - case kBottom: - clearWait = (m_y <= ay + ah - 1 + zoneSize); - break; - } - if (clearWait) { - clearSwitchWait(); - } - } - } - - // clamp mouse to edge - if (clamp) { + // same screen. clamp mouse to edge. + m_x = xOld + dx; + m_y = yOld + dy; if (m_x < ax) { m_x = ax; LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", m_active->getName().c_str())); @@ -824,22 +786,13 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy) m_y = ay + ah - 1; LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", m_active->getName().c_str())); } - newScreen = NULL; - } - // warp cursor if on same screen - if (newScreen == NULL || newScreen == m_active) { - // do nothing if mouse didn't move + // warp cursor if it moved. if (m_x != xOld || m_y != yOld) { LOG((CLOG_DEBUG2 "move on %s to %d,%d", m_active->getName().c_str(), m_x, m_y)); m_active->mouseMove(m_x, m_y); } } - - // otherwise screen screens - else { - switchScreen(newScreen, m_x, m_y, false); - } } void @@ -894,7 +847,7 @@ CServer::switchScreen(IClient* dst, SInt32 x, SInt32 y, bool forScreensaver) LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", m_active->getName().c_str(), dst->getName().c_str(), x, y)); // stop waiting to switch - clearSwitchWait(); + clearSwitchState(); // record new position m_x = x; @@ -1166,6 +1119,120 @@ CServer::getNeighbor(IClient* src, return dst; } +bool +CServer::isSwitchOkay(IClient* newScreen, EDirection dir, SInt32 x, SInt32 y) +{ + LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", m_active->getName().c_str(), CConfig::dirName(dir))); + + // is there a neighbor? + if (newScreen == NULL) { + // there's no neighbor. we don't want to switch and we don't + // want to try to switch later. + LOG((CLOG_DEBUG1 "no neighbor %s", CConfig::dirName(dir))); + clearSwitchState(); + return false; + } + + // should we switch or not? + bool preventSwitch = false; + bool allowSwitch = false; + + // note if the switch direction has changed. save the new + // direction and screen if so. + bool isNewDirection = (dir != m_switchDir); + if (isNewDirection || m_switchScreen == NULL) { + m_switchDir = dir; + m_switchScreen = newScreen; + } + + // is this a double tap and do we care? + if (!allowSwitch && m_switchTwoTapDelay > 0.0) { + if (isNewDirection || !m_switchTwoTapEngaged) { + // tapping a different or new edge. prepare for second tap. + preventSwitch = true; + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); + } + else { + // second tap if we were armed. if soon enough then switch. + if (m_switchTwoTapArmed && + m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay) { + allowSwitch = true; + } + else { + // not fast enough. reset the clock. + preventSwitch = true; + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); + } + } + } + + // if waiting before a switch then prepare to switch later + if (!allowSwitch && m_switchWaitDelay > 0.0) { + if (isNewDirection) { + m_switchWaitEngaged = true; + m_switchWaitX = x; + m_switchWaitY = y; + m_switchWaitTimer = m_primaryClient->addOneShotTimer( + m_switchWaitDelay); + LOG((CLOG_DEBUG1 "waiting to switch")); + } + preventSwitch = true; + } + + // ignore if mouse is locked to screen + if (!preventSwitch && isLockedToScreenNoLock()) { + LOG((CLOG_DEBUG1 "locked to screen")); + preventSwitch = true; + + // don't try to switch later. it's possible that we might + // not be locked to the screen when the wait delay expires + // and could switch then but we'll base the decision on + // when the user first attempts the switch. this also + // ensures that all switch tests are using the same + clearSwitchState(); + } + + return !preventSwitch; +} + +void +CServer::onNoSwitch() +{ + if (m_switchTwoTapEngaged) { + if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) { + // second tap took too long. disengage. + m_switchTwoTapEngaged = false; + m_switchTwoTapArmed = false; + } + else { + // we've moved away from the edge and there's still + // time to get back for a double tap. + m_switchTwoTapArmed = true; + } + } + + // once the mouse moves away from the edge we no longer want to + // switch after a delay. + m_switchWaitEngaged = false; +} + +void +CServer::clearSwitchState() +{ + if (m_switchScreen != NULL) { + m_switchDir = kNoDirection; + m_switchScreen = NULL; + m_switchWaitEngaged = false; + m_switchTwoTapEngaged = false; + } +} + void CServer::closeClients(const CConfig& config) { @@ -1196,8 +1263,8 @@ CServer::closeClients(const CConfig& config) index2->second->close(); // don't switch to it if we planned to - if (index2->second == m_switchWaitScreen) { - clearSwitchWait(); + if (index2->second == m_switchScreen) { + clearSwitchState(); } } else { @@ -1890,7 +1957,9 @@ CServer::removeConnection(const CString& name) m_primaryClient->getCursorCenter(m_x, m_y); // stop waiting to switch if we were - clearSwitchWait(); + if (active == m_switchScreen) { + clearSwitchState(); + } // don't notify active screen since it probably already disconnected LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y)); diff --git a/lib/server/CServer.h b/lib/server/CServer.h index d4e93778..11896830 100644 --- a/lib/server/CServer.h +++ b/lib/server/CServer.h @@ -22,6 +22,7 @@ #include "CCondVar.h" #include "CMutex.h" #include "CThread.h" +#include "CStopwatch.h" #include "stdlist.h" #include "stdmap.h" @@ -198,8 +199,18 @@ private: IClient* getNeighbor(IClient*, EDirection, SInt32& x, SInt32& y) const; + // test if a switch is permitted. this includes testing user + // options like switch delay and tracking any state required to + // implement them. returns true iff a switch is permitted. + bool isSwitchOkay(IClient* dst, EDirection, + SInt32 x, SInt32 y); + + // update switch state due to a mouse move that doesn't try to + // switch screens. + void onNoSwitch(); + // reset switch wait state - void clearSwitchWait(); + void clearSwitchState(); // send screen options to \c client void sendOptions(IClient* client) const; @@ -325,11 +336,22 @@ private: CCondVar m_httpAvailable; static const SInt32 s_httpMaxSimultaneousRequests; + // common state for screen switch tests. all tests are always + // trying to reach the same screen in the same direction. + EDirection m_switchDir; + IClient* m_switchScreen; + + // state for delayed screen switching double m_switchWaitDelay; - EDirection m_switchWaitDir; UInt32 m_switchWaitTimer; - IClient* m_switchWaitScreen; + bool m_switchWaitEngaged; SInt32 m_switchWaitX, m_switchWaitY; + + // state for double-tap screen switching + double m_switchTwoTapDelay; + CStopwatch m_switchTwoTapTimer; + bool m_switchTwoTapEngaged; + bool m_switchTwoTapArmed; }; #endif diff --git a/lib/synergy/OptionTypes.h b/lib/synergy/OptionTypes.h index ea7e594f..84a09f5c 100644 --- a/lib/synergy/OptionTypes.h +++ b/lib/synergy/OptionTypes.h @@ -52,6 +52,7 @@ static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM"); static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT"); +static const OptionID kOptionScreenSwitchTwoTap = OPTION_CODE("SSTT"); //@} #undef OPTION_CODE diff --git a/lib/synergy/ProtocolTypes.h b/lib/synergy/ProtocolTypes.h index b37d1265..8f9bd569 100644 --- a/lib/synergy/ProtocolTypes.h +++ b/lib/synergy/ProtocolTypes.h @@ -36,14 +36,17 @@ static const double kHeartBeatsUntilDeath = 3.0; // direction constants enum EDirection { + kNoDirection, kLeft, kRight, kTop, kBottom, kFirstDirection = kLeft, - kLastDirection = kBottom + kLastDirection = kBottom, + kNumDirections = kLastDirection - kFirstDirection + 1 }; enum EDirectionMask { + kNoDirMask = 0, kLeftMask = 1 << kLeft, kRightMask = 1 << kRight, kTopMask = 1 << kTop,