Merge branch 'alr' of https://github.com/CendioOssman/tigervnc
diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx
index a2f7a25..4d36d9f 100644
--- a/common/rfb/Congestion.cxx
+++ b/common/rfb/Congestion.cxx
@@ -1,4 +1,4 @@
-/* Copyright 2009-2015 Pierre Ossman for Cendio AB
+/* Copyright 2009-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -73,7 +73,7 @@
 Congestion::Congestion() :
     lastPosition(0), extraBuffer(0),
     baseRTT(-1), congWindow(INITIAL_WINDOW), inSlowStart(true),
-    measurements(0), minRTT(-1), minCongestedRTT(-1)
+    safeBaseRTT(-1), measurements(0), minRTT(-1), minCongestedRTT(-1)
 {
   gettimeofday(&lastUpdate, NULL);
   gettimeofday(&lastSent, NULL);
@@ -170,7 +170,7 @@
 
   // Try to estimate wire latency by tracking lowest seen latency
   if (rtt < baseRTT)
-    baseRTT = rtt;
+    safeBaseRTT = baseRTT = rtt;
 
   // Pings sent before the last adjustment aren't interesting as they
   // aren't a measurement of the current congestion window
@@ -284,6 +284,15 @@
   }
 }
 
+size_t Congestion::getBandwidth()
+{
+  // No measurements yet? Guess RTT of 60 ms
+  if (safeBaseRTT == (unsigned)-1)
+    return congWindow * 1000 / 60;
+
+  return congWindow * 1000 / safeBaseRTT;
+}
+
 void Congestion::debugTrace(const char* filename, int fd)
 {
 #ifdef CONGESTION_TRACE
diff --git a/common/rfb/Congestion.h b/common/rfb/Congestion.h
index fd57c22..d293512 100644
--- a/common/rfb/Congestion.h
+++ b/common/rfb/Congestion.h
@@ -1,4 +1,4 @@
-/* Copyright 2009-2015 Pierre Ossman for Cendio AB
+/* Copyright 2009-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -47,6 +47,10 @@
     // longer be congested.
     int getUncongestedETA();
 
+    // getBandwidth() returns the current bandwidth estimation in bytes
+    // per second.
+    size_t getBandwidth();
+
     // debugTrace() writes the current congestion window, as well as the
     // congestion window of the underlying TCP layer, to the specified
     // file
@@ -68,6 +72,8 @@
     unsigned congWindow;
     bool inSlowStart;
 
+    unsigned safeBaseRTT;
+
     struct RTTInfo {
       struct timeval tv;
       unsigned pos;
diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx
index 0cd5206..0ce611e 100644
--- a/common/rfb/EncodeManager.cxx
+++ b/common/rfb/EncodeManager.cxx
@@ -1,6 +1,6 @@
 /* Copyright (C) 2000-2003 Constantin Kaplinsky.  All Rights Reserved.
  * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,9 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
  * USA.
  */
+
+#include <stdlib.h>
+
 #include <rfb/EncodeManager.h>
 #include <rfb/Encoder.h>
 #include <rfb/Palette.h>
@@ -245,52 +248,79 @@
   }
 }
 
+bool EncodeManager::needsLosslessRefresh(const Region& req)
+{
+  return !lossyRegion.intersect(req).is_empty();
+}
+
+void EncodeManager::pruneLosslessRefresh(const Region& limits)
+{
+  lossyRegion.assign_intersect(limits);
+}
+
 void EncodeManager::writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
                                 const RenderedCursor* renderedCursor)
 {
+    doUpdate(true, ui.changed, ui.copied, ui.copy_delta, pb, renderedCursor);
+}
+
+void EncodeManager::writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
+                                         const RenderedCursor* renderedCursor,
+                                         size_t maxUpdateSize)
+{
+    doUpdate(false, getLosslessRefresh(req, maxUpdateSize),
+             Region(), Point(), pb, renderedCursor);
+}
+
+void EncodeManager::doUpdate(bool allowLossy, const Region& changed_,
+                             const Region& copied, const Point& copyDelta,
+                             const PixelBuffer* pb,
+                             const RenderedCursor* renderedCursor)
+{
     int nRects;
-    Region changed;
+    Region changed, cursorRegion;
 
     updates++;
 
-    prepareEncoders();
+    prepareEncoders(allowLossy);
+
+    changed = changed_;
+
+    /*
+     * We need to render the cursor seperately as it has its own
+     * magical pixel buffer, so split it out from the changed region.
+     */
+    if (renderedCursor != NULL) {
+      cursorRegion = changed.intersect(renderedCursor->getEffectiveRect());
+      changed.assign_subtract(renderedCursor->getEffectiveRect());
+    }
 
     if (conn->cp.supportsLastRect)
       nRects = 0xFFFF;
     else {
-      nRects = ui.copied.numRects();
-      nRects += computeNumRects(ui.changed);
-
-      if (renderedCursor != NULL)
-        nRects += 1;
+      nRects = copied.numRects();
+      nRects += computeNumRects(changed);
+      nRects += computeNumRects(cursorRegion);
     }
 
     conn->writer()->writeFramebufferUpdateStart(nRects);
 
-    writeCopyRects(ui);
+    writeCopyRects(copied, copyDelta);
 
     /*
      * We start by searching for solid rects, which are then removed
      * from the changed region.
      */
-    changed.copyFrom(ui.changed);
-
     if (conn->cp.supportsLastRect)
       writeSolidRects(&changed, pb);
 
     writeRects(changed, pb);
-
-    if (renderedCursor != NULL) {
-      Rect renderedCursorRect;
-
-      renderedCursorRect = renderedCursor->getEffectiveRect();
-      writeSubRect(renderedCursorRect, renderedCursor);
-    }
+    writeRects(cursorRegion, renderedCursor);
 
     conn->writer()->writeFramebufferUpdateEnd();
 }
 
-void EncodeManager::prepareEncoders()
+void EncodeManager::prepareEncoders(bool allowLossy)
 {
   enum EncoderClass solid, bitmap, bitmapRLE;
   enum EncoderClass indexed, indexedRLE, fullColour;
@@ -315,7 +345,7 @@
     break;
   case encodingTight:
     if (encoders[encoderTightJPEG]->isSupported() &&
-        (conn->cp.pf().bpp >= 16))
+        (conn->cp.pf().bpp >= 16) && allowLossy)
       fullColour = encoderTightJPEG;
     else
       fullColour = encoderTight;
@@ -333,7 +363,7 @@
 
   if (fullColour == encoderRaw) {
     if (encoders[encoderTightJPEG]->isSupported() &&
-        (conn->cp.pf().bpp >= 16))
+        (conn->cp.pf().bpp >= 16) && allowLossy)
       fullColour = encoderTightJPEG;
     else if (encoders[encoderZRLE]->isSupported())
       fullColour = encoderZRLE;
@@ -373,7 +403,7 @@
 
   // JPEG is the only encoder that can reduce things to grayscale
   if ((conn->cp.subsampling == subsampleGray) &&
-      encoders[encoderTightJPEG]->isSupported()) {
+      encoders[encoderTightJPEG]->isSupported() && allowLossy) {
     solid = bitmap = bitmapRLE = encoderTightJPEG;
     indexed = indexedRLE = fullColour = encoderTightJPEG;
   }
@@ -397,6 +427,52 @@
   }
 }
 
+Region EncodeManager::getLosslessRefresh(const Region& req,
+                                         size_t maxUpdateSize)
+{
+  std::vector<Rect> rects;
+  Region refresh;
+  size_t area;
+
+  // We make a conservative guess at the compression ratio at 2:1
+  maxUpdateSize *= 2;
+
+  area = 0;
+  lossyRegion.intersect(req).get_rects(&rects);
+  while (!rects.empty()) {
+    size_t idx;
+    Rect rect;
+
+    // Grab a random rect so we don't keep damaging and restoring the
+    // same rect over and over
+    idx = rand() % rects.size();
+
+    rect = rects[idx];
+
+    // Add rects until we exceed the threshold, then include as much as
+    // possible of the final rect
+    if ((area + rect.area()) > maxUpdateSize) {
+      // Use the narrowest axis to avoid getting to thin rects
+      if (rect.width() > rect.height()) {
+        int width = (maxUpdateSize - area) / rect.height();
+        rect.br.x = rect.tl.x + __rfbmax(1, width);
+      } else {
+        int height = (maxUpdateSize - area) / rect.width();
+        rect.br.y = rect.tl.y + __rfbmax(1, height);
+      }
+      refresh.assign_union(Region(rect));
+      break;
+    }
+
+    area += rect.area();
+    refresh.assign_union(Region(rect));
+
+    rects.erase(rects.begin() + idx);
+  }
+
+  return refresh;
+}
+
 int EncodeManager::computeNumRects(const Region& changed)
 {
   int numRects;
@@ -449,6 +525,11 @@
   encoder = encoders[klass];
   conn->writer()->startRect(rect, encoder->encoding);
 
+  if (encoder->flags & EncoderLossy)
+    lossyRegion.assign_union(Region(rect));
+  else
+    lossyRegion.assign_subtract(Region(rect));
+
   return encoder;
 }
 
@@ -465,14 +546,16 @@
   stats[klass][activeType].bytes += length;
 }
 
-void EncodeManager::writeCopyRects(const UpdateInfo& ui)
+void EncodeManager::writeCopyRects(const Region& copied, const Point& delta)
 {
   std::vector<Rect> rects;
   std::vector<Rect>::const_iterator rect;
 
+  Region lossyCopy;
+
   beforeLength = conn->getOutStream()->length();
 
-  ui.copied.get_rects(&rects, ui.copy_delta.x <= 0, ui.copy_delta.y <= 0);
+  copied.get_rects(&rects, delta.x <= 0, delta.y <= 0);
   for (rect = rects.begin(); rect != rects.end(); ++rect) {
     int equiv;
 
@@ -481,11 +564,16 @@
     equiv = 12 + rect->area() * conn->cp.pf().bpp/8;
     copyStats.equivalent += equiv;
 
-    conn->writer()->writeCopyRect(*rect, rect->tl.x - ui.copy_delta.x,
-                                   rect->tl.y - ui.copy_delta.y);
+    conn->writer()->writeCopyRect(*rect, rect->tl.x - delta.x,
+                                   rect->tl.y - delta.y);
   }
 
   copyStats.bytes += conn->getOutStream()->length() - beforeLength;
+
+  lossyCopy = lossyRegion;
+  lossyCopy.translate(delta);
+  lossyCopy.assign_intersect(copied);
+  lossyRegion.assign_union(lossyCopy);
 }
 
 void EncodeManager::writeSolidRects(Region *changed, const PixelBuffer* pb)
diff --git a/common/rfb/EncodeManager.h b/common/rfb/EncodeManager.h
index 4319b42..a91c544 100644
--- a/common/rfb/EncodeManager.h
+++ b/common/rfb/EncodeManager.h
@@ -1,6 +1,6 @@
 /* Copyright (C) 2000-2003 Constantin Kaplinsky.  All Rights Reserved.
  * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@
 
 #include <rdr/types.h>
 #include <rfb/PixelBuffer.h>
+#include <rfb/Region.h>
 
 namespace rfb {
   class SConnection;
@@ -31,7 +32,6 @@
   class UpdateInfo;
   class PixelBuffer;
   class RenderedCursor;
-  class Region;
   struct Rect;
 
   struct RectInfo;
@@ -46,18 +46,31 @@
     // Hack to let ConnParams calculate the client's preferred encoding
     static bool supported(int encoding);
 
+    bool needsLosslessRefresh(const Region& req);
+    void pruneLosslessRefresh(const Region& limits);
+
     void writeUpdate(const UpdateInfo& ui, const PixelBuffer* pb,
                      const RenderedCursor* renderedCursor);
 
+    void writeLosslessRefresh(const Region& req, const PixelBuffer* pb,
+                              const RenderedCursor* renderedCursor,
+                              size_t maxUpdateSize);
+
   protected:
-    void prepareEncoders();
+    void doUpdate(bool allowLossy, const Region& changed,
+                  const Region& copied, const Point& copy_delta,
+                  const PixelBuffer* pb,
+                  const RenderedCursor* renderedCursor);
+    void prepareEncoders(bool allowLossy);
+
+    Region getLosslessRefresh(const Region& req, size_t maxUpdateSize);
 
     int computeNumRects(const Region& changed);
 
     Encoder *startRect(const Rect& rect, int type);
     void endRect();
 
-    void writeCopyRects(const UpdateInfo& ui);
+    void writeCopyRects(const Region& copied, const Point& delta);
     void writeSolidRects(Region *changed, const PixelBuffer* pb);
     void findSolidRect(const Rect& rect, Region *changed, const PixelBuffer* pb);
     void writeRects(const Region& changed, const PixelBuffer* pb);
@@ -103,6 +116,8 @@
     std::vector<Encoder*> encoders;
     std::vector<int> activeEncoders;
 
+    Region lossyRegion;
+
     struct EncoderStats {
       unsigned rects;
       unsigned long long bytes;
diff --git a/common/rfb/Encoder.h b/common/rfb/Encoder.h
index a8a447e..66a10d2 100644
--- a/common/rfb/Encoder.h
+++ b/common/rfb/Encoder.h
@@ -35,6 +35,8 @@
     // Give us the raw frame buffer, and not something converted to
     // the what the client is asking for.
     EncoderUseNativePF = 1 << 0,
+    // Encoder does not encode pixels perfectly accurate
+    EncoderLossy = 1 << 1,
   };
 
   class Encoder {
diff --git a/common/rfb/Region.cxx b/common/rfb/Region.cxx
index 995f8c5..c17c5d4 100644
--- a/common/rfb/Region.cxx
+++ b/common/rfb/Region.cxx
@@ -143,10 +143,6 @@
   }
 }
 
-void rfb::Region::copyFrom(const rfb::Region& r) {
-  XUnionRegion(r.xrgn, r.xrgn, xrgn);
-}
-
 void rfb::Region::assign_intersect(const rfb::Region& r) {
   XIntersectRegion(xrgn, r.xrgn, xrgn);
 }
diff --git a/common/rfb/Region.h b/common/rfb/Region.h
index 9337556..9e53d36 100644
--- a/common/rfb/Region.h
+++ b/common/rfb/Region.h
@@ -52,7 +52,6 @@
     void setOrderedRects(const std::vector<Rect>& rects);
     void setExtentsAndOrderedRects(const ShortRect* extents, int nRects,
                                    const ShortRect* rects);
-    void copyFrom(const Region& r);
 
     void assign_intersect(const Region& r);
     void assign_union(const Region& r);
diff --git a/common/rfb/TightJPEGEncoder.cxx b/common/rfb/TightJPEGEncoder.cxx
index 7bb6126..385207f 100644
--- a/common/rfb/TightJPEGEncoder.cxx
+++ b/common/rfb/TightJPEGEncoder.cxx
@@ -64,7 +64,7 @@
 
 
 TightJPEGEncoder::TightJPEGEncoder(SConnection* conn) :
-  Encoder(conn, encodingTight, EncoderUseNativePF, -1),
+  Encoder(conn, encodingTight, (EncoderFlags)(EncoderUseNativePF | EncoderLossy), -1),
   qualityLevel(-1), fineQuality(-1), fineSubsampling(subsampleUndefined)
 {
 }
diff --git a/common/rfb/Timer.cxx b/common/rfb/Timer.cxx
index 7179cd8..fd1a87a 100644
--- a/common/rfb/Timer.cxx
+++ b/common/rfb/Timer.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2016 Pierre Ossman for Cendio AB
+ * Copyright 2016-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -94,7 +94,7 @@
 int Timer::getNextTimeout() {
   timeval now;
   gettimeofday(&now, 0);
-  int toWait = __rfbmax(1, diffTimeMillis(pending.front()->dueTime, now));
+  int toWait = __rfbmax(1, pending.front()->getRemainingMs());
   if (toWait > pending.front()->timeoutMs) {
     if (toWait - pending.front()->timeoutMs < 1000) {
       vlog.info("gettimeofday is broken...");
@@ -148,6 +148,12 @@
   return timeoutMs;
 }
 
+int Timer::getRemainingMs() {
+  timeval now;
+  gettimeofday(&now, 0);
+  return __rfbmax(0, diffTimeMillis(pending.front()->dueTime, now));
+}
+
 bool Timer::isBefore(timeval other) {
   return (dueTime.tv_sec < other.tv_sec) ||
     ((dueTime.tv_sec == other.tv_sec) &&
diff --git a/common/rfb/Timer.h b/common/rfb/Timer.h
index 78687d1..15b5d03 100644
--- a/common/rfb/Timer.h
+++ b/common/rfb/Timer.h
@@ -1,4 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright 2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -85,6 +86,11 @@
     //   Usually used with isStarted() to get the _current_ timeout.
     int getTimeoutMs();
 
+    // getRemainingMs
+    //   Determines how many milliseconds are left before the Timer
+    //   will timeout. Only valid for an active timer.
+    int getRemainingMs();
+
     // isBefore
     //   Determine whether the Timer will timeout before the specified time.
     bool isBefore(timeval other);
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index e707e49..126fb4e 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2009-2016 Pierre Ossman for Cendio AB
+ * Copyright 2009-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -223,6 +223,9 @@
           }
         }
       }
+
+      // Drop any lossy tracking that is now outside the framebuffer
+      encodeManager.pruneLosslessRefresh(Region(server->pb->getRect()));
     }
     // Just update the whole screen at the moment because we're too lazy to
     // work out what's actually changed.
@@ -962,18 +965,13 @@
 
 void VNCSConnectionST::writeDataUpdate()
 {
-  Region req;
+  Region req, pending;
   UpdateInfo ui;
   bool needNewUpdateInfo;
   const RenderedCursor *cursor;
 
   updates.enable_copyrect(cp.useCopyRect);
 
-  // See if we are allowed to send anything right now (the framebuffer
-  // might have changed in ways we haven't yet been informed of).
-  if (!server->checkUpdate())
-    return;
-
   // See what the client has requested (if anything)
   if (continuousUpdates)
     req = cuRegion.union_(requested);
@@ -983,6 +981,9 @@
   if (req.is_empty())
     return;
 
+  // Get any framebuffer changes we haven't yet been informed of
+  pending = server->getPendingRegion();
+
   // Get the lists of updates. Prior to exporting the data to the `ui' object,
   // getUpdateInfo() will normalize the `updates' object such way that its
   // `changed' and `copied' regions would not intersect.
@@ -996,7 +997,7 @@
   if (!ui.copied.is_empty() && !damagedCursorRegion.is_empty()) {
     Region bogusCopiedCursor;
 
-    bogusCopiedCursor.copyFrom(damagedCursorRegion);
+    bogusCopiedCursor = damagedCursorRegion;
     bogusCopiedCursor.translate(ui.copy_delta);
     bogusCopiedCursor.assign_intersect(server->pb->getRect());
     if (!ui.copied.intersect(bogusCopiedCursor).is_empty()) {
@@ -1015,53 +1016,78 @@
     removeRenderedCursor = false;
   }
 
-  // Return if there is nothing to send the client.
+  // If we need a full cursor update then make sure its entire region
+  // is marked as changed.
 
-  if (updates.is_empty() && !writer()->needFakeUpdate() && !updateRenderedCursor)
-    return;
+  if (updateRenderedCursor) {
+    updates.add_changed(server->getRenderedCursor()->getEffectiveRect());
+    needNewUpdateInfo = true;
+    updateRenderedCursor = false;
+  }
 
   // The `updates' object could change, make sure we have valid update info.
 
   if (needNewUpdateInfo)
     updates.getUpdateInfo(&ui, req);
 
-  // If the client needs a server-side rendered cursor, work out the cursor
-  // rectangle.  If it's empty then don't bother drawing it, but if it overlaps
-  // with the update region, we need to draw the rendered cursor regardless of
-  // whether it has changed.
+  // If there are queued updates then we cannot safely send an update
+  // without risking a partially updated screen
+
+  if (!pending.is_empty()) {
+    // However we might still be able to send a lossless refresh
+    req.assign_subtract(pending);
+    req.assign_subtract(ui.changed);
+    req.assign_subtract(ui.copied);
+
+    ui.changed.clear();
+    ui.copied.clear();
+  }
+
+  // Does the client need a server-side rendered cursor?
 
   cursor = NULL;
   if (needRenderedCursor()) {
     Rect renderedCursorRect;
 
     cursor = server->getRenderedCursor();
+    renderedCursorRect = cursor->getEffectiveRect();
 
-    renderedCursorRect
-      = cursor->getEffectiveRect().intersect(req.get_bounding_rect());
-
-    if (renderedCursorRect.is_empty()) {
-      cursor = NULL;
-    } else if (!updateRenderedCursor &&
-               ui.changed.union_(ui.copied)
-               .intersect(renderedCursorRect).is_empty()) {
-      cursor = NULL;
+    // Check that we don't try to copy over the cursor area, and
+    // if that happens we need to treat it as changed so that we can
+    // re-render it
+    if (!ui.copied.intersect(renderedCursorRect).is_empty()) {
+      ui.changed.assign_union(ui.copied.intersect(renderedCursorRect));
+      ui.copied.assign_subtract(renderedCursorRect);
     }
 
-    if (cursor) {
-      updates.subtract(renderedCursorRect);
-      updates.getUpdateInfo(&ui, req);
-    }
-
-    damagedCursorRegion.assign_union(renderedCursorRect);
-    updateRenderedCursor = false;
+    // Track where we've rendered the cursor
+    damagedCursorRegion.assign_union(ui.changed.intersect(renderedCursorRect));
   }
 
-  if (ui.is_empty() && !writer()->needFakeUpdate() && !cursor)
+  // Return if there is nothing to send the client.
+
+  if (ui.is_empty() && !writer()->needFakeUpdate() &&
+      !encodeManager.needsLosslessRefresh(req))
     return;
 
   writeRTTPing();
 
-  encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor);
+  if (!ui.is_empty())
+    encodeManager.writeUpdate(ui, server->getPixelBuffer(), cursor);
+  else {
+    size_t maxUpdateSize;
+
+    // FIXME: If continuous updates aren't used then the client might
+    //        be slower than frameRate in its requests and we could
+    //        afford a larger update size
+
+    // FIXME: Bandwidth estimation without congestion control
+    maxUpdateSize = congestion.getBandwidth() *
+                    server->msToNextUpdate() / 1000;
+
+    encodeManager.writeLosslessRefresh(req, server->getPixelBuffer(),
+                                       cursor, maxUpdateSize);
+  }
 
   writeRTTPing();
 
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index 0008dc4..95870c9 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2009-2017 Pierre Ossman for Cendio AB
+ * Copyright 2009-2018 Pierre Ossman for Cendio AB
  * 
  * This is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -105,10 +105,7 @@
   }
 
   // Stop the desktop object if active, *only* after deleting all clients!
-  if (desktopStarted) {
-    desktopStarted = false;
-    desktop->stop();
-  }
+  stopDesktop();
 
   if (comparer)
     comparer->logStats();
@@ -154,12 +151,8 @@
       delete *ci;
 
       // - Check that the desktop object is still required
-      if (authClientCount() == 0 && desktopStarted) {
-        slog.debug("no authenticated clients - stopping desktop");
-        desktopStarted = false;
-        desktop->stop();
-        stopFrameClock();
-      }
+      if (authClientCount() == 0)
+        stopDesktop();
 
       if (comparer)
         comparer->logStats();
@@ -552,6 +545,16 @@
   }
 }
 
+void VNCServerST::stopDesktop()
+{
+  if (desktopStarted) {
+    slog.debug("stopping desktop");
+    desktopStarted = false;
+    desktop->stop();
+    stopFrameClock();
+  }
+}
+
 int VNCServerST::authClientCount() {
   int count = 0;
   std::list<VNCSConnectionST*>::iterator ci;
@@ -576,6 +579,8 @@
     return;
   if (blockCounter > 0)
     return;
+  if (!desktopStarted)
+    return;
 
   // The first iteration will be just half a frame as we get a very
   // unstable update rate if we happen to be perfectly in sync with
@@ -588,6 +593,17 @@
   frameTimer.stop();
 }
 
+int VNCServerST::msToNextUpdate()
+{
+  // FIXME: If the application is updating slower than frameRate then
+  //        we could allow the clients more time here
+
+  if (!frameTimer.isStarted())
+    return 1000/rfb::Server::frameRate/2;
+  else
+    return frameTimer.getRemainingMs();
+}
+
 // writeUpdate() is called on a regular interval in order to see what
 // updates are pending and propagates them to the update tracker for
 // each client. It uses the ComparingUpdateTracker's compare() method
@@ -603,6 +619,7 @@
   std::list<VNCSConnectionST*>::iterator ci, ci_next;
 
   assert(blockCounter == 0);
+  assert(desktopStarted);
 
   comparer->getUpdateInfo(&ui, pb->getRect());
   toCheck = ui.changed.union_(ui.copied);
@@ -639,17 +656,21 @@
 // checkUpdate() is called by clients to see if it is safe to read from
 // the framebuffer at this time.
 
-bool VNCServerST::checkUpdate()
+Region VNCServerST::getPendingRegion()
 {
+  UpdateInfo ui;
+
   // Block clients as the frame buffer cannot be safely accessed
   if (blockCounter > 0)
-    return false;
+    return pb->getRect();
 
   // Block client from updating if there are pending updates
-  if (!comparer->is_empty())
-    return false;
+  if (comparer->is_empty())
+    return Region();
 
-  return true;
+  comparer->getUpdateInfo(&ui, pb->getRect());
+
+  return ui.changed.union_(ui.copied);
 }
 
 const RenderedCursor* VNCServerST::getRenderedCursor()
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index e00a1f7..b7845dd 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -195,6 +195,7 @@
     // - Internal methods
 
     void startDesktop();
+    void stopDesktop();
 
     static LogWriter connectionsLog;
     Blacklist blacklist;
@@ -226,8 +227,9 @@
     bool needRenderedCursor();
     void startFrameClock();
     void stopFrameClock();
+    int msToNextUpdate();
     void writeUpdate();
-    bool checkUpdate();
+    Region getPendingRegion();
     const RenderedCursor* getRenderedCursor();
 
     void notifyScreenLayoutChange(VNCSConnectionST *requester);