Added support on X11 for a global option to delay switching screens

when the mouse reaches a jump zone.
This commit is contained in:
crs 2003-02-22 21:53:25 +00:00
parent aef50800e3
commit 7bbd33d787
15 changed files with 306 additions and 79 deletions

View File

@ -102,6 +102,12 @@ CXWindowsPrimaryScreen::setOptions(const COptionsList& options)
}
}
UInt32
CXWindowsPrimaryScreen::addOneShotTimer(double timeout)
{
return m_screen->addOneShotTimer(timeout);
}
KeyModifierMask
CXWindowsPrimaryScreen::getToggleMask() const
{
@ -387,6 +393,12 @@ CXWindowsPrimaryScreen::onEvent(CEvent* event)
return false;
}
void
CXWindowsPrimaryScreen::onOneShotTimerExpired(UInt32 id)
{
m_receiver->onOneShotTimerExpired(id);
}
SInt32
CXWindowsPrimaryScreen::getJumpZoneSize() const
{

View File

@ -40,6 +40,7 @@ public:
virtual void warpCursor(SInt32 x, SInt32 y);
virtual void resetOptions();
virtual void setOptions(const COptionsList& options);
virtual UInt32 addOneShotTimer(double timeout);
virtual KeyModifierMask getToggleMask() const;
virtual bool isLockedToScreen() const;
virtual IScreen* getScreen() const;
@ -48,6 +49,7 @@ public:
virtual void onScreensaver(bool activated);
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual void onOneShotTimerExpired(UInt32 id);
virtual SInt32 getJumpZoneSize() const;
protected:

View File

@ -54,14 +54,13 @@
// CXWindowsScreen::CTimer
//
CXWindowsScreen::CTimer::CTimer(IJob* job, double timeout) :
CXWindowsScreen::CTimer::CTimer(IJob* job, double startTime, double resetTime) :
m_job(job),
m_timeout(timeout)
m_timeout(resetTime),
m_time(resetTime),
m_startTime(startTime)
{
assert(m_job != NULL);
assert(m_timeout > 0.0);
reset();
}
CXWindowsScreen::CTimer::~CTimer()
@ -72,19 +71,23 @@ CXWindowsScreen::CTimer::~CTimer()
void
CXWindowsScreen::CTimer::run()
{
m_job->run();
if (m_job != NULL) {
m_job->run();
}
}
void
CXWindowsScreen::CTimer::reset()
{
m_time = m_timeout;
m_time = m_timeout;
m_startTime = 0.0;
}
CXWindowsScreen::CTimer::CTimer&
CXWindowsScreen::CTimer::operator-=(double dt)
{
m_time -= dt;
m_time -= dt - m_startTime;
m_startTime = 0.0;
return *this;
}
@ -118,7 +121,8 @@ CXWindowsScreen::CXWindowsScreen(IScreenReceiver* receiver,
m_w(0), m_h(0),
m_screensaver(NULL),
m_screensaverNotify(false),
m_atomScreensaver(None)
m_atomScreensaver(None),
m_oneShotTimer(NULL)
{
assert(s_screen == NULL);
assert(m_receiver != NULL);
@ -137,6 +141,7 @@ CXWindowsScreen::~CXWindowsScreen()
assert(s_screen != NULL);
assert(m_display == NULL);
delete m_oneShotTimer;
s_screen = NULL;
}
@ -145,7 +150,7 @@ CXWindowsScreen::addTimer(IJob* job, double timeout)
{
CLock lock(&m_timersMutex);
removeTimerNoLock(job);
m_timers.push(CTimer(job, timeout));
m_timers.push(CTimer(job, m_time.getTime(), timeout));
}
void
@ -172,6 +177,15 @@ CXWindowsScreen::removeTimerNoLock(IJob* job)
m_timers.swap(tmp);
}
UInt32
CXWindowsScreen::addOneShotTimer(double timeout)
{
CLock lock(&m_timersMutex);
// FIXME -- support multiple one-shot timers
m_oneShotTimer = new CTimer(NULL, m_time.getTime(), timeout);
return 0;
}
void
CXWindowsScreen::setWindow(Window window)
{
@ -262,19 +276,27 @@ CXWindowsScreen::mainLoop()
#endif
while (!m_stop) {
// compute timeout to next timer
double dtimeout;
{
CLock timersLock(&m_timersMutex);
dtimeout = (m_timers.empty() ? -1.0 : m_timers.top());
if (m_oneShotTimer != NULL &&
(dtimeout == -1.0 || *m_oneShotTimer < dtimeout)) {
dtimeout = *m_oneShotTimer;
}
}
#if HAVE_POLL
int timeout = (m_timers.empty() ? -1 :
static_cast<int>(1000.0 * m_timers.top()));
int timeout = static_cast<int>(1000.0 * dtimeout);
#else
struct timeval timeout;
struct timeval* timeoutPtr;
if (m_timers.empty()) {
if (dtimeout < 0.0) {
timeoutPtr = NULL;
}
else {
timeout.tv_sec = static_cast<int>(m_timers.top());
timeout.tv_sec = static_cast<int>(dtimeout);
timeout.tv_usec = static_cast<int>(1.0e+6 *
(m_timers.top() - timeout.tv_sec));
(dtimeout - timeout.tv_sec));
timeoutPtr = &timeout;
}
@ -697,6 +719,7 @@ CXWindowsScreen::createBlankCursor()
bool
CXWindowsScreen::processTimers()
{
bool oneShot = false;
std::vector<IJob*> jobs;
{
CLock lock(&m_timersMutex);
@ -705,10 +728,21 @@ CXWindowsScreen::processTimers()
const double time = m_time.getTime();
// done if no timers have expired
if (m_timers.empty() || m_timers.top() > time) {
if ((m_oneShotTimer == NULL || *m_oneShotTimer > time) &&
(m_timers.empty() || m_timers.top() > time)) {
return false;
}
// handle one shot timers
if (m_oneShotTimer != NULL) {
*m_oneShotTimer -= time;
if (*m_oneShotTimer <= 0.0) {
delete m_oneShotTimer;
m_oneShotTimer = NULL;
oneShot = true;
}
}
// subtract current time from all timers. note that this won't
// change the order of elements in the priority queue (except
// for floating point round off which we'll ignore).
@ -718,18 +752,27 @@ CXWindowsScreen::processTimers()
}
// process all timers at or below zero, saving the jobs
while (m_timers.top() <= 0.0) {
CTimer timer = m_timers.top();
jobs.push_back(timer.getJob());
timer.reset();
m_timers.pop();
m_timers.push(timer);
if (!m_timers.empty()) {
while (m_timers.top() <= 0.0) {
CTimer timer = m_timers.top();
jobs.push_back(timer.getJob());
timer.reset();
m_timers.pop();
m_timers.push(timer);
}
}
// reset the clock
m_time.reset();
}
// now notify of the one shot timers
if (oneShot) {
m_mutex.unlock();
m_eventHandler->onOneShotTimerExpired(0);
m_mutex.lock();
}
// now run the jobs. note that if one of these jobs removes
// a timer later in the jobs list and deletes that job pointer
// then this will crash when it tries to run that job.

View File

@ -69,6 +69,14 @@ public:
*/
void removeTimer(IJob*);
//! Install a one-shot timer
/*!
Installs a one-shot timer for \c timeout seconds and returns the
id of the timer (which will be passed to the receiver's
\c onTimerExpired()).
*/
UInt32 addOneShotTimer(double timeout);
//! Set window
/*!
Set the window (created by the subclass). This performs some
@ -216,7 +224,7 @@ private:
// a timer priority queue element
class CTimer {
public:
CTimer(IJob* job, double timeout);
CTimer(IJob* job, double startTime, double resetTime);
~CTimer();
// manipulators
@ -242,6 +250,7 @@ private:
IJob* m_job;
double m_timeout;
double m_time;
double m_startTime;
};
private:
@ -278,6 +287,7 @@ private:
CTimerPriorityQueue m_timers;
CStopwatch m_time;
CMutex m_timersMutex;
CTimer* m_oneShotTimer;
// pointer to (singleton) screen. this is only needed by
// ioErrorHandler().

View File

@ -263,6 +263,12 @@ CXWindowsSecondaryScreen::onEvent(CEvent* event)
}
}
void
CXWindowsSecondaryScreen::onOneShotTimerExpired(UInt32)
{
// ignore
}
SInt32
CXWindowsSecondaryScreen::getJumpZoneSize() const
{

View File

@ -51,6 +51,7 @@ public:
virtual void onScreensaver(bool activated);
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual void onOneShotTimerExpired(UInt32 id);
virtual SInt32 getJumpZoneSize() const;
protected:

View File

@ -624,6 +624,9 @@ CConfig::getOptionName(OptionID id)
if (id == kOptionHeartbeat) {
return "heartbeat";
}
if (id == kOptionScreenSwitchDelay) {
return "switchDelay";
}
return NULL;
}
@ -659,7 +662,8 @@ CConfig::getOptionValue(OptionID id, OptionValue value)
return "none";
}
}
if (id == kOptionHeartbeat) {
if (id == kOptionHeartbeat ||
id == kOptionScreenSwitchDelay) {
return CStringUtil::print("%d", value);
}
@ -767,6 +771,9 @@ CConfig::readSectionOptions(std::istream& s)
else if (name == "heartbeat") {
addOption("", kOptionHeartbeat, parseInt(value));
}
else if (name == "switchDelay") {
addOption("", kOptionScreenSwitchDelay, parseInt(value));
}
else {
throw XConfigRead("unknown argument");
}

View File

@ -63,6 +63,12 @@ CPrimaryClient::reconfigure(UInt32 activeSides)
m_screen->reconfigure(activeSides);
}
UInt32
CPrimaryClient::addOneShotTimer(double timeout)
{
return m_screen->addOneShotTimer(timeout);
}
void
CPrimaryClient::getClipboard(ClipboardID id, CString& data) const
{

View File

@ -59,6 +59,13 @@ public:
*/
void reconfigure(UInt32 activeSides);
//! Install a one-shot timer
/*!
Installs a one-shot timer for \c timeout seconds and returns the
id of the timer (which will be passed to \c onTimerExpired()).
*/
UInt32 addOneShotTimer(double timeout);
//@}
//! @name accessors
//@{

View File

@ -59,7 +59,9 @@ CServer::CServer(const CString& serverName) :
m_seqNum(0),
m_activeSaver(NULL),
m_httpServer(NULL),
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests)
m_httpAvailable(&m_mutex, s_httpMaxSimultaneousRequests),
m_switchWaitDelay(0.0),
m_switchWaitScreen(NULL)
{
// do nothing
}
@ -220,13 +222,18 @@ CServer::setConfig(const CConfig& config)
// process global options
const CConfig::CScreenOptions* options = m_config.getOptions("");
if (options != NULL && options->size() > 0) {
/*
for (CConfig::CScreenOptions::const_iterator index = options->begin();
index != options->end(); ++index) {
const OptionID id = index->first;
const OptionValue value = index->second;
if (id == kOptionScreenSwitchDelay) {
m_switchWaitDelay = 1.0e-3 * static_cast<double>(value);
if (m_switchWaitDelay < 0.0) {
m_switchWaitDelay = 0.0;
}
clearSwitchWait();
}
}
*/
}
// tell primary screen about reconfiguration
@ -496,6 +503,36 @@ CServer::onScreensaver(bool activated)
}
}
void
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();
return;
}
// ignore if mouse is locked to screen
if (isLockedToScreenNoLock()) {
clearSwitchWait();
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;
}
}
void
CServer::onKeyDown(KeyID id, KeyModifierMask mask)
{
@ -582,11 +619,6 @@ CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
assert(m_primaryClient != NULL);
assert(m_active == m_primaryClient);
// ignore if mouse is locked to screen
if (isLockedToScreenNoLock()) {
return false;
}
// get screen shape
SInt32 ax, ay, aw, ah;
m_active->getShape(ax, ay, aw, ah);
@ -616,6 +648,7 @@ CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
}
else {
// still on local screen
clearSwitchWait();
return false;
}
@ -623,6 +656,27 @@ CServer::onMouseMovePrimaryNoLock(SInt32 x, SInt32 y)
// then ignore the move.
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;
}
@ -667,72 +721,110 @@ 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;
// keep compiler quiet about unset variable
dir = kLeft;
}
// switch screens if the mouse is outside the screen and not
// locked to the screen
IClient* newScreen = NULL;
if (!isLockedToScreenNoLock()) {
// find direction of neighbor
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 {
newScreen = m_active;
// keep compiler quiet about unset variable
dir = kLeft;
}
// get neighbor if we should switch
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 "leave \"%s\" on %s", m_active->getName().c_str(), CConfig::dirName(dir)));
// get new position or clamp to current screen
newScreen = getNeighbor(m_active, dir, m_x, m_y);
if (newScreen == NULL) {
LOG((CLOG_DEBUG1 "no neighbor; clamping"));
if (m_x < ax) {
m_x = ax;
}
else if (m_x > ax + aw - 1) {
m_x = ax + aw - 1;
}
if (m_y < ay) {
m_y = ay;
}
else if (m_y > ay + ah - 1) {
m_y = ay + ah - 1;
}
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 {
// clamp to edge when locked
// 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) {
if (m_x < ax) {
m_x = ax;
LOG((CLOG_DEBUG1 "clamp to left of \"%s\"", m_active->getName().c_str()));
LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", m_active->getName().c_str()));
}
else if (m_x > ax + aw - 1) {
m_x = ax + aw - 1;
LOG((CLOG_DEBUG1 "clamp to right of \"%s\"", m_active->getName().c_str()));
LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", m_active->getName().c_str()));
}
if (m_y < ay) {
m_y = ay;
LOG((CLOG_DEBUG1 "clamp to top of \"%s\"", m_active->getName().c_str()));
LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", m_active->getName().c_str()));
}
else if (m_y > ay + ah - 1) {
m_y = ay + ah - 1;
LOG((CLOG_DEBUG1 "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
@ -801,6 +893,9 @@ 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();
// record new position
m_x = x;
m_y = y;
@ -1099,6 +1194,11 @@ CServer::closeClients(const CConfig& config)
// close that client
assert(index2->second != m_primaryClient);
index2->second->close();
// don't switch to it if we planned to
if (index2->second == m_switchWaitScreen) {
clearSwitchWait();
}
}
else {
++index;
@ -1789,6 +1889,9 @@ CServer::removeConnection(const CString& name)
// record new position (center of primary screen)
m_primaryClient->getCursorCenter(m_x, m_y);
// stop waiting to switch if we were
clearSwitchWait();
// 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

@ -141,6 +141,7 @@ public:
// IPrimaryScreenReceiver overrides
virtual void onScreensaver(bool activated);
virtual void onOneShotTimerExpired(UInt32 id);
virtual void onKeyDown(KeyID, KeyModifierMask);
virtual void onKeyUp(KeyID, KeyModifierMask);
virtual void onKeyRepeat(KeyID, KeyModifierMask, SInt32 count);
@ -197,6 +198,9 @@ private:
IClient* getNeighbor(IClient*, EDirection,
SInt32& x, SInt32& y) const;
// reset switch wait state
void clearSwitchWait();
// send screen options to \c client
void sendOptions(IClient* client) const;
@ -320,6 +324,12 @@ private:
CHTTPServer* m_httpServer;
CCondVar<SInt32> m_httpAvailable;
static const SInt32 s_httpMaxSimultaneousRequests;
double m_switchWaitDelay;
EDirection m_switchWaitDir;
UInt32 m_switchWaitTimer;
IClient* m_switchWaitScreen;
SInt32 m_switchWaitX, m_switchWaitY;
};
#endif

View File

@ -129,6 +129,13 @@ public:
*/
virtual void setOptions(const COptionsList& options) = 0;
//! Install a one-shot timer
/*!
Installs a one-shot timer for \c timeout seconds and returns the
id of the timer.
*/
virtual UInt32 addOneShotTimer(double timeout) = 0;
//@}
//! @name accessors
//@{

View File

@ -34,6 +34,12 @@ public:
*/
virtual void onScreensaver(bool activated) = 0;
//! Notify of one-shot timer expiration
/*!
Called when a one-shot timer expires.
*/
virtual void onOneShotTimerExpired(UInt32 id) = 0;
// call to notify of events. onMouseMovePrimary() returns
// true iff the mouse enters a jump zone and jumps.
//! Notify of key press

View File

@ -57,6 +57,12 @@ public:
*/
virtual bool onEvent(CEvent* event) = 0;
//! Notify of one-shot timer expiration
/*!
Called when a one-shot timer expires.
*/
virtual void onOneShotTimerExpired(UInt32 id) = 0;
//@}
//! @name accessors
//@{

View File

@ -51,6 +51,7 @@ static const OptionID kOptionModifierMapForAlt = OPTION_CODE("MMFA");
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");
//@}
#undef OPTION_CODE