Added support for a user option to require hitting the edge of a

screen twice within a specified amount of time in order to switch
screens.  This can help prevent unintended switching.
This commit is contained in:
crs 2003-02-23 19:29:08 +00:00
parent 7bbd33d787
commit f411df65fb
5 changed files with 262 additions and 151 deletions

View File

@ -79,7 +79,7 @@ CConfig::renameScreen(const CString& oldName,
// update connections // update connections
for (index = m_map.begin(); index != m_map.end(); ++index) { 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( if (CStringUtil::CaselessCmp::equal(getCanonicalName(
index->second.m_neighbor[i]), oldCanonical)) { index->second.m_neighbor[i]), oldCanonical)) {
index->second.m_neighbor[i] = newName; index->second.m_neighbor[i] = newName;
@ -117,7 +117,7 @@ CConfig::removeScreen(const CString& name)
// disconnect // disconnect
for (index = m_map.begin(); index != m_map.end(); ++index) { for (index = m_map.begin(); index != m_map.end(); ++index) {
CCell& cell = index->second; 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) { if (getCanonicalName(cell.m_neighbor[i]) == canonical) {
cell.m_neighbor[i].erase(); cell.m_neighbor[i].erase();
} }
@ -200,6 +200,8 @@ bool
CConfig::connect(const CString& srcName, CConfig::connect(const CString& srcName,
EDirection srcSide, const CString& dstName) EDirection srcSide, const CString& dstName)
{ {
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell // find source cell
CCellMap::iterator index = m_map.find(getCanonicalName(srcName)); CCellMap::iterator index = m_map.find(getCanonicalName(srcName));
if (index == m_map.end()) { if (index == m_map.end()) {
@ -217,6 +219,8 @@ CConfig::connect(const CString& srcName,
bool bool
CConfig::disconnect(const CString& srcName, EDirection srcSide) CConfig::disconnect(const CString& srcName, EDirection srcSide)
{ {
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell // find source cell
CCellMap::iterator index = m_map.find(srcName); CCellMap::iterator index = m_map.find(srcName);
if (index == m_map.end()) { if (index == m_map.end()) {
@ -406,6 +410,8 @@ CConfig::getCanonicalName(const CString& name) const
CString CString
CConfig::getNeighbor(const CString& srcName, EDirection srcSide) const CConfig::getNeighbor(const CString& srcName, EDirection srcSide) const
{ {
assert(srcSide >= kFirstDirection && srcSide <= kLastDirection);
// find source cell // find source cell
CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); CCellMap::const_iterator index = m_map.find(getCanonicalName(srcName));
if (index == m_map.end()) { if (index == m_map.end()) {
@ -485,7 +491,7 @@ CConfig::operator==(const CConfig& x) const
} }
// compare neighbors // 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], if (!CStringUtil::CaselessCmp::equal(index1->second.m_neighbor[i],
index2->second.m_neighbor[i])) { index2->second.m_neighbor[i])) {
return false; return false;
@ -516,6 +522,9 @@ const char*
CConfig::dirName(EDirection dir) CConfig::dirName(EDirection dir)
{ {
static const char* s_name[] = { "left", "right", "top", "bottom" }; static const char* s_name[] = { "left", "right", "top", "bottom" };
assert(dir >= kFirstDirection && dir <= kLastDirection);
return s_name[dir - kFirstDirection]; return s_name[dir - kFirstDirection];
} }
@ -627,6 +636,9 @@ CConfig::getOptionName(OptionID id)
if (id == kOptionScreenSwitchDelay) { if (id == kOptionScreenSwitchDelay) {
return "switchDelay"; return "switchDelay";
} }
if (id == kOptionScreenSwitchTwoTap) {
return "switchDoubleTap";
}
return NULL; return NULL;
} }
@ -663,7 +675,8 @@ CConfig::getOptionValue(OptionID id, OptionValue value)
} }
} }
if (id == kOptionHeartbeat || if (id == kOptionHeartbeat ||
id == kOptionScreenSwitchDelay) { id == kOptionScreenSwitchDelay ||
id == kOptionScreenSwitchTwoTap) {
return CStringUtil::print("%d", value); return CStringUtil::print("%d", value);
} }
@ -774,6 +787,9 @@ CConfig::readSectionOptions(std::istream& s)
else if (name == "switchDelay") { else if (name == "switchDelay") {
addOption("", kOptionScreenSwitchDelay, parseInt(value)); addOption("", kOptionScreenSwitchDelay, parseInt(value));
} }
else if (name == "switchDoubleTap") {
addOption("", kOptionScreenSwitchTwoTap, parseInt(value));
}
else { else {
throw XConfigRead("unknown argument"); throw XConfigRead("unknown argument");
} }

View File

@ -60,8 +60,13 @@ CServer::CServer(const CString& serverName) :
m_activeSaver(NULL), m_activeSaver(NULL),
m_httpServer(NULL), m_httpServer(NULL),
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests), m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests),
m_switchDir(kNoDirection),
m_switchScreen(NULL),
m_switchWaitDelay(0.0), m_switchWaitDelay(0.0),
m_switchWaitScreen(NULL) m_switchWaitEngaged(false),
m_switchTwoTapDelay(0.0),
m_switchTwoTapEngaged(false),
m_switchTwoTapArmed(false)
{ {
// do nothing // do nothing
} }
@ -231,7 +236,14 @@ CServer::setConfig(const CConfig& config)
if (m_switchWaitDelay < 0.0) { if (m_switchWaitDelay < 0.0) {
m_switchWaitDelay = 0.0; m_switchWaitDelay = 0.0;
} }
clearSwitchWait(); m_switchWaitEngaged = false;
}
else if (id == kOptionScreenSwitchTwoTap) {
m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(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); CLock lock(&m_mutex);
// ignore old timer or if there's no jump screen anymore // ignore if it's an old timer or if switch wait isn't engaged anymore
if (m_switchWaitScreen == NULL || id != m_switchWaitTimer) { if (!m_switchWaitEngaged || id != m_switchWaitTimer) {
clearSwitchWait();
return; return;
} }
// ignore if mouse is locked to screen // ignore if mouse is locked to screen
if (isLockedToScreenNoLock()) { if (isLockedToScreenNoLock()) {
clearSwitchWait(); LOG((CLOG_DEBUG1 "locked to screen"));
clearSwitchState();
return; return;
} }
// switch screen // switch screen
switchScreen(m_switchWaitScreen, m_switchWaitX, m_switchWaitY, false); switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false);
}
void
CServer::clearSwitchWait()
{
if (m_switchWaitScreen != NULL) {
LOG((CLOG_DEBUG1 "cancel switch wait"));
m_switchWaitScreen = NULL;
}
} }
void void
@ -629,60 +632,37 @@ CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
if (x < ax + zoneSize) { if (x < ax + zoneSize) {
x -= zoneSize; x -= zoneSize;
dir = kLeft; dir = kLeft;
LOG((CLOG_DEBUG1 "switch to left"));
} }
else if (x >= ax + aw - zoneSize) { else if (x >= ax + aw - zoneSize) {
x += zoneSize; x += zoneSize;
dir = kRight; dir = kRight;
LOG((CLOG_DEBUG1 "switch to right"));
} }
else if (y < ay + zoneSize) { else if (y < ay + zoneSize) {
y -= zoneSize; y -= zoneSize;
dir = kTop; dir = kTop;
LOG((CLOG_DEBUG1 "switch to top"));
} }
else if (y >= ay + ah - zoneSize) { else if (y >= ay + ah - zoneSize) {
y += zoneSize; y += zoneSize;
dir = kBottom; dir = kBottom;
LOG((CLOG_DEBUG1 "switch to bottom"));
} }
else { else {
// still on local screen // still on local screen
clearSwitchWait(); onNoSwitch();
return false; return false;
} }
// get jump destination and, if no screen in jump direction, // get jump destination
// then ignore the move.
IClient* newScreen = getNeighbor(m_active, dir, x, y); IClient* newScreen = getNeighbor(m_active, dir, x, y);
if (newScreen == NULL) {
clearSwitchWait();
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;
}
// should we switch or not?
if (isSwitchOkay(newScreen, dir, x, y)) {
// switch screen // switch screen
switchScreen(newScreen, x, y, false); switchScreen(newScreen, x, y, false);
return true; return true;
}
else {
return false;
}
} }
void void
@ -721,9 +701,10 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
SInt32 ax, ay, aw, ah; SInt32 ax, ay, aw, ah;
m_active->getShape(ax, ay, aw, ah); m_active->getShape(ax, ay, aw, ah);
// find direction of neighbor // find direction of neighbor and get the neighbor
IClient* newScreen;
do {
EDirection dir; EDirection dir;
IClient* newScreen = NULL;
if (m_x < ax) { if (m_x < ax) {
dir = kLeft; dir = kLeft;
} }
@ -737,53 +718,16 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
dir = kBottom; dir = kBottom;
} }
else { else {
// we haven't left the screen
newScreen = m_active; newScreen = m_active;
// keep compiler quiet about unset variable // if waiting and mouse is not on the border we're waiting
dir = kLeft; // on then stop waiting. also if it's not on the border
} // then arm the double tap.
if (m_switchScreen != NULL) {
// 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
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;
}
}
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; bool clearWait;
SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); SInt32 zoneSize = m_primaryClient->getJumpZoneSize();
switch (m_switchWaitDir) { switch (m_switchDir) {
case kLeft: case kLeft:
clearWait = (m_x >= ax + zoneSize); clearWait = (m_x >= ax + zoneSize);
break; break;
@ -801,13 +745,31 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
break; break;
} }
if (clearWait) { if (clearWait) {
clearSwitchWait(); onNoSwitch();
}
} }
} }
// clamp mouse to edge // skip rest of block
if (clamp) { break;
}
// try to switch screen. get the neighbor.
newScreen = getNeighbor(m_active, dir, m_x, m_y);
// 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 {
// same screen. clamp mouse to edge.
m_x = xOld + dx;
m_y = yOld + dy;
if (m_x < ax) { if (m_x < ax) {
m_x = ax; m_x = ax;
LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", m_active->getName().c_str())); 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; m_y = ay + ah - 1;
LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", m_active->getName().c_str())); LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", m_active->getName().c_str()));
} }
newScreen = NULL;
}
// warp cursor if on same screen // warp cursor if it moved.
if (newScreen == NULL || newScreen == m_active) {
// do nothing if mouse didn't move
if (m_x != xOld || m_y != yOld) { 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)); 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); m_active->mouseMove(m_x, m_y);
} }
} }
// otherwise screen screens
else {
switchScreen(newScreen, m_x, m_y, false);
}
} }
void 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)); 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 // stop waiting to switch
clearSwitchWait(); clearSwitchState();
// record new position // record new position
m_x = x; m_x = x;
@ -1166,6 +1119,120 @@ CServer::getNeighbor(IClient* src,
return dst; 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 void
CServer::closeClients(const CConfig& config) CServer::closeClients(const CConfig& config)
{ {
@ -1196,8 +1263,8 @@ CServer::closeClients(const CConfig& config)
index2->second->close(); index2->second->close();
// don't switch to it if we planned to // don't switch to it if we planned to
if (index2->second == m_switchWaitScreen) { if (index2->second == m_switchScreen) {
clearSwitchWait(); clearSwitchState();
} }
} }
else { else {
@ -1890,7 +1957,9 @@ CServer::removeConnection(const CString& name)
m_primaryClient->getCursorCenter(m_x, m_y); m_primaryClient->getCursorCenter(m_x, m_y);
// stop waiting to switch if we were // stop waiting to switch if we were
clearSwitchWait(); if (active == m_switchScreen) {
clearSwitchState();
}
// don't notify active screen since it probably already disconnected // 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)); LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", active->getName().c_str(), m_primaryClient->getName().c_str(), m_x, m_y));

View File

@ -22,6 +22,7 @@
#include "CCondVar.h" #include "CCondVar.h"
#include "CMutex.h" #include "CMutex.h"
#include "CThread.h" #include "CThread.h"
#include "CStopwatch.h"
#include "stdlist.h" #include "stdlist.h"
#include "stdmap.h" #include "stdmap.h"
@ -198,8 +199,18 @@ private:
IClient* getNeighbor(IClient*, EDirection, IClient* getNeighbor(IClient*, EDirection,
SInt32& x, SInt32& y) const; 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 // reset switch wait state
void clearSwitchWait(); void clearSwitchState();
// send screen options to \c client // send screen options to \c client
void sendOptions(IClient* client) const; void sendOptions(IClient* client) const;
@ -325,11 +336,22 @@ private:
CCondVar<SInt32> m_httpAvailable; CCondVar<SInt32> m_httpAvailable;
static const SInt32 s_httpMaxSimultaneousRequests; 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; double m_switchWaitDelay;
EDirection m_switchWaitDir;
UInt32 m_switchWaitTimer; UInt32 m_switchWaitTimer;
IClient* m_switchWaitScreen; bool m_switchWaitEngaged;
SInt32 m_switchWaitX, m_switchWaitY; SInt32 m_switchWaitX, m_switchWaitY;
// state for double-tap screen switching
double m_switchTwoTapDelay;
CStopwatch m_switchTwoTapTimer;
bool m_switchTwoTapEngaged;
bool m_switchTwoTapArmed;
}; };
#endif #endif

View File

@ -52,6 +52,7 @@ static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM");
static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR");
static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); static const OptionID kOptionHeartbeat = OPTION_CODE("HART");
static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT"); static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT");
static const OptionID kOptionScreenSwitchTwoTap = OPTION_CODE("SSTT");
//@} //@}
#undef OPTION_CODE #undef OPTION_CODE

View File

@ -36,14 +36,17 @@ static const double kHeartBeatsUntilDeath = 3.0;
// direction constants // direction constants
enum EDirection { enum EDirection {
kNoDirection,
kLeft, kLeft,
kRight, kRight,
kTop, kTop,
kBottom, kBottom,
kFirstDirection = kLeft, kFirstDirection = kLeft,
kLastDirection = kBottom kLastDirection = kBottom,
kNumDirections = kLastDirection - kFirstDirection + 1
}; };
enum EDirectionMask { enum EDirectionMask {
kNoDirMask = 0,
kLeftMask = 1 << kLeft, kLeftMask = 1 << kLeft,
kRightMask = 1 << kRight, kRightMask = 1 << kRight,
kTopMask = 1 << kTop, kTopMask = 1 << kTop,