Merge branch 'exittimer' of https://github.com/CendioOssman/tigervnc
diff --git a/common/network/Socket.h b/common/network/Socket.h
index c7d06c3..d38feba 100644
--- a/common/network/Socket.h
+++ b/common/network/Socket.h
@@ -144,13 +144,6 @@
     //   This is only necessary if the Socket has been put in non-blocking
     //   mode and needs this callback to flush the buffer.
     virtual void processSocketWriteEvent(network::Socket* sock) = 0;
-
-    // checkTimeouts() allows the server to check socket timeouts, etc.  The
-    //   return value is the number of milliseconds to wait before
-    //   checkTimeouts() should be called again.  If this number is zero then
-    //   there is no timeout and checkTimeouts() should be called the next time
-    //   an event occurs.
-    virtual int checkTimeouts() = 0;
   };
 
 }
diff --git a/common/rfb/SDesktop.h b/common/rfb/SDesktop.h
index 6118246..0060aa2 100644
--- a/common/rfb/SDesktop.h
+++ b/common/rfb/SDesktop.h
@@ -75,6 +75,12 @@
     virtual void queryConnection(network::Socket* sock,
                                  const char* userName) = 0;
 
+    // terminate() is called by the server when it wishes to terminate
+    // itself, e.g. because it was configured to terminate when no one is
+    // using it.
+
+    virtual void terminate() = 0;
+
     // setScreenLayout() requests to reconfigure the framebuffer and/or
     // the layout of screens.
     virtual unsigned int setScreenLayout(int __unused_attr fb_width,
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 7b261e7..8758362 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -51,15 +51,23 @@
     fenceDataLen(0), fenceData(NULL), congestionTimer(this),
     losslessTimer(this), server(server_), updates(false),
     updateRenderedCursor(false), removeRenderedCursor(false),
-    continuousUpdates(false), encodeManager(this), pointerEventTime(0),
-    clientHasCursor(false)
+    continuousUpdates(false), encodeManager(this), idleTimer(this),
+    pointerEventTime(0), clientHasCursor(false)
 {
   setStreams(&sock->inStream(), &sock->outStream());
   peerEndpoint.buf = sock->getPeerEndpoint();
 
   // Configure the socket
   setSocketTimeouts();
-  lastEventTime = time(0);
+
+  // Kick off the idle timer
+  if (rfb::Server::idleTimeout) {
+    // minimum of 15 seconds while authenticating
+    if (rfb::Server::idleTimeout < 15)
+      idleTimer.start(secsToMillis(15));
+    else
+      idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
+  }
 }
 
 
@@ -312,36 +320,6 @@
 }
 
 
-int VNCSConnectionST::checkIdleTimeout()
-{
-  int idleTimeout = rfb::Server::idleTimeout;
-  if (idleTimeout == 0) return 0;
-  if (state() != RFBSTATE_NORMAL && idleTimeout < 15)
-    idleTimeout = 15; // minimum of 15 seconds while authenticating
-  time_t now = time(0);
-  if (now < lastEventTime) {
-    // Someone must have set the time backwards.  Set lastEventTime so that the
-    // idleTimeout will count from now.
-    vlog.info("Time has gone backwards - resetting idle timeout");
-    lastEventTime = now;
-  }
-  int timeLeft = lastEventTime + idleTimeout - now;
-  if (timeLeft < -60) {
-    // Our callback is over a minute late - someone must have set the time
-    // forwards.  Set lastEventTime so that the idleTimeout will count from
-    // now.
-    vlog.info("Time has gone forwards - resetting idle timeout");
-    lastEventTime = now;
-    return secsToMillis(idleTimeout);
-  }
-  if (timeLeft <= 0) {
-    close("Idle timeout");
-    return 0;
-  }
-  return secsToMillis(timeLeft);
-}
-
-
 bool VNCSConnectionST::getComparerState()
 {
   // We interpret a low compression level as an indication that the client
@@ -412,7 +390,8 @@
 
 void VNCSConnectionST::authSuccess()
 {
-  lastEventTime = time(0);
+  if (rfb::Server::idleTimeout)
+    idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
 
   // - Set the connection parameters appropriately
   cp.width = server->getPixelBuffer()->width();
@@ -438,7 +417,8 @@
 
 void VNCSConnectionST::clientInit(bool shared)
 {
-  lastEventTime = time(0);
+  if (rfb::Server::idleTimeout)
+    idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
   if (rfb::Server::alwaysShared || reverseConnection) shared = true;
   if (!accessCheck(AccessNonShared)) shared = true;
   if (rfb::Server::neverShared) shared = false;
@@ -457,7 +437,9 @@
 
 void VNCSConnectionST::pointerEvent(const Point& pos, int buttonMask)
 {
-  pointerEventTime = lastEventTime = time(0);
+  if (rfb::Server::idleTimeout)
+    idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
+  pointerEventTime = time(0);
   if (!accessCheck(AccessPtrEvents)) return;
   if (!rfb::Server::acceptPointerEvents) return;
   pointerEventPos = pos;
@@ -489,7 +471,8 @@
 void VNCSConnectionST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down) {
   rdr::U32 lookup;
 
-  lastEventTime = time(0);
+  if (rfb::Server::idleTimeout)
+    idleTimer.start(secsToMillis(rfb::Server::idleTimeout));
   if (!accessCheck(AccessKeyEvents)) return;
   if (!rfb::Server::acceptKeyEvents) return;
 
@@ -765,6 +748,9 @@
     close(e.str());
   }
 
+  if (t == &idleTimer)
+    close("Idle timeout");
+
   return false;
 }
 
@@ -1106,7 +1092,6 @@
 void VNCSConnectionST::setSocketTimeouts()
 {
   int timeoutms = rfb::Server::clientWaitTimeMillis;
-  soonestTimeout(&timeoutms, secsToMillis(rfb::Server::idleTimeout));
   if (timeoutms == 0)
     timeoutms = -1;
   sock->inStream().setTimeout(timeoutms);
diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h
index 1b7d59a..c992d14 100644
--- a/common/rfb/VNCSConnectionST.h
+++ b/common/rfb/VNCSConnectionST.h
@@ -77,11 +77,6 @@
     void setLEDStateOrClose(unsigned int state);
     void approveConnectionOrClose(bool accept, const char* reason);
 
-    // checkIdleTimeout() returns the number of milliseconds left until the
-    // idle timeout expires.  If it has expired, the connection is closed and
-    // zero is returned.  Zero is also returned if there is no idle timeout.
-    int checkIdleTimeout();
-
     // The following methods never throw exceptions
 
     // getComparerState() returns if this client would like the framebuffer
@@ -182,7 +177,8 @@
 
     std::map<rdr::U32, rdr::U32> pressedKeys;
 
-    time_t lastEventTime;
+    Timer idleTimer;
+
     time_t pointerEventTime;
     Point pointerEventPos;
     bool clientHasCursor;
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index 038da3d..40580b1 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -81,10 +81,16 @@
     cursor(new Cursor(0, 0, Point(), NULL)),
     renderedCursorInvalid(false),
     keyRemapper(&KeyRemapper::defInstance),
-    lastConnectionTime(0), frameTimer(this)
+    idleTimer(this), disconnectTimer(this), connectTimer(this),
+    frameTimer(this)
 {
-  lastUserInputTime = lastDisconnectTime = time(0);
   slog.debug("creating single-threaded server %s", name.buf);
+
+  // FIXME: Do we really want to kick off these right away?
+  if (rfb::Server::maxIdleTime)
+    idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
+  if (rfb::Server::maxDisconnectionTime)
+    disconnectTimer.start(secsToMillis(rfb::Server::maxDisconnectionTime));
 }
 
 VNCServerST::~VNCServerST()
@@ -144,9 +150,10 @@
   name.buf = sock->getPeerEndpoint();
   connectionsLog.status("accepted: %s", name.buf);
 
-  if (clients.empty()) {
-    lastConnectionTime = time(0);
-  }
+  // Adjust the exit timers
+  if (rfb::Server::maxConnectionTime && clients.empty())
+    connectTimer.start(secsToMillis(rfb::Server::maxConnectionTime));
+  disconnectTimer.stop();
 
   VNCSConnectionST* client = new VNCSConnectionST(this, sock, outgoing);
   clients.push_front(client);
@@ -164,8 +171,10 @@
       if (pointerClient == *ci)
         pointerClient = NULL;
 
-      if ((*ci)->authenticated())
-        lastDisconnectTime = time(0);
+      // Adjust the exit timers
+      connectTimer.stop();
+      if (rfb::Server::maxDisconnectionTime && clients.empty())
+        disconnectTimer.start(secsToMillis(rfb::Server::maxDisconnectionTime));
 
       // - Delete the per-Socket resources
       delete *ci;
@@ -215,89 +224,6 @@
   throw rdr::Exception("invalid Socket in VNCServerST");
 }
 
-int VNCServerST::checkTimeouts()
-{
-  int timeout = 0;
-  std::list<VNCSConnectionST*>::iterator ci, ci_next;
-
-  soonestTimeout(&timeout, Timer::checkTimeouts());
-
-  for (ci=clients.begin();ci!=clients.end();ci=ci_next) {
-    ci_next = ci; ci_next++;
-    soonestTimeout(&timeout, (*ci)->checkIdleTimeout());
-  }
-
-  int timeLeft;
-  time_t now = time(0);
-
-  // Check MaxDisconnectionTime 
-  if (rfb::Server::maxDisconnectionTime && clients.empty()) {
-    if (now < lastDisconnectTime) {
-      // Someone must have set the time backwards. 
-      slog.info("Time has gone backwards - resetting lastDisconnectTime");
-      lastDisconnectTime = now;
-    }
-    timeLeft = lastDisconnectTime + rfb::Server::maxDisconnectionTime - now;
-    if (timeLeft < -60) {
-      // Someone must have set the time forwards.
-      slog.info("Time has gone forwards - resetting lastDisconnectTime");
-      lastDisconnectTime = now;
-      timeLeft = rfb::Server::maxDisconnectionTime;
-    }
-    if (timeLeft <= 0) { 
-      slog.info("MaxDisconnectionTime reached, exiting");
-      exit(0);
-    }
-    soonestTimeout(&timeout, timeLeft * 1000);
-  }
-
-  // Check MaxConnectionTime 
-  if (rfb::Server::maxConnectionTime && lastConnectionTime && !clients.empty()) {
-    if (now < lastConnectionTime) {
-      // Someone must have set the time backwards. 
-      slog.info("Time has gone backwards - resetting lastConnectionTime");
-      lastConnectionTime = now;
-    }
-    timeLeft = lastConnectionTime + rfb::Server::maxConnectionTime - now;
-    if (timeLeft < -60) {
-      // Someone must have set the time forwards.
-      slog.info("Time has gone forwards - resetting lastConnectionTime");
-      lastConnectionTime = now;
-      timeLeft = rfb::Server::maxConnectionTime;
-    }
-    if (timeLeft <= 0) {
-      slog.info("MaxConnectionTime reached, exiting");
-      exit(0);
-    }
-    soonestTimeout(&timeout, timeLeft * 1000);
-  }
-
-  
-  // Check MaxIdleTime 
-  if (rfb::Server::maxIdleTime) {
-    if (now < lastUserInputTime) {
-      // Someone must have set the time backwards. 
-      slog.info("Time has gone backwards - resetting lastUserInputTime");
-      lastUserInputTime = now;
-    }
-    timeLeft = lastUserInputTime + rfb::Server::maxIdleTime - now;
-    if (timeLeft < -60) {
-      // Someone must have set the time forwards.
-      slog.info("Time has gone forwards - resetting lastUserInputTime");
-      lastUserInputTime = now;
-      timeLeft = rfb::Server::maxIdleTime;
-    }
-    if (timeLeft <= 0) {
-      slog.info("MaxIdleTime reached, exiting");
-      exit(0);
-    }
-    soonestTimeout(&timeout, timeLeft * 1000);
-  }
-  
-  return timeout;
-}
-
-
 // VNCServer methods
 
 void VNCServerST::blockUpdates()
@@ -495,7 +421,8 @@
 
 void VNCServerST::keyEvent(rdr::U32 keysym, rdr::U32 keycode, bool down)
 {
-  lastUserInputTime = time(0);
+  if (rfb::Server::maxIdleTime)
+    idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
 
   // Remap the key if required
   if (keyRemapper) {
@@ -513,7 +440,8 @@
 void VNCServerST::pointerEvent(VNCSConnectionST* client,
                                const Point& pos, int buttonMask)
 {
-  lastUserInputTime = time(0);
+  if (rfb::Server::maxIdleTime)
+    idleTimer.start(secsToMillis(rfb::Server::maxIdleTime));
 
   // Let one client own the cursor whilst buttons are pressed in order
   // to provide a bit more sane user experience
@@ -628,6 +556,15 @@
     }
 
     return true;
+  } else if (t == &idleTimer) {
+    slog.info("MaxIdleTime reached, exiting");
+    desktop->terminate();
+  } else if (t == &disconnectTimer) {
+    slog.info("MaxDisconnectionTime reached, exiting");
+    desktop->terminate();
+  } else if (t == &connectTimer) {
+    slog.info("MaxConnectionTime reached, exiting");
+    desktop->terminate();
   }
 
   return false;
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index 545b9a4..43a3bb9 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -76,12 +76,6 @@
     //   Flush pending data from the Socket on to the network.
     virtual void processSocketWriteEvent(network::Socket* sock);
 
-    // checkTimeouts
-    //   Returns the number of milliseconds left until the next idle timeout
-    //   expires.  If any have already expired, the corresponding connections
-    //   are closed.  Zero is returned if there is no idle timeout.
-    virtual int checkTimeouts();
-
 
     // Methods overridden from VNCServer
 
@@ -198,9 +192,9 @@
 
     KeyRemapper* keyRemapper;
 
-    time_t lastUserInputTime;
-    time_t lastDisconnectTime;
-    time_t lastConnectionTime;
+    Timer idleTimer;
+    Timer disconnectTimer;
+    Timer connectTimer;
 
     Timer frameTimer;
   };
diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx
index 5f71ccb..1fdc9e2 100644
--- a/unix/x0vncserver/XDesktop.cxx
+++ b/unix/x0vncserver/XDesktop.cxx
@@ -19,6 +19,8 @@
  */
 
 #include <assert.h>
+#include <signal.h>
+#include <unistd.h>
 
 #include <rfb/LogWriter.h>
 
@@ -273,6 +275,10 @@
   pb = 0;
 }
 
+void XDesktop::terminate() {
+  kill(getpid(), SIGTERM);
+}
+
 bool XDesktop::isRunning() {
   return running;
 }
diff --git a/unix/x0vncserver/XDesktop.h b/unix/x0vncserver/XDesktop.h
index 7d06061..3e85aac 100644
--- a/unix/x0vncserver/XDesktop.h
+++ b/unix/x0vncserver/XDesktop.h
@@ -49,6 +49,7 @@
   // -=- SDesktop interface
   virtual void start(rfb::VNCServer* vs);
   virtual void stop();
+  virtual void terminate();
   bool isRunning();
   virtual void queryConnection(network::Socket* sock,
                                const char* userName);
diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx
index c77870d..cf2c35a 100644
--- a/unix/x0vncserver/x0vncserver.cxx
+++ b/unix/x0vncserver/x0vncserver.cxx
@@ -322,7 +322,7 @@
         }
       }
 
-      soonestTimeout(&wait_ms, server.checkTimeouts());
+      soonestTimeout(&wait_ms, Timer::checkTimeouts());
 
       tv.tv_sec = wait_ms / 1000;
       tv.tv_usec = (wait_ms % 1000) * 1000;
@@ -357,7 +357,7 @@
         }
       }
 
-      server.checkTimeouts();
+      Timer::checkTimeouts();
 
       // Client list could have been changed.
       server.getSockets(&sockets);
diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc
index e61472b..d8b3a4d 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.cc
+++ b/unix/xserver/hw/vnc/XserverDesktop.cc
@@ -22,6 +22,7 @@
 //
 
 #include <assert.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <strings.h>
@@ -367,7 +368,7 @@
     }
 
     // Trigger timers and check when the next will expire
-    int nextTimeout = server->checkTimeouts();
+    int nextTimeout = Timer::checkTimeouts();
     if (nextTimeout > 0 && (*timeout == -1 || nextTimeout < *timeout))
       *timeout = nextTimeout;
   } catch (rdr::Exception& e) {
@@ -423,6 +424,11 @@
 // SDesktop callbacks
 
 
+void XserverDesktop::terminate()
+{
+  kill(getpid(), SIGTERM);
+}
+
 void XserverDesktop::pointerEvent(const Point& pos, int buttonMask)
 {
   vncPointerMove(pos.x + vncGetScreenX(screenIndex),
diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h
index ff36b3b..1253935 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.h
+++ b/unix/xserver/hw/vnc/XserverDesktop.h
@@ -87,6 +87,7 @@
   // rfb::SDesktop callbacks
   virtual void start(rfb::VNCServer* vs);
   virtual void stop();
+  virtual void terminate();
   virtual void queryConnection(network::Socket* sock,
                                const char* userName);
   virtual void pointerEvent(const rfb::Point& pos, int buttonMask);
diff --git a/win/rfb_win32/SDisplay.cxx b/win/rfb_win32/SDisplay.cxx
index afb72ad..2cedc4a 100644
--- a/win/rfb_win32/SDisplay.cxx
+++ b/win/rfb_win32/SDisplay.cxx
@@ -71,6 +71,7 @@
     statusLocation(0), queryConnectionHandler(0), ledState(0)
 {
   updateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
+  terminateEvent.h = CreateEvent(0, TRUE, FALSE, 0);
 }
 
 SDisplay::~SDisplay()
@@ -140,6 +141,11 @@
   if (statusLocation) *statusLocation = false;
 }
 
+void SDisplay::terminate()
+{
+  SetEvent(terminateEvent);
+}
+
 
 void SDisplay::queryConnection(network::Socket* sock,
                                const char* userName)
diff --git a/win/rfb_win32/SDisplay.h b/win/rfb_win32/SDisplay.h
index 76ddf50..6dbfabb 100644
--- a/win/rfb_win32/SDisplay.h
+++ b/win/rfb_win32/SDisplay.h
@@ -72,6 +72,7 @@
 
       virtual void start(VNCServer* vs);
       virtual void stop();
+      virtual void terminate();
       virtual void queryConnection(network::Socket* sock,
                                    const char* userName);
       virtual void pointerEvent(const Point& pos, int buttonmask);
@@ -89,6 +90,7 @@
       // -=- EventHandler interface
 
       HANDLE getUpdateEvent() {return updateEvent;}
+      HANDLE getTerminateEvent() {return terminateEvent;}
       virtual void processEvent(HANDLE event);
 
       // -=- Notification of whether or not SDisplay is started
@@ -161,6 +163,8 @@
 
       // -=- Event signalled to trigger an update to be flushed
       Handle updateEvent;
+      // -=- Event signalled to terminate the server
+      Handle terminateEvent;
 
       // -=- Where to write the active/inactive indicator to
       bool* statusLocation;
diff --git a/win/rfb_win32/SocketManager.cxx b/win/rfb_win32/SocketManager.cxx
index aa469e5..0092d94 100644
--- a/win/rfb_win32/SocketManager.cxx
+++ b/win/rfb_win32/SocketManager.cxx
@@ -21,6 +21,7 @@
 #include <winsock2.h>
 #include <list>
 #include <rfb/LogWriter.h>
+#include <rfb/Timer.h>
 #include <rfb_win32/SocketManager.h>
 
 using namespace rfb;
@@ -161,7 +162,7 @@
 
   std::map<HANDLE,ListenInfo>::iterator i;
   for (i=listeners.begin(); i!=listeners.end(); i++)
-    soonestTimeout(&timeout, i->second.server->checkTimeouts());
+    soonestTimeout(&timeout, Timer::checkTimeouts());
 
   std::list<network::Socket*> shutdownSocks;
   std::map<HANDLE,ConnInfo>::iterator j, j_next;
diff --git a/win/winvnc/VNCServerWin32.cxx b/win/winvnc/VNCServerWin32.cxx
index 03b1bca..e001449 100644
--- a/win/winvnc/VNCServerWin32.cxx
+++ b/win/winvnc/VNCServerWin32.cxx
@@ -76,6 +76,7 @@
 
   // Register the desktop's event to be handled
   sockMgr.addEvent(desktop.getUpdateEvent(), &desktop);
+  sockMgr.addEvent(desktop.getTerminateEvent(), this);
 
   // Register the queued command event to be handled
   sockMgr.addEvent(commandEvent, this);
@@ -335,7 +336,8 @@
       command = NoCommand;
       commandSig->signal();
     }
-  } else if (event_ == sessionEvent.h) {
+  } else if ((event_ == sessionEvent.h) ||
+             (event_ == desktop.getTerminateEvent())) {
     stop();
   }
 }