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
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");
}

View File

@ -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<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);
// 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();
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
switchScreen(newScreen, x, y, false);
return true;
}
else {
return false;
}
}
void
@ -721,9 +701,10 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
SInt32 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;
IClient* newScreen = NULL;
if (m_x < ax) {
dir = kLeft;
}
@ -737,53 +718,16 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
dir = kBottom;
}
else {
// we haven't left the screen
newScreen = m_active;
// keep compiler quiet about unset variable
dir = kLeft;
}
// 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) {
// 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_switchWaitDir) {
switch (m_switchDir) {
case kLeft:
clearWait = (m_x >= ax + zoneSize);
break;
@ -801,13 +745,31 @@ CServer::onMouseMoveSecondaryNoLock(SInt32 dx, SInt32 dy)
break;
}
if (clearWait) {
clearSwitchWait();
}
onNoSwitch();
}
}
// clamp mouse to edge
if (clamp) {
// skip rest of block
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) {
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));

View File

@ -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<SInt32> 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

View File

@ -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

View File

@ -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,