Merge branch 'altgraph' of https://github.com/CendioOssman/tigervnc
diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt
index 5047e5e..62ef401 100644
--- a/common/rfb/CMakeLists.txt
+++ b/common/rfb/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 set(RFB_SOURCES
   Blacklist.cxx
+  Congestion.cxx
   CConnection.cxx
   CMsgHandler.cxx
   CMsgReader.cxx
diff --git a/common/rfb/Congestion.cxx b/common/rfb/Congestion.cxx
new file mode 100644
index 0000000..a2f7a25
--- /dev/null
+++ b/common/rfb/Congestion.cxx
@@ -0,0 +1,470 @@
+/* Copyright 2009-2015 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+/*
+ * This code implements congestion control in the same way as TCP in
+ * order to avoid excessive latency in the transport. This is needed
+ * because "buffer bloat" is unfortunately still a very real problem.
+ *
+ * The basic principle is TCP Congestion Control (RFC 5618), with the
+ * addition of using the TCP Vegas algorithm. The reason we use Vegas
+ * is that we run on top of a reliable transport so we need a latency
+ * based algorithm rather than a loss based one. There is also a lot of
+ * interpolation of values. This is because we have rather horrible
+ * granularity in our measurements.
+ *
+ * We use a simplistic form of slow start in order to ramp up quickly
+ * from an idle state. We do not have any persistent threshold though
+ * as we have too much noise for it to be reliable.
+ */
+
+#include <assert.h>
+#include <sys/time.h>
+
+#ifdef __linux__
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <linux/sockios.h>
+#endif
+
+#include <rfb/Congestion.h>
+#include <rfb/LogWriter.h>
+#include <rfb/util.h>
+
+// Debug output on what the congestion control is up to
+#undef CONGESTION_DEBUG
+
+// Dump socket congestion window debug trace to disk
+#undef CONGESTION_TRACE
+
+using namespace rfb;
+
+// This window should get us going fairly fast on a decent bandwidth network.
+// If it's too high, it will rapidly be reduced and stay low.
+static const unsigned INITIAL_WINDOW = 16384;
+
+// TCP's minimal window is 3*MSS. But since we don't know the MSS, we
+// make a guess at 4 KiB (it's probably a bit higher).
+static const unsigned MINIMUM_WINDOW = 4096;
+
+// The current default maximum window for Linux (4 MiB). Should be a good
+// limit for now...
+static const unsigned MAXIMUM_WINDOW = 4194304;
+
+static LogWriter vlog("Congestion");
+
+Congestion::Congestion() :
+    lastPosition(0), extraBuffer(0),
+    baseRTT(-1), congWindow(INITIAL_WINDOW), inSlowStart(true),
+    measurements(0), minRTT(-1), minCongestedRTT(-1)
+{
+  gettimeofday(&lastUpdate, NULL);
+  gettimeofday(&lastSent, NULL);
+  memset(&lastPong, 0, sizeof(lastPong));
+  gettimeofday(&lastPongArrival, NULL);
+  gettimeofday(&lastAdjustment, NULL);
+}
+
+Congestion::~Congestion()
+{
+}
+
+void Congestion::updatePosition(unsigned pos)
+{
+  struct timeval now;
+  unsigned delta, consumed;
+
+  gettimeofday(&now, NULL);
+
+  delta = pos - lastPosition;
+  if ((delta > 0) || (extraBuffer > 0))
+    lastSent = now;
+
+  // Idle for too long?
+  // We use a very crude RTO calculation in order to keep things simple
+  // FIXME: should implement RFC 2861
+  if (msBetween(&lastSent, &now) > __rfbmax(baseRTT*2, 100)) {
+
+#ifdef CONGESTION_DEBUG
+    vlog.debug("Connection idle for %d ms, resetting congestion control",
+               msBetween(&lastSent, &now));
+#endif
+
+    // Close congestion window and redo wire latency measurement
+    congWindow = __rfbmin(INITIAL_WINDOW, congWindow);
+    baseRTT = -1;
+    measurements = 0;
+    gettimeofday(&lastAdjustment, NULL);
+    minRTT = minCongestedRTT = -1;
+    inSlowStart = true;
+  }
+
+  // Commonly we will be in a state of overbuffering. We need to
+  // estimate the extra delay that causes so we can separate it from
+  // the delay caused by an incorrect congestion window.
+  // (we cannot do this until we have a RTT measurement though)
+  if (baseRTT != (unsigned)-1) {
+    extraBuffer += delta;
+    consumed = msBetween(&lastUpdate, &now) * congWindow / baseRTT;
+    if (extraBuffer < consumed)
+      extraBuffer = 0;
+    else
+      extraBuffer -= consumed;
+  }
+
+  lastPosition = pos;
+  lastUpdate = now;
+}
+
+void Congestion::sentPing()
+{
+  struct RTTInfo rttInfo;
+
+  memset(&rttInfo, 0, sizeof(struct RTTInfo));
+
+  gettimeofday(&rttInfo.tv, NULL);
+  rttInfo.pos = lastPosition;
+  rttInfo.extra = getExtraBuffer();
+  rttInfo.congested = isCongested();
+
+  pings.push_back(rttInfo);
+}
+
+void Congestion::gotPong()
+{
+  struct timeval now;
+  struct RTTInfo rttInfo;
+  unsigned rtt, delay;
+
+  if (pings.empty())
+    return;
+
+  gettimeofday(&now, NULL);
+
+  rttInfo = pings.front();
+  pings.pop_front();
+
+  lastPong = rttInfo;
+  lastPongArrival = now;
+
+  rtt = msBetween(&rttInfo.tv, &now);
+  if (rtt < 1)
+    rtt = 1;
+
+  // Try to estimate wire latency by tracking lowest seen latency
+  if (rtt < baseRTT)
+    baseRTT = rtt;
+
+  // Pings sent before the last adjustment aren't interesting as they
+  // aren't a measurement of the current congestion window
+  if (isBefore(&rttInfo.tv, &lastAdjustment))
+    return;
+
+  // Estimate added delay because of overtaxed buffers (see above)
+  delay = rttInfo.extra * baseRTT / congWindow;
+  if (delay < rtt)
+    rtt -= delay;
+  else
+    rtt = 1;
+
+  // A latency less than the wire latency means that we've
+  // understimated the congestion window. We can't really determine
+  // how much, so pretend that we got no buffer latency at all.
+  if (rtt < baseRTT)
+    rtt = baseRTT;
+
+  // Record the minimum seen delay (hopefully ignores jitter) and let
+  // the congestion control do its thing.
+  //
+  // Note: We are delay based rather than loss based, which means we
+  //       need to look at pongs even if they weren't limited by the
+  //       current window ("congested"). Otherwise we will fail to
+  //       detect increasing congestion until the application exceeds
+  //       the congestion window.
+  if (rtt < minRTT)
+    minRTT = rtt;
+  if (rttInfo.congested) {
+    if (rtt < minCongestedRTT)
+      minCongestedRTT = rtt;
+  }
+
+  measurements++;
+  updateCongestion();
+}
+
+bool Congestion::isCongested()
+{
+  if (getInFlight() < congWindow)
+    return false;
+
+  return true;
+}
+
+int Congestion::getUncongestedETA()
+{
+  unsigned targetAcked;
+
+  const struct RTTInfo* prevPing;
+  unsigned eta, elapsed;
+  unsigned etaNext, delay;
+
+  std::list<struct RTTInfo>::const_iterator iter;
+
+  targetAcked = lastPosition - congWindow;
+
+  // Simple case?
+  if (lastPong.pos > targetAcked)
+    return 0;
+
+  // No measurements yet?
+  if (baseRTT == (unsigned)-1)
+    return -1;
+
+  prevPing = &lastPong;
+  eta = 0;
+  elapsed = msSince(&lastPongArrival);
+
+  // Walk the ping queue and figure out which one we are waiting for to
+  // get to an uncongested state
+
+  for (iter = pings.begin(); ;++iter) {
+    struct RTTInfo curPing;
+
+    // If we aren't waiting for a pong that will clear the congested
+    // state then we have to estimate the final bit by pretending that
+    // we had a ping just after the last position update.
+    if (iter == pings.end()) {
+      curPing.tv = lastUpdate;
+      curPing.pos = lastPosition;
+      curPing.extra = extraBuffer;
+    } else {
+      curPing = *iter;
+    }
+
+    etaNext = msBetween(&prevPing->tv, &curPing.tv);
+    // Compensate for buffering delays
+    delay = curPing.extra * baseRTT / congWindow;
+    etaNext += delay;
+    delay = prevPing->extra * baseRTT / congWindow;
+    if (delay >= etaNext)
+      etaNext = 0;
+    else
+      etaNext -= delay;
+
+    // Found it?
+    if (curPing.pos > targetAcked) {
+      eta += etaNext * (curPing.pos - targetAcked) / (curPing.pos - prevPing->pos);
+      if (elapsed > eta)
+        return 0;
+      else
+        return eta - elapsed;
+    }
+
+    assert(iter != pings.end());
+
+    eta += etaNext;
+    prevPing = &*iter;
+  }
+}
+
+void Congestion::debugTrace(const char* filename, int fd)
+{
+#ifdef CONGESTION_TRACE
+#ifdef __linux__
+  FILE *f;
+  f = fopen(filename, "ab");
+  if (f != NULL) {
+    struct tcp_info info;
+    int buffered;
+    socklen_t len;
+    len = sizeof(info);
+    if ((getsockopt(fd, IPPROTO_TCP,
+                    TCP_INFO, &info, &len) == 0) &&
+        (ioctl(fd, SIOCOUTQ, &buffered) == 0)) {
+      struct timeval now;
+      gettimeofday(&now, NULL);
+      fprintf(f, "%u.%06u,%u,%u,%u,%u\n",
+              (unsigned)now.tv_sec, (unsigned)now.tv_usec,
+              congWindow, info.tcpi_snd_cwnd * info.tcpi_snd_mss,
+              getInFlight(), buffered);
+    }
+    fclose(f);
+  }
+#endif
+#endif
+}
+
+unsigned Congestion::getExtraBuffer()
+{
+  unsigned elapsed;
+  unsigned consumed;
+
+  if (baseRTT == (unsigned)-1)
+    return 0;
+
+  elapsed = msSince(&lastUpdate);
+  consumed = elapsed * congWindow / baseRTT;
+
+  if (consumed >= extraBuffer)
+    return 0;
+  else
+    return extraBuffer - consumed;
+}
+
+unsigned Congestion::getInFlight()
+{
+  struct RTTInfo nextPong;
+  unsigned etaNext, delay, elapsed, acked;
+
+  // Simple case?
+  if (lastPosition == lastPong.pos)
+    return 0;
+
+  // No measurements yet?
+  if (baseRTT == (unsigned)-1) {
+    if (!pings.empty())
+      return lastPosition - pings.front().pos;
+    return 0;
+  }
+
+  // If we aren't waiting for any pong then we have to estimate things
+  // by pretending that we had a ping just after the last position
+  // update.
+  if (pings.empty()) {
+    nextPong.tv = lastUpdate;
+    nextPong.pos = lastPosition;
+    nextPong.extra = extraBuffer;
+  } else {
+    nextPong = pings.front();
+  }
+
+  // First we need to estimate how many bytes have made it through
+  // completely. Look at the next ping that should arrive and figure
+  // out how far behind it should be and interpolate the positions.
+
+  etaNext = msBetween(&lastPong.tv, &nextPong.tv);
+  // Compensate for buffering delays
+  delay = nextPong.extra * baseRTT / congWindow;
+  etaNext += delay;
+  delay = lastPong.extra * baseRTT / congWindow;
+  if (delay >= etaNext)
+    etaNext = 0;
+  else
+    etaNext -= delay;
+
+  elapsed = msSince(&lastPongArrival);
+
+  // The pong should be here any second. Be optimistic and assume
+  // we can already use its value.
+  if (etaNext <= elapsed)
+    acked = nextPong.pos;
+  else {
+    acked = lastPong.pos;
+    acked += (nextPong.pos - lastPong.pos) * elapsed / etaNext;
+  }
+
+  return lastPosition - acked;
+}
+
+void Congestion::updateCongestion()
+{
+  unsigned diff;
+
+  // We want at least three measurements to avoid noise
+  if (measurements < 3)
+    return;
+
+  assert(minRTT >= baseRTT);
+  assert(minCongestedRTT >= baseRTT);
+
+  // The goal is to have a slightly too large congestion window since
+  // a "perfect" one cannot be distinguished from a too small one. This
+  // translates to a goal of a few extra milliseconds of delay.
+
+  diff = minRTT - baseRTT;
+
+  if (diff > __rfbmax(100, baseRTT/2)) {
+    // We have no way of detecting loss, so assume massive latency
+    // spike means packet loss. Adjust the window and go directly
+    // to congestion avoidance.
+#ifdef CONGESTION_DEBUG
+    vlog.debug("Latency spike! Backing off...");
+#endif
+    congWindow = congWindow * baseRTT / minRTT;
+    inSlowStart = false;
+  }
+
+  if (inSlowStart) {
+    // Slow start. Aggressive growth until we see congestion.
+
+    if (diff > 25) {
+      // If we see an increased latency then we assume we've hit the
+      // limit and it's time to leave slow start and switch to
+      // congestion avoidance
+      congWindow = congWindow * baseRTT / minRTT;
+      inSlowStart = false;
+    } else {
+      // It's not safe to increase unless we actually used the entire
+      // congestion window, hence we look at minCongestedRTT and not
+      // minRTT
+
+      diff = minCongestedRTT - baseRTT;
+      if (diff < 25)
+        congWindow *= 2;
+    }
+  } else {
+    // Congestion avoidance (VEGAS)
+
+    if (diff > 50) {
+      // Slightly too fast
+      congWindow -= 4096;
+    } else {
+      // Only the "congested" pongs are checked to see if the
+      // window is too small.
+
+      diff = minCongestedRTT - baseRTT;
+
+      if (diff < 5) {
+        // Way too slow
+        congWindow += 8192;
+      } else if (diff < 25) {
+        // Too slow
+        congWindow += 4096;
+      }
+    }
+  }
+
+  if (congWindow < MINIMUM_WINDOW)
+    congWindow = MINIMUM_WINDOW;
+  if (congWindow > MAXIMUM_WINDOW)
+    congWindow = MAXIMUM_WINDOW;
+
+#ifdef CONGESTION_DEBUG
+  vlog.debug("RTT: %d/%d ms (%d ms), Window: %d KiB, Bandwidth: %g Mbps%s",
+             minRTT, minCongestedRTT, baseRTT, congWindow / 1024,
+             congWindow * 8.0 / baseRTT / 1000.0,
+             inSlowStart ? " (slow start)" : "");
+#endif
+
+  measurements = 0;
+  gettimeofday(&lastAdjustment, NULL);
+  minRTT = minCongestedRTT = -1;
+}
+
diff --git a/common/rfb/Congestion.h b/common/rfb/Congestion.h
new file mode 100644
index 0000000..fd57c22
--- /dev/null
+++ b/common/rfb/Congestion.h
@@ -0,0 +1,89 @@
+/* Copyright 2009-2015 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+#ifndef __RFB_CONGESTION_H__
+#define __RFB_CONGESTION_H__
+
+#include <list>
+
+namespace rfb {
+  class Congestion {
+  public:
+    Congestion();
+    ~Congestion();
+
+    // updatePosition() registers the current stream position and can
+    // and should be called often.
+    void updatePosition(unsigned pos);
+
+    // sentPing() must be called when a marker is placed on the
+    // outgoing stream. gotPong() must be called when the response for
+    // such a marker is received.
+    void sentPing();
+    void gotPong();
+
+    // isCongested() determines if the transport is currently congested
+    // or if more data can be sent.
+    bool isCongested();
+
+    // getUncongestedETA() returns the number of milliseconds until the
+    // transport is no longer congested. Returns 0 if there is no
+    // congestion, and -1 if it is unknown when the transport will no
+    // longer be congested.
+    int getUncongestedETA();
+
+    // debugTrace() writes the current congestion window, as well as the
+    // congestion window of the underlying TCP layer, to the specified
+    // file
+    void debugTrace(const char* filename, int fd);
+
+  protected:
+    unsigned getExtraBuffer();
+    unsigned getInFlight();
+
+    void updateCongestion();
+
+  private:
+    unsigned lastPosition;
+    unsigned extraBuffer;
+    struct timeval lastUpdate;
+    struct timeval lastSent;
+
+    unsigned baseRTT;
+    unsigned congWindow;
+    bool inSlowStart;
+
+    struct RTTInfo {
+      struct timeval tv;
+      unsigned pos;
+      unsigned extra;
+      bool congested;
+    };
+
+    std::list<struct RTTInfo> pings;
+
+    struct RTTInfo lastPong;
+    struct timeval lastPongArrival;
+
+    int measurements;
+    struct timeval lastAdjustment;
+    unsigned minRTT, minCongestedRTT;
+  };
+}
+
+#endif
diff --git a/common/rfb/ListConnInfo.h b/common/rfb/ListConnInfo.h
index 9e939d1..c49947d 100644
--- a/common/rfb/ListConnInfo.h
+++ b/common/rfb/ListConnInfo.h
@@ -20,6 +20,10 @@
 #ifndef __RFB_LISTCONNINFO_INCLUDED__
 #define __RFB_LISTCONNINFO_INCLUDED__
 
+#include <list>
+
+#include <rfb/util.h>
+
 namespace rfb {
 
   struct ListConnInfo  {
diff --git a/common/rfb/PixelFormat.cxx b/common/rfb/PixelFormat.cxx
index 76051dc..883b041 100644
--- a/common/rfb/PixelFormat.cxx
+++ b/common/rfb/PixelFormat.cxx
@@ -34,6 +34,7 @@
 using namespace rfb;
 
 rdr::U8 PixelFormat::upconvTable[256*8];
+rdr::U8 PixelFormat::downconvTable[256*8];
 
 class PixelFormat::Init {
 public:
@@ -47,24 +48,29 @@
 {
   int bits;
 
-  // Bit replication is almost perfect, but not quite. And
+  // Shifting bits is almost perfect, but not quite. And
   // a lookup table is still quicker when there is a large
   // difference between the source and destination depth.
 
   for (bits = 1;bits <= 8;bits++) {
     int i, maxVal;
-    rdr::U8 *subTable;
+    rdr::U8 *subUpTable;
+    rdr::U8 *subDownTable;
 
     maxVal = (1 << bits) - 1;
-    subTable = &upconvTable[(bits-1)*256];
+    subUpTable = &upconvTable[(bits-1)*256];
+    subDownTable = &downconvTable[(bits-1)*256];
 
     for (i = 0;i <= maxVal;i++)
-      subTable[i] = i * 255 / maxVal;
+      subUpTable[i] = i * 255 / maxVal;
 
-    // Duplicate the table so that we don't have to care about
+    // Duplicate the up table so that we don't have to care about
     // the upper bits when doing a lookup
     for (;i < 256;i += maxVal+1)
-      memcpy(&subTable[i], &subTable[0], maxVal+1);
+      memcpy(&subUpTable[i], &subUpTable[0], maxVal+1);
+
+    for (i = 0;i <= 255;i++)
+      subDownTable[i] = (i * maxVal + 128) / 255;
   }
 }
 
diff --git a/common/rfb/PixelFormat.h b/common/rfb/PixelFormat.h
index 8d67f8a..5b4b633 100644
--- a/common/rfb/PixelFormat.h
+++ b/common/rfb/PixelFormat.h
@@ -137,6 +137,7 @@
     bool endianMismatch;
 
     static rdr::U8 upconvTable[256*8];
+    static rdr::U8 downconvTable[256*8];
 
     class Init;
     friend class Init;
diff --git a/common/rfb/PixelFormat.inl b/common/rfb/PixelFormat.inl
index f9fb125..5a40379 100644
--- a/common/rfb/PixelFormat.inl
+++ b/common/rfb/PixelFormat.inl
@@ -79,10 +79,9 @@
 {
   Pixel p;
 
-  /* We don't need to mask since we shift out unwanted bits */
-  p = ((Pixel)red >> (16 - redBits)) << redShift;
-  p |= ((Pixel)green >> (16 - greenBits)) << greenShift;
-  p |= ((Pixel)blue >> (16 - blueBits)) << blueShift;
+  p = (Pixel)downconvTable[(redBits-1)*256 + (red >> 8)] << redShift;
+  p |= (Pixel)downconvTable[(greenBits-1)*256 + (green >> 8)] << greenShift;
+  p |= (Pixel)downconvTable[(blueBits-1)*256 + (blue >> 8)] << blueShift;
 
   return p;
 }
@@ -92,9 +91,9 @@
 {
   Pixel p;
 
-  p = ((Pixel)red >> (8 - redBits)) << redShift;
-  p |= ((Pixel)green >> (8 - greenBits)) << greenShift;
-  p |= ((Pixel)blue >> (8 - blueBits)) << blueShift;
+  p = (Pixel)downconvTable[(redBits-1)*256 + red] << redShift;
+  p |= (Pixel)downconvTable[(greenBits-1)*256 + green] << greenShift;
+  p |= (Pixel)downconvTable[(blueBits-1)*256 + blue] << blueShift;
 
   return p;
 }
diff --git a/common/rfb/PixelFormatBPP.cxx b/common/rfb/PixelFormatBPP.cxx
index 6b5ad6b..c8e432d 100644
--- a/common/rfb/PixelFormatBPP.cxx
+++ b/common/rfb/PixelFormatBPP.cxx
@@ -41,11 +41,11 @@
   const rdr::U8 *r, *g, *b;
   int dstPad, srcPad;
 
-  int redTruncShift, greenTruncShift, blueTruncShift;
+  const rdr::U8 *redDownTable, *greenDownTable, *blueDownTable;
 
-  redTruncShift = 8 - redBits;
-  greenTruncShift = 8 - greenBits;
-  blueTruncShift = 8 - blueBits;
+  redDownTable = &downconvTable[(redBits-1)*256];
+  greenDownTable = &downconvTable[(greenBits-1)*256];
+  blueDownTable = &downconvTable[(blueBits-1)*256];
 
   if (srcPF.bigEndian) {
     r = src + (24 - srcPF.redShift)/8;
@@ -64,9 +64,9 @@
     while (w_--) {
       rdr::UOUT d;
 
-      d = (*r >> redTruncShift) << redShift;
-      d |= (*g >> greenTruncShift) << greenShift;
-      d |= (*b >> blueTruncShift) << blueShift;
+      d = redDownTable[*r] << redShift;
+      d |= greenDownTable[*g] << greenShift;
+      d |= blueDownTable[*b] << blueShift;
 
 #if OUTBPP != 8
       if (endianMismatch)
diff --git a/common/rfb/Timer.cxx b/common/rfb/Timer.cxx
index 71887a0..7179cd8 100644
--- a/common/rfb/Timer.cxx
+++ b/common/rfb/Timer.cxx
@@ -124,6 +124,9 @@
   gettimeofday(&now, 0);
   stop();
   timeoutMs = timeoutMs_;
+  // The rest of the code assumes non-zero timeout
+  if (timeoutMs <= 0)
+    timeoutMs = 1;
   dueTime = addMillis(now, timeoutMs);
   insertTimer(this);
 }
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index bd4bc36..e707e49 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -17,28 +17,20 @@
  * USA.
  */
 
-// Debug output on what the congestion control is up to
-#undef CONGESTION_DEBUG
-
-#include <sys/time.h>
-
-#ifdef CONGESTION_DEBUG
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#endif
-
 #include <network/TcpSocket.h>
-#include <rfb/VNCSConnectionST.h>
+
+#include <rfb/ComparingUpdateTracker.h>
+#include <rfb/Encoder.h>
+#include <rfb/KeyRemapper.h>
 #include <rfb/LogWriter.h>
 #include <rfb/Security.h>
+#include <rfb/ServerCore.h>
+#include <rfb/SMsgWriter.h>
+#include <rfb/VNCServerST.h>
+#include <rfb/VNCSConnectionST.h>
 #include <rfb/screenTypes.h>
 #include <rfb/fenceTypes.h>
 #include <rfb/ledStates.h>
-#include <rfb/ServerCore.h>
-#include <rfb/ComparingUpdateTracker.h>
-#include <rfb/KeyRemapper.h>
-#include <rfb/Encoder.h>
 #define XK_LATIN1
 #define XK_MISCELLANY
 #define XK_XKB_KEYS
@@ -48,24 +40,6 @@
 
 static LogWriter vlog("VNCSConnST");
 
-// This window should get us going fairly fast on a decent bandwidth network.
-// If it's too high, it will rapidly be reduced and stay low.
-static const unsigned INITIAL_WINDOW = 16384;
-
-// TCP's minimal window is 3*MSS. But since we don't know the MSS, we
-// make a guess at 4 KiB (it's probably a bit higher).
-static const unsigned MINIMUM_WINDOW = 4096;
-
-// The current default maximum window for Linux (4 MiB). Should be a good
-// limit for now...
-static const unsigned MAXIMUM_WINDOW = 4194304;
-
-struct RTTInfo {
-  struct timeval tv;
-  int offset;
-  unsigned inFlight;
-};
-
 static Cursor emptyCursor(0, 0, Point(0, 0), NULL);
 
 VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s,
@@ -73,10 +47,7 @@
   : sock(s), reverseConnection(reverse),
     inProcessMessages(false),
     pendingSyncFence(false), syncFence(false), fenceFlags(0),
-    fenceDataLen(0), fenceData(NULL),
-    baseRTT(-1), congWindow(0), ackedOffset(0), sentOffset(0),
-    minRTT(-1), seenCongestion(false),
-    pingCounter(0), congestionTimer(this),
+    fenceDataLen(0), fenceData(NULL), congestionTimer(this),
     server(server_), updates(false),
     updateRenderedCursor(false), removeRenderedCursor(false),
     continuousUpdates(false), encodeManager(this), pointerEventTime(0),
@@ -457,10 +428,6 @@
   // - Mark the entire display as "dirty"
   updates.add_changed(server->pb->getRect());
   startTime = time(0);
-
-  // - Bootstrap the congestion control
-  ackedOffset = sock->outStream().length();
-  congWindow = INITIAL_WINDOW;
 }
 
 void VNCSConnectionST::queryConnection(const char* userName)
@@ -769,6 +736,8 @@
 
 void VNCSConnectionST::fence(rdr::U32 flags, unsigned len, const char data[])
 {
+  rdr::U8 type;
+
   if (flags & fenceFlagRequest) {
     if (flags & fenceFlagSyncNext) {
       pendingSyncFence = true;
@@ -792,18 +761,20 @@
     return;
   }
 
-  struct RTTInfo rttInfo;
+  if (len < 1)
+    vlog.error("Fence response of unexpected size received");
 
-  switch (len) {
+  type = data[0];
+
+  switch (type) {
   case 0:
     // Initial dummy fence;
     break;
-  case sizeof(struct RTTInfo):
-    memcpy(&rttInfo, data, sizeof(struct RTTInfo));
-    handleRTTPong(rttInfo);
+  case 1:
+    congestion.gotPong();
     break;
   default:
-    vlog.error("Fence response of unexpected size received");
+    vlog.error("Fence response of unexpected type received");
   }
 }
 
@@ -842,7 +813,8 @@
 
 void VNCSConnectionST::supportsFence()
 {
-  writer()->writeFence(fenceFlagRequest, 0, NULL);
+  char type = 0;
+  writer()->writeFence(fenceFlagRequest, sizeof(type), &type);
 }
 
 void VNCSConnectionST::supportsContinuousUpdates()
@@ -865,7 +837,7 @@
 {
   try {
     if (t == &congestionTimer)
-      updateCongestion();
+      writeFramebufferUpdate();
   } catch (rdr::Exception& e) {
     close(e.str());
   }
@@ -889,182 +861,54 @@
 
 void VNCSConnectionST::writeRTTPing()
 {
-  struct RTTInfo rttInfo;
+  char type;
 
   if (!cp.supportsFence)
     return;
 
-  memset(&rttInfo, 0, sizeof(struct RTTInfo));
-
-  gettimeofday(&rttInfo.tv, NULL);
-  rttInfo.offset = sock->outStream().length();
-  rttInfo.inFlight = rttInfo.offset - ackedOffset;
+  congestion.updatePosition(sock->outStream().length());
 
   // We need to make sure any old update are already processed by the
   // time we get the response back. This allows us to reliably throttle
   // back on client overload, as well as network overload.
+  type = 1;
   writer()->writeFence(fenceFlagRequest | fenceFlagBlockBefore,
-                       sizeof(struct RTTInfo), (const char*)&rttInfo);
+                       sizeof(type), &type);
 
-  pingCounter++;
-
-  sentOffset = rttInfo.offset;
-
-  // Let some data flow before we adjust the settings
-  if (!congestionTimer.isStarted())
-    congestionTimer.start(__rfbmin(baseRTT * 2, 100));
-}
-
-void VNCSConnectionST::handleRTTPong(const struct RTTInfo &rttInfo)
-{
-  unsigned rtt, delay;
-
-  pingCounter--;
-
-  rtt = msSince(&rttInfo.tv);
-  if (rtt < 1)
-    rtt = 1;
-
-  ackedOffset = rttInfo.offset;
-
-  // Try to estimate wire latency by tracking lowest seen latency
-  if (rtt < baseRTT)
-    baseRTT = rtt;
-
-  if (rttInfo.inFlight > congWindow) {
-    seenCongestion = true;
-
-    // Estimate added delay because of overtaxed buffers
-    delay = (rttInfo.inFlight - congWindow) * baseRTT / congWindow;
-
-    if (delay < rtt)
-      rtt -= delay;
-    else
-      rtt = 1;
-
-    // If we underestimate the congestion window, then we'll get a latency
-    // that's less than the wire latency, which will confuse other portions
-    // of the code.
-    if (rtt < baseRTT)
-      rtt = baseRTT;
-  }
-
-  // We only keep track of the minimum latency seen (for a given interval)
-  // on the basis that we want to avoid continuous buffer issue, but don't
-  // mind (or even approve of) bursts.
-  if (rtt < minRTT)
-    minRTT = rtt;
+  congestion.sentPing();
 }
 
 bool VNCSConnectionST::isCongested()
 {
-  int offset;
+  unsigned eta;
+
+  congestionTimer.stop();
 
   // Stuff still waiting in the send buffer?
   sock->outStream().flush();
+  congestion.debugTrace("congestion-trace.csv", sock->getFd());
   if (sock->outStream().bufferUsage() > 0)
     return true;
 
   if (!cp.supportsFence)
     return false;
 
-  // Idle for too long? (and no data on the wire)
-  //
-  // FIXME: This should really just be one baseRTT, but we're getting
-  //        problems with triggering the idle timeout on each update.
-  //        Maybe we need to use a moving average for the wire latency
-  //        instead of baseRTT.
-  if ((sentOffset == ackedOffset) &&
-      (sock->outStream().getIdleTime() > 2 * baseRTT)) {
-
-#ifdef CONGESTION_DEBUG
-    if (congWindow > INITIAL_WINDOW)
-      fprintf(stderr, "Reverting to initial window (%d KiB) after %d ms\n",
-              INITIAL_WINDOW / 1024, sock->outStream().getIdleTime());
-#endif
-
-    // Close congestion window and allow a transfer
-    // FIXME: Reset baseRTT like Linux Vegas?
-    congWindow = __rfbmin(INITIAL_WINDOW, congWindow);
-
-    return false;
-  }
-
-  offset = sock->outStream().length();
-
-  // FIXME: Should we compensate for non-update data?
-  //        (i.e. use sentOffset instead of offset)
-  if ((offset - ackedOffset) < congWindow)
+  congestion.updatePosition(sock->outStream().length());
+  if (!congestion.isCongested())
     return false;
 
-  // If we just have one outstanding "ping", that means the client has
-  // started receiving our update. In order to not regress compared to
-  // before we had congestion avoidance, we allow another update here.
-  // This could further clog up the tubes, but congestion control isn't
-  // really working properly right now anyway as the wire would otherwise
-  // be idle for at least RTT/2.
-  if (pingCounter == 1)
-    return false;
+  eta = congestion.getUncongestedETA();
+  if (eta >= 0)
+    congestionTimer.start(eta);
 
   return true;
 }
 
 
-void VNCSConnectionST::updateCongestion()
-{
-  unsigned diff;
-
-  if (!seenCongestion)
-    return;
-
-  diff = minRTT - baseRTT;
-
-  if (diff > __rfbmin(100, baseRTT)) {
-    // Way too fast
-    congWindow = congWindow * baseRTT / minRTT;
-  } else if (diff > __rfbmin(50, baseRTT/2)) {
-    // Slightly too fast
-    congWindow -= 4096;
-  } else if (diff < 5) {
-    // Way too slow
-    congWindow += 8192;
-  } else if (diff < 25) {
-    // Too slow
-    congWindow += 4096;
-  }
-
-  if (congWindow < MINIMUM_WINDOW)
-    congWindow = MINIMUM_WINDOW;
-  if (congWindow > MAXIMUM_WINDOW)
-    congWindow = MAXIMUM_WINDOW;
-
-#ifdef CONGESTION_DEBUG
-  fprintf(stderr, "RTT: %d ms (%d ms), Window: %d KiB, Bandwidth: %g Mbps\n",
-          minRTT, baseRTT, congWindow / 1024,
-          congWindow * 8.0 / baseRTT / 1000.0);
-
-#ifdef TCP_INFO
-  struct tcp_info tcp_info;
-  socklen_t tcp_info_length;
-
-  tcp_info_length = sizeof(tcp_info);
-  if (getsockopt(sock->getFd(), SOL_TCP, TCP_INFO,
-                 (void *)&tcp_info, &tcp_info_length) == 0) {
-    fprintf(stderr, "Socket: RTT: %d ms (+/- %d ms) Window %d KiB\n",
-            tcp_info.tcpi_rtt / 1000, tcp_info.tcpi_rttvar / 1000,
-            tcp_info.tcpi_snd_mss * tcp_info.tcpi_snd_cwnd / 1024);
-  }
-#endif
-
-#endif
-
-  minRTT = -1;
-  seenCongestion = false;
-}
-
-
 void VNCSConnectionST::writeFramebufferUpdate()
 {
+  congestion.updatePosition(sock->outStream().length());
+
   // We're in the middle of processing a command that's supposed to be
   // synchronised. Allowing an update to slip out right now might violate
   // that synchronisation.
@@ -1101,6 +945,8 @@
   writeDataUpdate();
 
   sock->cork(false);
+
+  congestion.updatePosition(sock->outStream().length());
 }
 
 void VNCSConnectionST::writeNoDataUpdate()
diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h
index 8d6a7bc..2f075a6 100644
--- a/common/rfb/VNCSConnectionST.h
+++ b/common/rfb/VNCSConnectionST.h
@@ -28,15 +28,14 @@
 #define __RFB_VNCSCONNECTIONST_H__
 
 #include <map>
-#include <rfb/SConnection.h>
-#include <rfb/SMsgWriter.h>
-#include <rfb/VNCServerST.h>
-#include <rfb/Timer.h>
-#include <rfb/EncodeManager.h>
 
-struct RTTInfo;
+#include <rfb/Congestion.h>
+#include <rfb/EncodeManager.h>
+#include <rfb/SConnection.h>
+#include <rfb/Timer.h>
 
 namespace rfb {
+  class VNCServerST;
 
   class VNCSConnectionST : public SConnection,
                            public Timer::Callback {
@@ -164,9 +163,7 @@
 
     // Congestion control
     void writeRTTPing();
-    void handleRTTPong(const struct RTTInfo &rttInfo);
     bool isCongested();
-    void updateCongestion();
 
     // writeFramebufferUpdate() attempts to write a framebuffer update to the
     // client.
@@ -192,13 +189,7 @@
     unsigned fenceDataLen;
     char *fenceData;
 
-    unsigned baseRTT;
-    unsigned congWindow;
-    unsigned ackedOffset, sentOffset;
-
-    unsigned minRTT;
-    bool seenCongestion;
-    unsigned pingCounter;
+    Congestion congestion;
     Timer congestionTimer;
 
     VNCServerST* server;
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index f27099f..0008dc4 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -51,12 +51,13 @@
 #include <assert.h>
 #include <stdlib.h>
 
+#include <rfb/ComparingUpdateTracker.h>
+#include <rfb/KeyRemapper.h>
+#include <rfb/ListConnInfo.h>
+#include <rfb/Security.h>
 #include <rfb/ServerCore.h>
 #include <rfb/VNCServerST.h>
 #include <rfb/VNCSConnectionST.h>
-#include <rfb/ComparingUpdateTracker.h>
-#include <rfb/Security.h>
-#include <rfb/KeyRemapper.h>
 #include <rfb/util.h>
 #include <rfb/ledStates.h>
 
@@ -157,6 +158,7 @@
         slog.debug("no authenticated clients - stopping desktop");
         desktopStarted = false;
         desktop->stop();
+        stopFrameClock();
       }
 
       if (comparer)
@@ -313,8 +315,11 @@
   screenLayout = layout;
 
   if (!pb) {
+    screenLayout = ScreenSet();
+
     if (desktopStarted)
       throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
+
     return;
   }
 
@@ -337,18 +342,10 @@
 
 void VNCServerST::setPixelBuffer(PixelBuffer* pb_)
 {
-  ScreenSet layout;
-
-  if (!pb_) {
-    if (desktopStarted)
-      throw Exception("setPixelBuffer: null PixelBuffer when desktopStarted?");
-    return;
-  }
-
-  layout = screenLayout;
+  ScreenSet layout = screenLayout;
 
   // Check that the screen layout is still valid
-  if (!layout.validate(pb_->width(), pb_->height())) {
+  if (pb_ && !layout.validate(pb_->width(), pb_->height())) {
     Rect fbRect;
     ScreenSet::iterator iter, iter_next;
 
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index 3a56370..e00a1f7 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -28,19 +28,18 @@
 
 #include <rfb/SDesktop.h>
 #include <rfb/VNCServer.h>
-#include <rfb/Configuration.h>
 #include <rfb/LogWriter.h>
 #include <rfb/Blacklist.h>
 #include <rfb/Cursor.h>
 #include <rfb/Timer.h>
 #include <network/Socket.h>
-#include <rfb/ListConnInfo.h>
 #include <rfb/ScreenSet.h>
 
 namespace rfb {
 
   class VNCSConnectionST;
   class ComparingUpdateTracker;
+  class ListConnInfo;
   class PixelBuffer;
   class KeyRemapper;
 
diff --git a/common/rfb/util.cxx b/common/rfb/util.cxx
index cfec2ef..4fd84eb 100644
--- a/common/rfb/util.cxx
+++ b/common/rfb/util.cxx
@@ -121,19 +121,38 @@
     dest[src ? destlen-1 : 0] = 0;
   }
 
+  unsigned msBetween(const struct timeval *first,
+                     const struct timeval *second)
+  {
+    unsigned diff;
+
+    diff = (second->tv_sec - first->tv_sec) * 1000;
+
+    diff += second->tv_usec / 1000;
+    diff -= first->tv_usec / 1000;
+
+    return diff;
+  }
+
   unsigned msSince(const struct timeval *then)
   {
     struct timeval now;
-    unsigned diff;
 
     gettimeofday(&now, NULL);
 
-    diff = (now.tv_sec - then->tv_sec) * 1000;
+    return msBetween(then, &now);
+  }
 
-    diff += now.tv_usec / 1000;
-    diff -= then->tv_usec / 1000;
-
-    return diff;
+  bool isBefore(const struct timeval *first,
+                const struct timeval *second)
+  {
+    if (first->tv_sec < second->tv_sec)
+      return true;
+    if (first->tv_sec > second->tv_sec)
+      return false;
+    if (first->tv_usec < second->tv_usec)
+      return true;
+    return false;
   }
 
   static size_t doPrefix(long long value, const char *unit,
diff --git a/common/rfb/util.h b/common/rfb/util.h
index 0de64c4..b678b89 100644
--- a/common/rfb/util.h
+++ b/common/rfb/util.h
@@ -99,9 +99,17 @@
     return (secs < 0 || secs > (INT_MAX/1000) ? INT_MAX : secs * 1000);
   }
 
+  // Returns time elapsed between two moments in milliseconds.
+  unsigned msBetween(const struct timeval *first,
+                     const struct timeval *second);
+
   // Returns time elapsed since given moment in milliseconds.
   unsigned msSince(const struct timeval *then);
 
+  // Returns true if first happened before seconds
+  bool isBefore(const struct timeval *first,
+                const struct timeval *second);
+
   size_t siPrefix(long long value, const char *unit,
                   char *buffer, size_t maxlen, int precision=6);
   size_t iecPrefix(long long value, const char *unit,
diff --git a/po/LINGUAS b/po/LINGUAS
index e9427f9..f7a6c54 100644
--- a/po/LINGUAS
+++ b/po/LINGUAS
@@ -8,6 +8,7 @@
 fr
 fur
 hu
+id
 it
 nl
 pl
diff --git a/po/id.po b/po/id.po
new file mode 100644
index 0000000..ef31239
--- /dev/null
+++ b/po/id.po
@@ -0,0 +1,659 @@
+# Indonesian translation of TigerVNC
+# Copyright (C) 2018 the TigerVNC Team (msgids)
+# This file is distributed under the same license as the tigervnc package.
+# Andika Triwidada <andika@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: tigervnc 1.7.90\n"
+"Report-Msgid-Bugs-To: tigervnc-devel@googlegroups.com\n"
+"POT-Creation-Date: 2017-04-19 13:05+0000\n"
+"PO-Revision-Date: 2018-03-08 00:54+0700\n"
+"Last-Translator: Andika Triwidada <andika@gmail.com>\n"
+"Language-Team: Indonesian <translation-team-id@lists.sourceforge.net>\n"
+"Language: id\n"
+"X-Bugs: Report translation errors to the Language-Team address.\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: vncviewer/CConn.cxx:110
+#, c-format
+msgid "connected to host %s port %d"
+msgstr "terhubung ke host %s port %d"
+
+#: vncviewer/CConn.cxx:169
+#, c-format
+msgid "Desktop name: %.80s"
+msgstr "Nama desktop: %.80s"
+
+#: vncviewer/CConn.cxx:174
+#, c-format
+msgid "Host: %.80s port: %d"
+msgstr "Host: %.80s port: %d"
+
+#: vncviewer/CConn.cxx:179
+#, c-format
+msgid "Size: %d x %d"
+msgstr "Ukuran: %d × %d"
+
+#: vncviewer/CConn.cxx:187
+#, c-format
+msgid "Pixel format: %s"
+msgstr "Format piksel: %s"
+
+#: vncviewer/CConn.cxx:194
+#, c-format
+msgid "(server default %s)"
+msgstr "(server baku %s)"
+
+#: vncviewer/CConn.cxx:199
+#, c-format
+msgid "Requested encoding: %s"
+msgstr "Pengkodean yang diminta: %s"
+
+#: vncviewer/CConn.cxx:204
+#, c-format
+msgid "Last used encoding: %s"
+msgstr "Pengkodean yang terakhir dipakai: %s"
+
+#: vncviewer/CConn.cxx:209
+#, c-format
+msgid "Line speed estimate: %d kbit/s"
+msgstr "Estimasi kecepatan saluran: %d kbit/s"
+
+#: vncviewer/CConn.cxx:214
+#, c-format
+msgid "Protocol version: %d.%d"
+msgstr "Versi protokol: %d.%d"
+
+#: vncviewer/CConn.cxx:219
+#, c-format
+msgid "Security method: %s"
+msgstr "Metode keamanan: %s"
+
+#: vncviewer/CConn.cxx:343
+#, c-format
+msgid "SetDesktopSize failed: %d"
+msgstr "SetDesktopSize gagal: %d"
+
+#: vncviewer/CConn.cxx:413
+msgid "Invalid SetColourMapEntries from server!"
+msgstr "SetColourMapEntries yang tidak valid dari server!"
+
+#: vncviewer/CConn.cxx:489
+msgid "Enabling continuous updates"
+msgstr "Memfungsikan pembaruan terus-menerus"
+
+#: vncviewer/CConn.cxx:559
+#, c-format
+msgid "Throughput %d kbit/s - changing to quality %d"
+msgstr "Throughput %d kbit/s - mengubah ke kualitas %d"
+
+#: vncviewer/CConn.cxx:581
+#, c-format
+msgid "Throughput %d kbit/s - full color is now %s"
+msgstr "Throughput %d kbit/s - warna penuh sekarang %s"
+
+#: vncviewer/CConn.cxx:583
+msgid "disabled"
+msgstr "dinonaktifkan"
+
+#: vncviewer/CConn.cxx:583
+msgid "enabled"
+msgstr "difungsikan"
+
+#: vncviewer/CConn.cxx:593
+#, c-format
+msgid "Using %s encoding"
+msgstr "Memakai pengkodean %s"
+
+#: vncviewer/CConn.cxx:640
+#, c-format
+msgid "Using pixel format %s"
+msgstr "Memakai format piksel %s"
+
+#: vncviewer/DesktopWindow.cxx:121
+msgid "Invalid geometry specified!"
+msgstr "Geometri yang dinyatakan tidak valid!"
+
+#: vncviewer/DesktopWindow.cxx:434
+msgid "Adjusting window size to avoid accidental full screen request"
+msgstr "Menyetel ukuran jendela untuk menghindari permintaan layar penuh tak disengaja"
+
+#: vncviewer/DesktopWindow.cxx:478
+#, c-format
+msgid "Press %s to open the context menu"
+msgstr "Tekan %s untuk membuka menu konteks"
+
+#: vncviewer/DesktopWindow.cxx:741 vncviewer/DesktopWindow.cxx:747
+#: vncviewer/DesktopWindow.cxx:760
+msgid "Failure grabbing keyboard"
+msgstr "Kegagalan dalam mengambil alih papan ketik"
+
+#: vncviewer/DesktopWindow.cxx:772
+msgid "Failure grabbing mouse"
+msgstr "Kegagalan dalam mengambil alih tetikus"
+
+#: vncviewer/DesktopWindow.cxx:1002
+msgid "Invalid screen layout computed for resize request!"
+msgstr "Tata letak layar yang tidak valid dihitung untuk permintaan ubah ukuran!"
+
+#: vncviewer/FLTKPixelBuffer.cxx:33
+msgid "Not enough memory for framebuffer"
+msgstr "Tidak cukup memori untuk framebuffer"
+
+#: vncviewer/OptionsDialog.cxx:57
+msgid "VNC Viewer: Connection Options"
+msgstr "Penampil VNC: Opsi Sambungan"
+
+#: vncviewer/OptionsDialog.cxx:83 vncviewer/ServerDialog.cxx:91
+#: vncviewer/vncviewer.cxx:282
+msgid "Cancel"
+msgstr "Batal"
+
+#: vncviewer/OptionsDialog.cxx:88 vncviewer/vncviewer.cxx:281
+msgid "OK"
+msgstr "OK"
+
+#: vncviewer/OptionsDialog.cxx:423
+msgid "Compression"
+msgstr "Kompresi"
+
+#: vncviewer/OptionsDialog.cxx:439
+msgid "Auto select"
+msgstr "Pilih otomatis"
+
+#: vncviewer/OptionsDialog.cxx:451
+msgid "Preferred encoding"
+msgstr "Pengkodean yang disukai"
+
+#: vncviewer/OptionsDialog.cxx:499
+msgid "Color level"
+msgstr "Tingkat warna"
+
+#: vncviewer/OptionsDialog.cxx:510
+msgid "Full (all available colors)"
+msgstr "Penuh (semua warna yang tersedia)"
+
+#: vncviewer/OptionsDialog.cxx:517
+msgid "Medium (256 colors)"
+msgstr "Menengah (256 warna)"
+
+#: vncviewer/OptionsDialog.cxx:524
+msgid "Low (64 colors)"
+msgstr "Rendah (64 warna)"
+
+#: vncviewer/OptionsDialog.cxx:531
+msgid "Very low (8 colors)"
+msgstr "Sangat rendah (8 warna)"
+
+#: vncviewer/OptionsDialog.cxx:548
+msgid "Custom compression level:"
+msgstr "Tingkat kompresi ubahan:"
+
+#: vncviewer/OptionsDialog.cxx:554
+msgid "level (1=fast, 6=best [4-6 are rarely useful])"
+msgstr "level (1=cepat, 6=terbaik [4-6 jarang berguna])"
+
+#: vncviewer/OptionsDialog.cxx:561
+msgid "Allow JPEG compression:"
+msgstr "Izinkan kompresi JPEG:"
+
+#: vncviewer/OptionsDialog.cxx:567
+msgid "quality (0=poor, 9=best)"
+msgstr "kualitas (0=buruk, 9=terbaik)"
+
+#: vncviewer/OptionsDialog.cxx:578
+msgid "Security"
+msgstr "Keamanan"
+
+#: vncviewer/OptionsDialog.cxx:593
+msgid "Encryption"
+msgstr "Enkripsi"
+
+#: vncviewer/OptionsDialog.cxx:604 vncviewer/OptionsDialog.cxx:657
+#: vncviewer/OptionsDialog.cxx:737
+msgid "None"
+msgstr "Nihil"
+
+#: vncviewer/OptionsDialog.cxx:610
+msgid "TLS with anonymous certificates"
+msgstr "TLS dengan sertifikat anonim"
+
+#: vncviewer/OptionsDialog.cxx:616
+msgid "TLS with X509 certificates"
+msgstr "TLS dengan sertifikat X509"
+
+#: vncviewer/OptionsDialog.cxx:623
+msgid "Path to X509 CA certificate"
+msgstr "Path ke sertifikat CA X509"
+
+#: vncviewer/OptionsDialog.cxx:630
+msgid "Path to X509 CRL file"
+msgstr "Path ke berkas CRL X509"
+
+#: vncviewer/OptionsDialog.cxx:646
+msgid "Authentication"
+msgstr "Otentikasi"
+
+#: vncviewer/OptionsDialog.cxx:663
+msgid "Standard VNC (insecure without encryption)"
+msgstr "VNC standar (tidak aman tanpa enkripsi)"
+
+#: vncviewer/OptionsDialog.cxx:669
+msgid "Username and password (insecure without encryption)"
+msgstr "Nama pengguna dan kata sandi (tidak aman tanpa enkripsi)"
+
+#: vncviewer/OptionsDialog.cxx:688
+msgid "Input"
+msgstr "Masukan"
+
+#: vncviewer/OptionsDialog.cxx:696
+msgid "View only (ignore mouse and keyboard)"
+msgstr "Hanya melihat (mengabaikan tetikus dan papan ketik)"
+
+#: vncviewer/OptionsDialog.cxx:702
+msgid "Accept clipboard from server"
+msgstr "Terima papan klip dari server"
+
+#: vncviewer/OptionsDialog.cxx:710
+msgid "Also set primary selection"
+msgstr "Juga atur pemilihan primer"
+
+#: vncviewer/OptionsDialog.cxx:717
+msgid "Send clipboard to server"
+msgstr "Kirim papan klip ke server"
+
+#: vncviewer/OptionsDialog.cxx:725
+msgid "Send primary selection as clipboard"
+msgstr "Kirim pilihan primer sebagai papan klip"
+
+#: vncviewer/OptionsDialog.cxx:732
+msgid "Pass system keys directly to server (full screen)"
+msgstr "Lewatkan tombol-tombol sistem secara langsung ke server (layar penuh)"
+
+#: vncviewer/OptionsDialog.cxx:735
+msgid "Menu key"
+msgstr "Tombol menu"
+
+#: vncviewer/OptionsDialog.cxx:751
+msgid "Screen"
+msgstr "Layar"
+
+#: vncviewer/OptionsDialog.cxx:759
+msgid "Resize remote session on connect"
+msgstr "Ubah ukuran sesi jauh pada saat tersambung"
+
+#: vncviewer/OptionsDialog.cxx:772
+msgid "Resize remote session to the local window"
+msgstr "Ubah ukuran sesi jauh ke jendela lokal"
+
+#: vncviewer/OptionsDialog.cxx:778
+msgid "Full-screen mode"
+msgstr "Mode layar penuh"
+
+#: vncviewer/OptionsDialog.cxx:784
+msgid "Enable full-screen mode over all monitors"
+msgstr "Fungsikan mode layar penuh di atas semua monitor"
+
+#: vncviewer/OptionsDialog.cxx:793
+msgid "Misc."
+msgstr "Rupa-rupa"
+
+#: vncviewer/OptionsDialog.cxx:801
+msgid "Shared (don't disconnect other viewers)"
+msgstr "Bersama (jangan putuskan pemirsa lain)"
+
+#: vncviewer/OptionsDialog.cxx:807
+msgid "Show dot when no cursor"
+msgstr "Tampilkan titik saat tidak ada kursor"
+
+#: vncviewer/ServerDialog.cxx:42
+msgid "VNC Viewer: Connection Details"
+msgstr "Penampil VNC: Rincian Sambungan"
+
+#: vncviewer/ServerDialog.cxx:49 vncviewer/ServerDialog.cxx:54
+msgid "VNC server:"
+msgstr "Server VNC:"
+
+#: vncviewer/ServerDialog.cxx:64
+msgid "Options..."
+msgstr "Opsi..."
+
+#: vncviewer/ServerDialog.cxx:69
+msgid "Load..."
+msgstr "Muat..."
+
+#: vncviewer/ServerDialog.cxx:74
+msgid "Save As..."
+msgstr "Simpan Sebagai..."
+
+#: vncviewer/ServerDialog.cxx:86
+msgid "About..."
+msgstr "Tentang..."
+
+#: vncviewer/ServerDialog.cxx:96
+msgid "Connect"
+msgstr "Sambung"
+
+#: vncviewer/UserDialog.cxx:74
+msgid "Opening password file failed"
+msgstr "Membuka berkas kata sandi gagal"
+
+#: vncviewer/UserDialog.cxx:86 vncviewer/UserDialog.cxx:96
+msgid "VNC authentication"
+msgstr "Otentikasi VNC"
+
+#: vncviewer/UserDialog.cxx:87 vncviewer/UserDialog.cxx:102
+msgid "Password:"
+msgstr "Kata Sandi:"
+
+#: vncviewer/UserDialog.cxx:89
+msgid "Authentication cancelled"
+msgstr "Autentikasi dibatalkan"
+
+#: vncviewer/UserDialog.cxx:99
+msgid "Username:"
+msgstr "Nama Pengguna:"
+
+#: vncviewer/Viewport.cxx:586
+#, c-format
+msgid "No scan code for extended virtual key 0x%02x"
+msgstr "Tidak ada kode pindai untuk tombol virtual yang diperluas 0x%02x"
+
+#: vncviewer/Viewport.cxx:588
+#, c-format
+msgid "No scan code for virtual key 0x%02x"
+msgstr "Tidak ada kode pindai untuk tombol virtual 0x%02x"
+
+#: vncviewer/Viewport.cxx:605
+#, c-format
+msgid "No symbol for extended virtual key 0x%02x"
+msgstr "Tidak ada simbol untuk tombol virtual yang diperluas 0x%02x"
+
+#: vncviewer/Viewport.cxx:607
+#, c-format
+msgid "No symbol for virtual key 0x%02x"
+msgstr "Tidak ada simbol untuk tombol virtual 0x%02x"
+
+#: vncviewer/Viewport.cxx:645
+#, c-format
+msgid "No symbol for key code 0x%02x (in the current state)"
+msgstr "Tidak ada simbol untuk kode tombol 0x%02x (dalam keadaan saat ini)"
+
+#: vncviewer/Viewport.cxx:671
+#, c-format
+msgid "No symbol for key code %d (in the current state)"
+msgstr "Tidak ada simbol untuk kode tombol %d (dalam keadaan saat ini)"
+
+#: vncviewer/Viewport.cxx:708
+msgctxt "ContextMenu|"
+msgid "E&xit viewer"
+msgstr "&Keluar penampil"
+
+#: vncviewer/Viewport.cxx:711
+msgctxt "ContextMenu|"
+msgid "&Full screen"
+msgstr "Layar &Penuh"
+
+#: vncviewer/Viewport.cxx:714
+msgctxt "ContextMenu|"
+msgid "Minimi&ze"
+msgstr "Mi&nimalkan"
+
+#: vncviewer/Viewport.cxx:716
+msgctxt "ContextMenu|"
+msgid "Resize &window to session"
+msgstr "Ubah ukuran &jendela ke sesi"
+
+#: vncviewer/Viewport.cxx:721
+msgctxt "ContextMenu|"
+msgid "&Ctrl"
+msgstr "&Ctrl"
+
+#: vncviewer/Viewport.cxx:724
+msgctxt "ContextMenu|"
+msgid "&Alt"
+msgstr "&Alt"
+
+#: vncviewer/Viewport.cxx:730
+#, c-format
+msgctxt "ContextMenu|"
+msgid "Send %s"
+msgstr "Kirim %s"
+
+#: vncviewer/Viewport.cxx:736
+msgctxt "ContextMenu|"
+msgid "Send Ctrl-Alt-&Del"
+msgstr "Kirim Ctrl-Alt-Del"
+
+#: vncviewer/Viewport.cxx:739
+msgctxt "ContextMenu|"
+msgid "&Refresh screen"
+msgstr "Sega&rkan layar"
+
+#: vncviewer/Viewport.cxx:742
+msgctxt "ContextMenu|"
+msgid "&Options..."
+msgstr "&Opsi..."
+
+#: vncviewer/Viewport.cxx:744
+msgctxt "ContextMenu|"
+msgid "Connection &info..."
+msgstr "&Info koneksi..."
+
+#: vncviewer/Viewport.cxx:746
+msgctxt "ContextMenu|"
+msgid "About &TigerVNC viewer..."
+msgstr "Tentang penampil &TigerVNC..."
+
+#: vncviewer/Viewport.cxx:749
+msgctxt "ContextMenu|"
+msgid "Dismiss &menu"
+msgstr "Tutup &menu"
+
+#: vncviewer/Viewport.cxx:833
+msgid "VNC connection info"
+msgstr "Informasi sambungan VNC"
+
+#: vncviewer/parameters.cxx:286 vncviewer/parameters.cxx:320
+#, c-format
+msgid "The name of the parameter %s was too large to write to the registry"
+msgstr "Nama parameter %s terlalu besar untuk ditulis ke registry"
+
+#: vncviewer/parameters.cxx:292 vncviewer/parameters.cxx:299
+#, c-format
+msgid "The parameter %s was too large to write to the registry"
+msgstr "Parameter %s terlalu besar untuk ditulis ke registry"
+
+#: vncviewer/parameters.cxx:305 vncviewer/parameters.cxx:326
+#, c-format
+msgid "Failed to write parameter %s of type %s to the registry: %ld"
+msgstr "Gagal menulis parameter %s jenis %s ke registry: %ld"
+
+#: vncviewer/parameters.cxx:341 vncviewer/parameters.cxx:380
+#, c-format
+msgid "The name of the parameter %s was too large to read from the registry"
+msgstr "Nama parameter %s terlalu besar untuk dibaca dari registry"
+
+#: vncviewer/parameters.cxx:350 vncviewer/parameters.cxx:389
+#, c-format
+msgid "Failed to read parameter %s from the registry: %ld"
+msgstr "Gagal membaca parameter %s dari registry: %ld"
+
+#: vncviewer/parameters.cxx:359
+#, c-format
+msgid "The parameter %s was too large to read from the registry"
+msgstr "Parameter %s terlalu besar untuk dibaca dari registry"
+
+#: vncviewer/parameters.cxx:409
+#, c-format
+msgid "Failed to create registry key: %ld"
+msgstr "Gagal membuat kunci registry: %ld"
+
+#: vncviewer/parameters.cxx:423 vncviewer/parameters.cxx:472
+#: vncviewer/parameters.cxx:534 vncviewer/parameters.cxx:665
+#, c-format
+msgid "Unknown parameter type for parameter %s"
+msgstr "Tipe parameter tidak dikenal untuk parameter %s"
+
+#: vncviewer/parameters.cxx:430 vncviewer/parameters.cxx:479
+#, c-format
+msgid "Failed to close registry key: %ld"
+msgstr "Gagal menutup kunci registry: %ld"
+
+#: vncviewer/parameters.cxx:446
+#, c-format
+msgid "Failed to open registry key: %ld"
+msgstr "Gagal membuka kunci registry: %ld"
+
+#: vncviewer/parameters.cxx:503
+msgid "Failed to write configuration file, can't obtain home directory path."
+msgstr "Gagal menulis berkas konfigurasi, tak bisa memperoleh path direktori rumah."
+
+#: vncviewer/parameters.cxx:516
+#, c-format
+msgid "Failed to write configuration file, can't open %s: %s"
+msgstr "Gagal menulis berkas konfigurasi, tak bisa membuka %s: %s"
+
+#: vncviewer/parameters.cxx:559
+msgid "Failed to read configuration file, can't obtain home directory path."
+msgstr "Gagal membaca berkas konfigurasi, tak bisa memperoleh path direktori rumah."
+
+#: vncviewer/parameters.cxx:572
+#, c-format
+msgid "Failed to read configuration file, can't open %s: %s"
+msgstr "Gagal membaca berkas konfigurasi, tak bisa membuka %s: %s"
+
+#: vncviewer/parameters.cxx:585 vncviewer/parameters.cxx:590
+#: vncviewer/parameters.cxx:615 vncviewer/parameters.cxx:628
+#: vncviewer/parameters.cxx:644
+#, c-format
+msgid "Failed to read line %d in file %s: %s"
+msgstr "Gagal membaca baris %d dalam berkas %s: %s"
+
+#: vncviewer/parameters.cxx:591
+msgid "Line too long"
+msgstr "Baris terlalu panjang"
+
+#: vncviewer/parameters.cxx:598
+#, c-format
+msgid "Configuration file %s is in an invalid format"
+msgstr "Berkas konfigurasi %s berada dalam format yang tidak valid"
+
+#: vncviewer/parameters.cxx:616
+msgid "Invalid format"
+msgstr "Format tidak valid"
+
+#: vncviewer/parameters.cxx:629 vncviewer/parameters.cxx:645
+msgid "Invalid format or too large value"
+msgstr "Format tidak valid atau nilai terlalu besar"
+
+#: vncviewer/parameters.cxx:672
+#, c-format
+msgid "Unknown parameter %s on line %d in file %s"
+msgstr "Parameter tidak dikenal %s pada baris %d dalam berkas %s"
+
+#: vncviewer/vncviewer.cxx:100
+#, c-format
+msgid ""
+"TigerVNC Viewer %d-bit v%s\n"
+"Built on: %s\n"
+"Copyright (C) 1999-%d TigerVNC Team and many others (see README.txt)\n"
+"See http://www.tigervnc.org for information on TigerVNC."
+msgstr ""
+"Penampil TigerVNC %d-bit v%s\n"
+"Dibangun pada: %s\n"
+"Hak Cipta (C) 1999-%d Tim TigerVNC dan banyak lagi (lihat README.txt)\n"
+"Lihat http://www.tigervnc.org untuk informasi tentang TigerVNC."
+
+#: vncviewer/vncviewer.cxx:127
+msgid "About TigerVNC Viewer"
+msgstr "Tentang Penampil &TigerVNC"
+
+#: vncviewer/vncviewer.cxx:140
+msgid "Internal FLTK error. Exiting."
+msgstr "Kesalahan FLTK internal. Keluar."
+
+#: vncviewer/vncviewer.cxx:158 vncviewer/vncviewer.cxx:170
+#, c-format
+msgid "Error starting new TigerVNC Viewer: %s"
+msgstr "Kesalahan saat memulai Penampil TigerVNC baru: %s"
+
+#: vncviewer/vncviewer.cxx:179
+#, c-format
+msgid "Termination signal %d has been received. TigerVNC Viewer will now exit."
+msgstr "Sinyal pengakhiran %d telah diterima. Penampil TigerVNC sekarang akan keluar."
+
+#: vncviewer/vncviewer.cxx:271
+msgid "TigerVNC Viewer"
+msgstr "Penampil TigerVNC"
+
+#: vncviewer/vncviewer.cxx:279
+msgid "No"
+msgstr "Tidak"
+
+#: vncviewer/vncviewer.cxx:280
+msgid "Yes"
+msgstr "Ya"
+
+#: vncviewer/vncviewer.cxx:283
+msgid "Close"
+msgstr "Tutup"
+
+#: vncviewer/vncviewer.cxx:288
+msgid "About"
+msgstr "Tentang"
+
+#: vncviewer/vncviewer.cxx:291
+msgid "Hide"
+msgstr "Sembunyikan"
+
+#: vncviewer/vncviewer.cxx:294
+msgid "Quit"
+msgstr "Keluar"
+
+#: vncviewer/vncviewer.cxx:298
+msgid "Services"
+msgstr "Layanan"
+
+#: vncviewer/vncviewer.cxx:299
+msgid "Hide Others"
+msgstr "Sembunyikan Lainnya"
+
+#: vncviewer/vncviewer.cxx:300
+msgid "Show All"
+msgstr "Tampilkan Semua"
+
+#: vncviewer/vncviewer.cxx:309
+msgctxt "SysMenu|"
+msgid "&File"
+msgstr "&Berkas"
+
+#: vncviewer/vncviewer.cxx:312
+msgctxt "SysMenu|File|"
+msgid "&New Connection"
+msgstr "Ko&neksi Baru"
+
+#: vncviewer/vncviewer.cxx:324
+msgid "Could not create VNC home directory: can't obtain home directory path."
+msgstr "Tidak bisa membuat direktori rumah VNC: tak bisa memperoleh path direktori rumah."
+
+#: vncviewer/vncviewer.cxx:329
+#, c-format
+msgid "Could not create VNC home directory: %s."
+msgstr "Tidak bisa membuat direktori rumah VNC: %s."
+
+#. TRANSLATORS: "Parameters" are command line arguments, or settings
+#. from a file or the Windows registry.
+#: vncviewer/vncviewer.cxx:534 vncviewer/vncviewer.cxx:535
+msgid "Parameters -listen and -via are incompatible"
+msgstr "Parameter -listen dan -via tidak kompatibel"
+
+#: vncviewer/vncviewer.cxx:550
+#, c-format
+msgid "Listening on port %d"
+msgstr "Mendengarkan pada port %d"
diff --git a/tests/conv.cxx b/tests/conv.cxx
index 840f18d..3901b9d 100644
--- a/tests/conv.cxx
+++ b/tests/conv.cxx
@@ -72,9 +72,9 @@
   g = (p >> dstpf.greenShift) & dstpf.greenMax;
   b = (p >> dstpf.blueShift) & dstpf.blueMax;
 
-  r <<= 8 - dstpf.redBits;
-  g <<= 8 - dstpf.greenBits;
-  b <<= 8 - dstpf.blueBits;
+  r = (r * 255 + dstpf.redMax/2) / dstpf.redMax;
+  g = (g * 255 + dstpf.greenMax/2) / dstpf.greenMax;
+  b = (b * 255 + dstpf.blueMax/2) / dstpf.blueMax;
 
   // The allowed error depends on:
   //
diff --git a/unix/vncserver b/unix/vncserver
index 2ef436a..9e7a6ac 100755
--- a/unix/vncserver
+++ b/unix/vncserver
@@ -207,7 +207,7 @@
 # override these where present.
 $default_opts{desktop} = &quotedString($desktopName);
 $default_opts{httpd} = $vncJavaFiles if ($vncJavaFiles);
-$default_opts{auth} = $xauthorityFile;
+$default_opts{auth} = &quotedString($xauthorityFile);
 $default_opts{geometry} = $geometry if ($geometry);
 $default_opts{depth} = $depth if ($depth);
 $default_opts{pixelformat} = $pixelformat if ($pixelformat);
diff --git a/unix/x0vncserver/Image.cxx b/unix/x0vncserver/Image.cxx
index f1a4593..a5bd2b0 100644
--- a/unix/x0vncserver/Image.cxx
+++ b/unix/x0vncserver/Image.cxx
@@ -58,13 +58,13 @@
 static rfb::LogWriter vlog("Image");
 
 Image::Image(Display *d)
-  : xim(NULL), dpy(d), trueColor(true)
+  : xim(NULL), dpy(d)
 {
   imageCleanup.images.push_back(this);
 }
 
 Image::Image(Display *d, int width, int height)
-  : xim(NULL), dpy(d), trueColor(true)
+  : xim(NULL), dpy(d)
 {
   imageCleanup.images.push_back(this);
   Init(width, height);
@@ -73,7 +73,11 @@
 void Image::Init(int width, int height)
 {
   Visual* vis = DefaultVisual(dpy, DefaultScreen(dpy));
-  trueColor = (vis->c_class == TrueColor);
+
+  if (vis->c_class != TrueColor) {
+    vlog.error("pseudocolour not supported");
+    exit(1);
+  }
 
   xim = XCreateImage(dpy, vis, DefaultDepth(dpy, DefaultScreen(dpy)),
                      ZPixmap, 0, 0, width, height, BitmapPad(dpy), 0);
@@ -239,7 +243,10 @@
     depth = vinfo->depth;
   }
 
-  trueColor = (visual->c_class == TrueColor);
+  if (visual->c_class != TrueColor) {
+    vlog.error("pseudocolour not supported");
+    exit(1);
+  }
 
   shminfo = new XShmSegmentInfo;
 
diff --git a/unix/x0vncserver/Image.h b/unix/x0vncserver/Image.h
index 23502eb..bf62e7d 100644
--- a/unix/x0vncserver/Image.h
+++ b/unix/x0vncserver/Image.h
@@ -38,8 +38,6 @@
   Image(Display *d, int width, int height);
   virtual ~Image();
 
-  bool isTrueColor() const { return trueColor; }
-
   virtual const char *className() const {
     return "Image";
   }
@@ -84,8 +82,6 @@
                   int w, int h);
 
   Display *dpy;
-  bool trueColor;
-
 };
 
 //
diff --git a/unix/x0vncserver/XDesktop.cxx b/unix/x0vncserver/XDesktop.cxx
index 4a989d3..748796b 100644
--- a/unix/x0vncserver/XDesktop.cxx
+++ b/unix/x0vncserver/XDesktop.cxx
@@ -56,271 +56,275 @@
 };
 
 XDesktop::XDesktop(Display* dpy_, Geometry *geometry_)
-    : dpy(dpy_), geometry(geometry_), pb(0), server(0),
-      oldButtonMask(0), haveXtest(false), haveDamage(false),
-      maxButtons(0), running(false), ledMasks(), ledState(0),
-      codeMap(0), codeMapLen(0)
+  : dpy(dpy_), geometry(geometry_), pb(0), server(0),
+    oldButtonMask(0), haveXtest(false), haveDamage(false),
+    maxButtons(0), running(false), ledMasks(), ledState(0),
+    codeMap(0), codeMapLen(0)
 {
-    int major, minor;
+  int major, minor;
 
-    int xkbOpcode, xkbErrorBase;
+  int xkbOpcode, xkbErrorBase;
 
-    major = XkbMajorVersion;
-    minor = XkbMinorVersion;
-    if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
-                           &xkbErrorBase, &major, &minor)) {
-      vlog.error("XKEYBOARD extension not present");
-      throw Exception();
-    }
+  major = XkbMajorVersion;
+  minor = XkbMinorVersion;
+  if (!XkbQueryExtension(dpy, &xkbOpcode, &xkbEventBase,
+                         &xkbErrorBase, &major, &minor)) {
+    vlog.error("XKEYBOARD extension not present");
+    throw Exception();
+  }
 
-    XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
-                    XkbIndicatorStateNotifyMask);
+  XkbSelectEvents(dpy, XkbUseCoreKbd, XkbIndicatorStateNotifyMask,
+                  XkbIndicatorStateNotifyMask);
 
-    // figure out bit masks for the indicators we are interested in
-    for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
-      Atom a;
-      int shift;
-      Bool on;
+  // figure out bit masks for the indicators we are interested in
+  for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
+    Atom a;
+    int shift;
+    Bool on;
 
-      a = XInternAtom(dpy, ledNames[i], True);
-      if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
-        continue;
+    a = XInternAtom(dpy, ledNames[i], True);
+    if (!a || !XkbGetNamedIndicator(dpy, a, &shift, &on, NULL, NULL))
+      continue;
 
-      ledMasks[i] = 1u << shift;
-      vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
-      if (on)
-        ledState |= 1u << i;
-    }
+    ledMasks[i] = 1u << shift;
+    vlog.debug("Mask for '%s' is 0x%x", ledNames[i], ledMasks[i]);
+    if (on)
+      ledState |= 1u << i;
+  }
 
-    // X11 unfortunately uses keyboard driver specific keycodes and provides no
-    // direct way to query this, so guess based on the keyboard mapping
-    XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
-    if (desc && desc->names) {
-      char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
+  // X11 unfortunately uses keyboard driver specific keycodes and provides no
+  // direct way to query this, so guess based on the keyboard mapping
+  XkbDescPtr desc = XkbGetKeyboard(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+  if (desc && desc->names) {
+    char *keycodes = XGetAtomName(dpy, desc->names->keycodes);
 
-      if (keycodes) {
-        if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
-          codeMap = code_map_qnum_to_xorgevdev;
-          codeMapLen = code_map_qnum_to_xorgevdev_len;
-          vlog.info("Using evdev codemap\n");
-        } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
-          codeMap = code_map_qnum_to_xorgkbd;
-          codeMapLen = code_map_qnum_to_xorgkbd_len;
-          vlog.info("Using xorgkbd codemap\n");
-        } else {
-          vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
-        }
-        XFree(keycodes);
+    if (keycodes) {
+      if (strncmp("evdev", keycodes, strlen("evdev")) == 0) {
+        codeMap = code_map_qnum_to_xorgevdev;
+        codeMapLen = code_map_qnum_to_xorgevdev_len;
+        vlog.info("Using evdev codemap\n");
+      } else if (strncmp("xfree86", keycodes, strlen("xfree86")) == 0) {
+        codeMap = code_map_qnum_to_xorgkbd;
+        codeMapLen = code_map_qnum_to_xorgkbd_len;
+        vlog.info("Using xorgkbd codemap\n");
       } else {
-        vlog.debug("Unable to get keycode map\n");
+        vlog.info("Unknown keycode '%s', no codemap\n", keycodes);
       }
-
-      XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
-    }
-
-#ifdef HAVE_XTEST
-    int xtestEventBase;
-    int xtestErrorBase;
-
-    if (XTestQueryExtension(dpy, &xtestEventBase,
-                            &xtestErrorBase, &major, &minor)) {
-      XTestGrabControl(dpy, True);
-      vlog.info("XTest extension present - version %d.%d",major,minor);
-      haveXtest = true;
+      XFree(keycodes);
     } else {
-#endif
-      vlog.info("XTest extension not present");
-      vlog.info("Unable to inject events or display while server is grabbed");
-#ifdef HAVE_XTEST
+      vlog.debug("Unable to get keycode map\n");
     }
+
+    XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
+  }
+
+#ifdef HAVE_XTEST
+  int xtestEventBase;
+  int xtestErrorBase;
+
+  if (XTestQueryExtension(dpy, &xtestEventBase,
+                          &xtestErrorBase, &major, &minor)) {
+    XTestGrabControl(dpy, True);
+    vlog.info("XTest extension present - version %d.%d",major,minor);
+    haveXtest = true;
+  } else {
+#endif
+    vlog.info("XTest extension not present");
+    vlog.info("Unable to inject events or display while server is grabbed");
+#ifdef HAVE_XTEST
+  }
 #endif
 
 #ifdef HAVE_XDAMAGE
-    int xdamageErrorBase;
+  int xdamageErrorBase;
 
-    if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
-      haveDamage = true;
-    } else {
+  if (XDamageQueryExtension(dpy, &xdamageEventBase, &xdamageErrorBase)) {
+    haveDamage = true;
+  } else {
 #endif
-      vlog.info("DAMAGE extension not present");
-      vlog.info("Will have to poll screen for changes");
+    vlog.info("DAMAGE extension not present");
+    vlog.info("Will have to poll screen for changes");
 #ifdef HAVE_XDAMAGE
-    }
+  }
 #endif
 
 #ifdef HAVE_XFIXES
-    int xfixesErrorBase;
+  int xfixesErrorBase;
 
-    if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
-      XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
-                              XFixesDisplayCursorNotifyMask);
-    } else {
+  if (XFixesQueryExtension(dpy, &xfixesEventBase, &xfixesErrorBase)) {
+    XFixesSelectCursorInput(dpy, DefaultRootWindow(dpy),
+                            XFixesDisplayCursorNotifyMask);
+  } else {
 #endif
-      vlog.info("XFIXES extension not present");
-      vlog.info("Will not be able to display cursors");
+    vlog.info("XFIXES extension not present");
+    vlog.info("Will not be able to display cursors");
 #ifdef HAVE_XFIXES
-    }
+  }
 #endif
 
-    TXWindow::setGlobalEventHandler(this);
+  TXWindow::setGlobalEventHandler(this);
 }
 
 XDesktop::~XDesktop() {
+  if (running)
     stop();
 }
 
 
 void XDesktop::poll() {
-    if (pb and not haveDamage)
-      pb->poll(server);
-    if (running) {
-      Window root, child;
-      int x, y, wx, wy;
-      unsigned int mask;
-      XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
-                    &x, &y, &wx, &wy, &mask);
-      server->setCursorPos(rfb::Point(x, y));
-    }
+  if (pb and not haveDamage)
+    pb->poll(server);
+  if (running) {
+    Window root, child;
+    int x, y, wx, wy;
+    unsigned int mask;
+    XQueryPointer(dpy, DefaultRootWindow(dpy), &root, &child,
+                  &x, &y, &wx, &wy, &mask);
+    server->setCursorPos(rfb::Point(x, y));
   }
+}
 
 
 void XDesktop::start(VNCServer* vs) {
 
-    // Determine actual number of buttons of the X pointer device.
-    unsigned char btnMap[8];
-    int numButtons = XGetPointerMapping(dpy, btnMap, 8);
-    maxButtons = (numButtons > 8) ? 8 : numButtons;
-    vlog.info("Enabling %d button%s of X pointer device",
-              maxButtons, (maxButtons != 1) ? "s" : "");
+  // Determine actual number of buttons of the X pointer device.
+  unsigned char btnMap[8];
+  int numButtons = XGetPointerMapping(dpy, btnMap, 8);
+  maxButtons = (numButtons > 8) ? 8 : numButtons;
+  vlog.info("Enabling %d button%s of X pointer device",
+            maxButtons, (maxButtons != 1) ? "s" : "");
 
-    // Create an ImageFactory instance for producing Image objects.
-    ImageFactory factory((bool)useShm);
+  // Create an ImageFactory instance for producing Image objects.
+  ImageFactory factory((bool)useShm);
 
-    // Create pixel buffer and provide it to the server object.
-    pb = new XPixelBuffer(dpy, factory, geometry->getRect());
-    vlog.info("Allocated %s", pb->getImage()->classDesc());
+  // Create pixel buffer and provide it to the server object.
+  pb = new XPixelBuffer(dpy, factory, geometry->getRect());
+  vlog.info("Allocated %s", pb->getImage()->classDesc());
 
-    server = (VNCServerST *)vs;
-    server->setPixelBuffer(pb);
+  server = (VNCServerST *)vs;
+  server->setPixelBuffer(pb);
 
 #ifdef HAVE_XDAMAGE
-    if (haveDamage) {
-      damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
-                             XDamageReportRawRectangles);
-    }
+  if (haveDamage) {
+    damage = XDamageCreate(dpy, DefaultRootWindow(dpy),
+                           XDamageReportRawRectangles);
+  }
 #endif
 
 #ifdef HAVE_XFIXES
-    setCursor();
+  setCursor();
 #endif
 
-    server->setLEDState(ledState);
+  server->setLEDState(ledState);
 
-    running = true;
+  running = true;
 }
 
 void XDesktop::stop() {
-    running = false;
+  running = false;
 
 #ifdef HAVE_XDAMAGE
-    if (haveDamage)
-      XDamageDestroy(dpy, damage);
+  if (haveDamage)
+    XDamageDestroy(dpy, damage);
 #endif
 
-    delete pb;
-    pb = 0;
+  server->setPixelBuffer(0);
+  server = 0;
+
+  delete pb;
+  pb = 0;
 }
 
 bool XDesktop::isRunning() {
-    return running;
+  return running;
 }
 
 void XDesktop::pointerEvent(const Point& pos, int buttonMask) {
 #ifdef HAVE_XTEST
-    if (!haveXtest) return;
-    XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
-                         geometry->offsetLeft() + pos.x,
-                         geometry->offsetTop() + pos.y,
-                         CurrentTime);
-    if (buttonMask != oldButtonMask) {
-      for (int i = 0; i < maxButtons; i++) {
-	if ((buttonMask ^ oldButtonMask) & (1<<i)) {
-          if (buttonMask & (1<<i)) {
-            XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
-          } else {
-            XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
-          }
+  if (!haveXtest) return;
+  XTestFakeMotionEvent(dpy, DefaultScreen(dpy),
+                       geometry->offsetLeft() + pos.x,
+                       geometry->offsetTop() + pos.y,
+                       CurrentTime);
+  if (buttonMask != oldButtonMask) {
+    for (int i = 0; i < maxButtons; i++) {
+      if ((buttonMask ^ oldButtonMask) & (1<<i)) {
+        if (buttonMask & (1<<i)) {
+          XTestFakeButtonEvent(dpy, i+1, True, CurrentTime);
+        } else {
+          XTestFakeButtonEvent(dpy, i+1, False, CurrentTime);
         }
       }
     }
-    oldButtonMask = buttonMask;
+  }
+  oldButtonMask = buttonMask;
 #endif
 }
 
 #ifdef HAVE_XTEST
 KeyCode XDesktop::XkbKeysymToKeycode(Display* dpy, KeySym keysym) {
-    XkbDescPtr xkb;
-    XkbStateRec state;
-    unsigned keycode;
+  XkbDescPtr xkb;
+  XkbStateRec state;
+  unsigned keycode;
 
-    xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
-    if (!xkb)
-      return 0;
+  xkb = XkbGetMap(dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+  if (!xkb)
+    return 0;
 
-    XkbGetState(dpy, XkbUseCoreKbd, &state);
+  XkbGetState(dpy, XkbUseCoreKbd, &state);
 
-    for (keycode = xkb->min_key_code;
-         keycode <= xkb->max_key_code;
-         keycode++) {
-      KeySym cursym;
-      unsigned int mods, out_mods;
-      // XkbStateFieldFromRec() doesn't work properly because
-      // state.lookup_mods isn't properly updated, so we do this manually
-      mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
-      XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
-      if (cursym == keysym)
-        break;
-    }
+  for (keycode = xkb->min_key_code;
+       keycode <= xkb->max_key_code;
+       keycode++) {
+    KeySym cursym;
+    unsigned int mods, out_mods;
+    // XkbStateFieldFromRec() doesn't work properly because
+    // state.lookup_mods isn't properly updated, so we do this manually
+    mods = XkbBuildCoreState(XkbStateMods(&state), state.group);
+    XkbTranslateKeyCode(xkb, keycode, mods, &out_mods, &cursym);
+    if (cursym == keysym)
+      break;
+  }
 
-    if (keycode > xkb->max_key_code)
-      keycode = 0;
+  if (keycode > xkb->max_key_code)
+    keycode = 0;
 
-    XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
+  XkbFreeKeyboard(xkb, XkbAllComponentsMask, True);
 
-    return keycode;
+  return keycode;
 }
 #endif
 
 void XDesktop::keyEvent(rdr::U32 keysym, rdr::U32 xtcode, bool down) {
 #ifdef HAVE_XTEST
-    int keycode = 0;
+  int keycode = 0;
 
-    if (!haveXtest)
-      return;
+  if (!haveXtest)
+    return;
 
-    // Use scan code if provided and mapping exists
-    if (codeMap && rawKeyboard && xtcode < codeMapLen)
-        keycode = codeMap[xtcode];
+  // Use scan code if provided and mapping exists
+  if (codeMap && rawKeyboard && xtcode < codeMapLen)
+      keycode = codeMap[xtcode];
 
-    if (!keycode) {
-      if (pressedKeys.find(keysym) != pressedKeys.end())
-        keycode = pressedKeys[keysym];
-      else {
-        // XKeysymToKeycode() doesn't respect state, so we have to use
-        // something slightly more complex
-        keycode = XkbKeysymToKeycode(dpy, keysym);
-      }
+  if (!keycode) {
+    if (pressedKeys.find(keysym) != pressedKeys.end())
+      keycode = pressedKeys[keysym];
+    else {
+      // XKeysymToKeycode() doesn't respect state, so we have to use
+      // something slightly more complex
+      keycode = XkbKeysymToKeycode(dpy, keysym);
     }
+  }
 
-    if (!keycode)
-      return;
+  if (!keycode)
+    return;
 
-    if (down)
-      pressedKeys[keysym] = keycode;
-    else
-      pressedKeys.erase(keysym);
+  if (down)
+    pressedKeys[keysym] = keycode;
+  else
+    pressedKeys.erase(keysym);
 
-    XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
+  XTestFakeKeyEvent(dpy, keycode, down, CurrentTime);
 #endif
 }
 
@@ -329,102 +333,102 @@
 
 
 bool XDesktop::handleGlobalEvent(XEvent* ev) {
-    if (ev->type == xkbEventBase + XkbEventCode) {
-      XkbEvent *kb = (XkbEvent *)ev;
+  if (ev->type == xkbEventBase + XkbEventCode) {
+    XkbEvent *kb = (XkbEvent *)ev;
 
-      if (kb->any.xkb_type != XkbIndicatorStateNotify)
-        return false;
+    if (kb->any.xkb_type != XkbIndicatorStateNotify)
+      return false;
 
-      vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
+    vlog.debug("Got indicator update, mask is now 0x%x", kb->indicators.state);
 
-      ledState = 0;
-      for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
-        if (kb->indicators.state & ledMasks[i])
-          ledState |= 1u << i;
-      }
-
-      if (running)
-        server->setLEDState(ledState);
-
-      return true;
-#ifdef HAVE_XDAMAGE
-    } else if (ev->type == xdamageEventBase) {
-      XDamageNotifyEvent* dev;
-      Rect rect;
-
-      if (!running)
-        return true;
-
-      dev = (XDamageNotifyEvent*)ev;
-      rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
-      server->add_changed(rect);
-
-      return true;
-#endif
-#ifdef HAVE_XFIXES
-    } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
-      XFixesCursorNotifyEvent* cev;
-
-      if (!running)
-        return true;
-
-      cev = (XFixesCursorNotifyEvent*)ev;
-
-      if (cev->subtype != XFixesDisplayCursorNotify)
-        return false;
-
-      return setCursor();
-#endif
+    ledState = 0;
+    for (int i = 0; i < XDESKTOP_N_LEDS; i++) {
+      if (kb->indicators.state & ledMasks[i])
+        ledState |= 1u << i;
     }
 
-    return false;
+    if (running)
+      server->setLEDState(ledState);
+
+    return true;
+#ifdef HAVE_XDAMAGE
+  } else if (ev->type == xdamageEventBase) {
+    XDamageNotifyEvent* dev;
+    Rect rect;
+
+    if (!running)
+      return true;
+
+    dev = (XDamageNotifyEvent*)ev;
+    rect.setXYWH(dev->area.x, dev->area.y, dev->area.width, dev->area.height);
+    server->add_changed(rect);
+
+    return true;
+#endif
+#ifdef HAVE_XFIXES
+  } else if (ev->type == xfixesEventBase + XFixesCursorNotify) {
+    XFixesCursorNotifyEvent* cev;
+
+    if (!running)
+      return true;
+
+    cev = (XFixesCursorNotifyEvent*)ev;
+
+    if (cev->subtype != XFixesDisplayCursorNotify)
+      return false;
+
+    return setCursor();
+#endif
+  }
+
+  return false;
 }
 
 bool XDesktop::setCursor()
 {
-      XFixesCursorImage *cim;
+  XFixesCursorImage *cim;
 
-      cim = XFixesGetCursorImage(dpy);
-      if (cim == NULL)
-        return false;
+  cim = XFixesGetCursorImage(dpy);
+  if (cim == NULL)
+    return false;
 
-      // Copied from XserverDesktop::setCursor() in
-      // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
-      // handle long -> U32 conversion for 64-bit Xlib
-      rdr::U8* cursorData;
-      rdr::U8 *out;
-      const unsigned long *pixels;
+  // Copied from XserverDesktop::setCursor() in
+  // unix/xserver/hw/vnc/XserverDesktop.cc and adapted to
+  // handle long -> U32 conversion for 64-bit Xlib
+  rdr::U8* cursorData;
+  rdr::U8 *out;
+  const unsigned long *pixels;
 
-      cursorData = new rdr::U8[cim->width * cim->height * 4];
+  cursorData = new rdr::U8[cim->width * cim->height * 4];
 
-      // Un-premultiply alpha
-      pixels = cim->pixels;
-      out = cursorData;
-      for (int y = 0; y < cim->height; y++) {
-        for (int x = 0; x < cim->width; x++) {
-          rdr::U8 alpha;
-          rdr::U32 pixel = *pixels++;
-          rdr::U8 *in = (rdr::U8 *) &pixel;
+  // Un-premultiply alpha
+  pixels = cim->pixels;
+  out = cursorData;
+  for (int y = 0; y < cim->height; y++) {
+    for (int x = 0; x < cim->width; x++) {
+      rdr::U8 alpha;
+      rdr::U32 pixel = *pixels++;
+      rdr::U8 *in = (rdr::U8 *) &pixel;
 
-          alpha = in[3];
-          if (alpha == 0)
-            alpha = 1; // Avoid division by zero
+      alpha = in[3];
+      if (alpha == 0)
+        alpha = 1; // Avoid division by zero
 
-          *out++ = (unsigned)*in++ * 255/alpha;
-          *out++ = (unsigned)*in++ * 255/alpha;
-          *out++ = (unsigned)*in++ * 255/alpha;
-          *out++ = *in++;
-        }
-      }
+      *out++ = (unsigned)*in++ * 255/alpha;
+      *out++ = (unsigned)*in++ * 255/alpha;
+      *out++ = (unsigned)*in++ * 255/alpha;
+      *out++ = *in++;
+    }
+  }
 
-      try {
-        server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
-                          cursorData);
-      } catch (rdr::Exception& e) {
-        vlog.error("XserverDesktop::setCursor: %s",e.str());
-      }
+  try {
+    server->setCursor(cim->width, cim->height, Point(cim->xhot, cim->yhot),
+                      cursorData);
+  } catch (rdr::Exception& e) {
+    vlog.error("XserverDesktop::setCursor: %s",e.str());
+  }
 
-      delete [] cursorData;
-      XFree(cim);
-      return true;
+  delete [] cursorData;
+  XFree(cim);
+  return true;
 }
diff --git a/unix/x0vncserver/XPixelBuffer.cxx b/unix/x0vncserver/XPixelBuffer.cxx
index f464182..4769b65 100644
--- a/unix/x0vncserver/XPixelBuffer.cxx
+++ b/unix/x0vncserver/XPixelBuffer.cxx
@@ -41,7 +41,7 @@
   format = PixelFormat(m_image->xim->bits_per_pixel,
                        m_image->xim->depth,
                        (m_image->xim->byte_order == MSBFirst),
-                       m_image->isTrueColor(),
+                       true,
                        m_image->xim->red_mask   >> (ffs(m_image->xim->red_mask) - 1),
                        m_image->xim->green_mask >> (ffs(m_image->xim->green_mask) - 1),
                        m_image->xim->blue_mask  >> (ffs(m_image->xim->blue_mask) - 1),
diff --git a/vncviewer/DesktopWindow.cxx b/vncviewer/DesktopWindow.cxx
index 7652779..47bdd5a 100644
--- a/vncviewer/DesktopWindow.cxx
+++ b/vncviewer/DesktopWindow.cxx
@@ -1381,13 +1381,13 @@
   fl_draw(buffer, 5, statsHeight - 5);
 
   fl_color(FL_YELLOW);
-  siPrefix(self->stats[statsCount-1].pps * 8, "pix/s",
+  siPrefix(self->stats[statsCount-1].pps, "pix/s",
            buffer, sizeof(buffer), 3);
   fl_draw(buffer, 5 + (statsWidth-10)/3, statsHeight - 5);
 
   fl_color(FL_RED);
-  iecPrefix(self->stats[statsCount-1].bps * 8, "Bps",
-            buffer, sizeof(buffer), 3);
+  siPrefix(self->stats[statsCount-1].bps * 8, "bps",
+           buffer, sizeof(buffer), 3);
   fl_draw(buffer, 5 + (statsWidth-10)*2/3, statsHeight - 5);
 
   image = surface->image();