Bool rfbSendFramebufferUpdate(rfbClientPtr cl)
{
  ScreenPtr pScreen = screenInfo.screens[0];
  int i;
  int nUpdateRegionRects;
  rfbFramebufferUpdateMsg *fu = (rfbFramebufferUpdateMsg *)updateBuf;
  RegionRec _updateRegion, *updateRegion = &_updateRegion, updateCopyRegion,
    idRegion;
  Bool emptyUpdateRegion = FALSE;
  rfbClientPtr cl2;
  int dx, dy;
  Bool sendCursorShape = FALSE;
  Bool sendCursorPos = FALSE;
  double tUpdateStart = 0.0;

  TimerCancel(cl->updateTimer);

  /*
   * 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.
   */

  if (cl->syncFence) return TRUE;

  if (cl->state != RFB_NORMAL) return TRUE;

  if (rfbProfile) {
    tUpdateStart = gettime();
    if (tStart < 0.) tStart = tUpdateStart;
  }

  /* Check that we actually have some space on the link and retry in a
     bit if things are congested. */

  if (rfbCongestionControl && rfbIsCongested(cl)) {
    cl->updateTimer = TimerSet(cl->updateTimer, 0, 50, updateCallback, cl);
    return TRUE;
  }

  /* In continuous mode, we will be outputting at least three distinct
     messages.  We need to aggregate these in order to not clog up TCP's
     congestion window. */

  rfbCorkSock(cl->sock);

  if (cl->pendingExtDesktopResize) {
    if (!rfbSendExtDesktopSize(cl)) return FALSE;
    cl->pendingExtDesktopResize = FALSE;
  }

  if (cl->pendingDesktopResize) {
    if (!rfbSendDesktopSize(cl)) return FALSE;
    cl->pendingDesktopResize = FALSE;
  }

  if (rfbFB.blockUpdates) {
    rfbUncorkSock(cl->sock);
    return TRUE;
  }

  /*
   * If this client understands cursor shape updates, cursor should be
   * removed from the framebuffer. Otherwise, make sure it's put up.
   */

  if (cl->enableCursorShapeUpdates) {
    if (rfbFB.cursorIsDrawn)
      rfbSpriteRemoveCursorAllDev(pScreen);
    if (!rfbFB.cursorIsDrawn && cl->cursorWasChanged)
      sendCursorShape = TRUE;
  } else {
    if (!rfbFB.cursorIsDrawn)
      rfbSpriteRestoreCursorAllDev(pScreen);
  }

  /*
   * Do we plan to send cursor position update?
   */

  if (cl->enableCursorPosUpdates && cl->cursorWasMoved)
    sendCursorPos = TRUE;

  /*
   * The modifiedRegion may overlap the destination copyRegion.  We remove
   * any overlapping bits from the copyRegion (since they'd only be
   * overwritten anyway).
   */

  REGION_SUBTRACT(pScreen, &cl->copyRegion, &cl->copyRegion,
                  &cl->modifiedRegion);

  /*
   * The client is interested in the region requestedRegion.  The region
   * which should be updated now is the intersection of requestedRegion
   * and the union of modifiedRegion and copyRegion.  If it's empty then
   * no update is needed.
   */

  REGION_INIT(pScreen, updateRegion, NullBox, 0);
  REGION_UNION(pScreen, updateRegion, &cl->copyRegion, &cl->modifiedRegion);

  if (cl->continuousUpdates)
    REGION_UNION(pScreen, &cl->requestedRegion, &cl->requestedRegion,
                 &cl->cuRegion);

  REGION_INTERSECT(pScreen, updateRegion, &cl->requestedRegion, updateRegion);

  if (!REGION_NOTEMPTY(pScreen, updateRegion) && !sendCursorShape &&
      !sendCursorPos) {
    REGION_UNINIT(pScreen, updateRegion);
    return TRUE;
  }

  /*
   * We assume that the client doesn't have any pixel data outside the
   * requestedRegion.  In other words, both the source and destination of a
   * copy must lie within requestedRegion.  So the region we can send as a
   * copy is the intersection of the copyRegion with both the requestedRegion
   * and the requestedRegion translated by the amount of the copy.  We set
   * updateCopyRegion to this.
   */

  REGION_INIT(pScreen, &updateCopyRegion, NullBox, 0);
  REGION_INTERSECT(pScreen, &updateCopyRegion, &cl->copyRegion,
                   &cl->requestedRegion);
  REGION_TRANSLATE(pScreen, &cl->requestedRegion, cl->copyDX, cl->copyDY);
  REGION_INTERSECT(pScreen, &updateCopyRegion, &updateCopyRegion,
                   &cl->requestedRegion);
  dx = cl->copyDX;
  dy = cl->copyDY;

  /*
   * Next we remove updateCopyRegion from updateRegion so that updateRegion
   * is the part of this update which is sent as ordinary pixel data (i.e not
   * a copy).
   */

  REGION_SUBTRACT(pScreen, updateRegion, updateRegion, &updateCopyRegion);

  /*
   * Finally we leave modifiedRegion to be the remainder (if any) of parts of
   * the screen which are modified but outside the requestedRegion.  We also
   * empty both the requestedRegion and the copyRegion - note that we never
   * carry over a copyRegion for a future update.
   */

  REGION_UNION(pScreen, &cl->modifiedRegion, &cl->modifiedRegion,
               &cl->copyRegion);
  REGION_SUBTRACT(pScreen, &cl->modifiedRegion, &cl->modifiedRegion,
                  updateRegion);
  REGION_SUBTRACT(pScreen, &cl->modifiedRegion, &cl->modifiedRegion,
                  &updateCopyRegion);

  REGION_EMPTY(pScreen, &cl->requestedRegion);
  REGION_EMPTY(pScreen, &cl->copyRegion);
  cl->copyDX = 0;
  cl->copyDY = 0;

  /*
   * Now send the update.
   */

  if (REGION_NUM_RECTS(updateRegion) > rfbCombineRect) {
    RegionRec combinedUpdateRegion;
    REGION_INIT(pScreen, &combinedUpdateRegion,
                REGION_EXTENTS(pScreen, updateRegion), 1);
    REGION_UNINIT(pScreen, updateRegion);
    REGION_INIT(pScreen, updateRegion,
                REGION_EXTENTS(pScreen, &combinedUpdateRegion), 1);
    REGION_UNINIT(pScreen, &combinedUpdateRegion);
  }

  if ((updateRegion->extents.x2 > pScreen->width ||
       updateRegion->extents.y2 > pScreen->height) &&
      REGION_NUM_RECTS(updateRegion) > 0) {
    rfbLog("WARNING: Framebuffer update at %d,%d with dimensions %dx%d has been clipped to the screen boundaries\n",
           updateRegion->extents.x1, updateRegion->extents.y1,
           updateRegion->extents.x2 - updateRegion->extents.x1,
           updateRegion->extents.y2 - updateRegion->extents.y1);
    ClipToScreen(pScreen, updateRegion);
  }

  if (cl->compareFB) {
    if ((cl->ifRegion.extents.x2 > pScreen->width ||
         cl->ifRegion.extents.y2 > pScreen->height) &&
        REGION_NUM_RECTS(&cl->ifRegion) > 0)
      ClipToScreen(pScreen, &cl->ifRegion);

    updateRegion = &cl->ifRegion;
    emptyUpdateRegion = TRUE;
    if (rfbInterframeDebug)
      REGION_INIT(pScreen, &idRegion, NullBox, 0);
    for (i = 0; i < REGION_NUM_RECTS(&_updateRegion); i++) {
      int x = REGION_RECTS(&_updateRegion)[i].x1;
      int y = REGION_RECTS(&_updateRegion)[i].y1;
      int w = REGION_RECTS(&_updateRegion)[i].x2 - x;
      int h = REGION_RECTS(&_updateRegion)[i].y2 - y;
      int pitch = rfbFB.paddedWidthInBytes;
      int ps = rfbServerFormat.bitsPerPixel / 8;
      char *src = &rfbFB.pfbMemory[y * pitch + x * ps];
      char *dst = &cl->compareFB[y * pitch + x * ps];
      int row, col;
      int hBlockSize = rfbICEBlockSize == 0 ? w : rfbICEBlockSize;
      int vBlockSize = rfbICEBlockSize == 0 ? h : rfbICEBlockSize;

      for (row = 0; row < h; row += vBlockSize) {
        for (col = 0; col < w; col += hBlockSize) {

          Bool different = FALSE;
          int compareWidth = min(hBlockSize, w - col);
          int compareHeight = min(vBlockSize, h - row);
          int rows = compareHeight;
          char *srcPtr = &src[row * pitch + col * ps];
          char *dstPtr = &dst[row * pitch + col * ps];

          while (rows--) {
            if (cl->firstCompare ||
                memcmp(srcPtr, dstPtr, compareWidth * ps)) {
              memcpy(dstPtr, srcPtr, compareWidth * ps);
              different = TRUE;
            }
            srcPtr += pitch;
            dstPtr += pitch;
          }
          if (different || rfbInterframeDebug) {
            RegionRec tmpRegion;
            BoxRec box;
            box.x1 = x + col;
            box.y1 = y + row;
            box.x2 = box.x1 + compareWidth;
            box.y2 = box.y1 + compareHeight;

            REGION_INIT(pScreen, &tmpRegion, &box, 1);
            if (!different && rfbInterframeDebug &&
                !RECT_IN_REGION(pScreen, &cl->ifRegion, &box)) {
              int pad = pitch - compareWidth * ps;

              dstPtr = &dst[row * pitch + col * ps];
              REGION_UNION(pScreen, &idRegion, &idRegion, &tmpRegion);
              rows = compareHeight;

              while (rows--) {
                char *endOfRow = &dstPtr[compareWidth * ps];
                while (dstPtr < endOfRow)
                  *dstPtr++ ^= 0xFF;
                dstPtr += pad;
              }
            }
            REGION_UNION(pScreen, &cl->ifRegion, &cl->ifRegion, &tmpRegion);
            REGION_UNINIT(pScreen, &tmpRegion);
          }
          if (!different && rfbProfile) {
            idmpixels += (double)(compareWidth * compareHeight) / 1000000.;
            if (!rfbInterframeDebug)
              mpixels += (double)(compareWidth * compareHeight) / 1000000.;
          }
        }
      }
    }
    REGION_UNINIT(pScreen, &_updateRegion);
    REGION_NULL(pScreen, &_updateRegion);
    cl->firstCompare = FALSE;

    /* The Windows TurboVNC Viewer (and probably some other VNC viewers as
       well) will ignore any empty FBUs and stop sending FBURs when it
       receives one.  If CU is not active, then this causes the viewer to
       stop receiving updates until something else, such as a mouse cursor
       change, triggers a new FBUR.  Thus, if the ICE culls all of the
       pixels in this update, we send a 1-pixel FBU rather than an empty
       one. */
    if (REGION_NUM_RECTS(updateRegion) == 0) {
      BoxRec box;
      box.x1 = box.y1 = 0;
      box.x2 = box.y2 = 1;
      REGION_UNINIT(pScreen, updateRegion);
      REGION_INIT(pScreen, updateRegion, &box, 1);
    }
  }

  if (!rfbSendRTTPing(cl))
    goto abort;

  cl->rfbFramebufferUpdateMessagesSent++;

  if (cl->preferredEncoding == rfbEncodingCoRRE) {
    nUpdateRegionRects = 0;

    for (i = 0; i < REGION_NUM_RECTS(updateRegion); i++) {
      int x = REGION_RECTS(updateRegion)[i].x1;
      int y = REGION_RECTS(updateRegion)[i].y1;
      int w = REGION_RECTS(updateRegion)[i].x2 - x;
      int h = REGION_RECTS(updateRegion)[i].y2 - y;
      nUpdateRegionRects += (((w - 1) / cl->correMaxWidth + 1) *
                             ((h - 1) / cl->correMaxHeight + 1));
    }
  } else if (cl->preferredEncoding == rfbEncodingZlib) {
    nUpdateRegionRects = 0;

    for (i = 0; i < REGION_NUM_RECTS(updateRegion); i++) {
      int x = REGION_RECTS(updateRegion)[i].x1;
      int y = REGION_RECTS(updateRegion)[i].y1;
      int w = REGION_RECTS(updateRegion)[i].x2 - x;
      int h = REGION_RECTS(updateRegion)[i].y2 - y;
      nUpdateRegionRects += (((h - 1) / (ZLIB_MAX_SIZE(w) / w)) + 1);
    }
  } else if (cl->preferredEncoding == rfbEncodingTight) {
    nUpdateRegionRects = 0;

    for (i = 0; i < REGION_NUM_RECTS(updateRegion); i++) {
      int x = REGION_RECTS(updateRegion)[i].x1;
      int y = REGION_RECTS(updateRegion)[i].y1;
      int w = REGION_RECTS(updateRegion)[i].x2 - x;
      int h = REGION_RECTS(updateRegion)[i].y2 - y;
      int n = rfbNumCodedRectsTight(cl, x, y, w, h);
      if (n == 0) {
        nUpdateRegionRects = 0xFFFF;
        break;
      }
      nUpdateRegionRects += n;
    }
  } else {
    nUpdateRegionRects = REGION_NUM_RECTS(updateRegion);
  }

  fu->type = rfbFramebufferUpdate;
  if (nUpdateRegionRects != 0xFFFF) {
    fu->nRects = Swap16IfLE(REGION_NUM_RECTS(&updateCopyRegion) +
                            nUpdateRegionRects +
                            !!sendCursorShape + !!sendCursorPos);
  } else {
    fu->nRects = 0xFFFF;
  }
  ublen = sz_rfbFramebufferUpdateMsg;

  cl->captureEnable = TRUE;

  if (sendCursorShape) {
    cl->cursorWasChanged = FALSE;
    if (!rfbSendCursorShape(cl, pScreen))
      goto abort;
  }

  if (sendCursorPos) {
    cl->cursorWasMoved = FALSE;
    if (!rfbSendCursorPos(cl, pScreen))
      goto abort;
  }

  if (REGION_NOTEMPTY(pScreen, &updateCopyRegion)) {
    if (!rfbSendCopyRegion(cl, &updateCopyRegion, dx, dy))
      goto abort;
  }

  REGION_UNINIT(pScreen, &updateCopyRegion);
  REGION_NULL(pScreen, &updateCopyRegion);

  for (i = 0; i < REGION_NUM_RECTS(updateRegion); i++) {
    int x = REGION_RECTS(updateRegion)[i].x1;
    int y = REGION_RECTS(updateRegion)[i].y1;
    int w = REGION_RECTS(updateRegion)[i].x2 - x;
    int h = REGION_RECTS(updateRegion)[i].y2 - y;

    cl->rfbRawBytesEquivalent += (sz_rfbFramebufferUpdateRectHeader +
                                 w * (cl->format.bitsPerPixel / 8) * h);

    if (rfbProfile) mpixels += (double)w * (double)h / 1000000.;

    switch (cl->preferredEncoding) {
      case rfbEncodingRaw:
        if (!rfbSendRectEncodingRaw(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingRRE:
        if (!rfbSendRectEncodingRRE(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingCoRRE:
        if (!rfbSendRectEncodingCoRRE(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingHextile:
        if (!rfbSendRectEncodingHextile(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingZlib:
        if (!rfbSendRectEncodingZlib(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingZRLE:
      case rfbEncodingZYWRLE:
        if (!rfbSendRectEncodingZRLE(cl, x, y, w, h))
          goto abort;
        break;
      case rfbEncodingTight:
        if (!rfbSendRectEncodingTight(cl, x, y, w, h))
          goto abort;
        break;
    }
  }

  if (cl->compareFB) {
    if (rfbInterframeDebug) {
      for (i = 0; i < REGION_NUM_RECTS(&idRegion); i++) {
        int x = REGION_RECTS(&idRegion)[i].x1;
        int y = REGION_RECTS(&idRegion)[i].y1;
        int w = REGION_RECTS(&idRegion)[i].x2 - x;
        int h = REGION_RECTS(&idRegion)[i].y2 - y, rows;
        int pitch = rfbFB.paddedWidthInBytes;
        int ps = rfbServerFormat.bitsPerPixel / 8;
        char *src = &rfbFB.pfbMemory[y * pitch + x * ps];
        char *dst = &cl->compareFB[y * pitch + x * ps];
        rows = h;
        while (rows--) {
          memcpy(dst, src, w * ps);
          src += pitch;
          dst += pitch;
        }
      }
      REGION_UNINIT(pScreen, &idRegion);
      REGION_NULL(pScreen, &idRegion);
    }
    REGION_EMPTY(pScreen, updateRegion);
  } else {
    REGION_UNINIT(pScreen, updateRegion);
    REGION_NULL(pScreen, updateRegion);
  }

  if (nUpdateRegionRects == 0xFFFF && !rfbSendLastRectMarker(cl))
    goto abort;

  if (!rfbSendUpdateBuf(cl))
    goto abort;

  cl->captureEnable = FALSE;

  if (!rfbSendRTTPing(cl))
    goto abort;

  if (rfbProfile) {
    tUpdate += gettime() - tUpdateStart;
    tElapsed = gettime() - tStart;
    iter++;

    if (tElapsed > 5.) {
      rfbLog("%.2f updates/sec,  %.2f Mpixels/sec,  %.3f Mbits/sec\n",
             (double)iter / tElapsed, mpixels / tElapsed,
             (double)sendBytes / 125000. / tElapsed);
      rfbLog("Time/update:  Encode = %.3f ms,  Other = %.3f ms\n",
             tUpdate / (double)iter * 1000.,
             (tElapsed - tUpdate) / (double)iter * 1000.);
      if (cl->compareFB) {
        rfbLog("Identical Mpixels/sec:  %.2f  (%f %%)\n",
               (double)idmpixels / tElapsed, idmpixels / mpixels * 100.0);
        idmpixels = 0.;
      }
      tUpdate = 0.;
      iter = 0;
      mpixels = 0.;
      sendBytes = 0;
      tStart = gettime();
    }
  }

  if (rfbAutoLosslessRefresh > 0.0 &&
      (!putImageOnly || REGION_NOTEMPTY(pScreen, &cl->alrEligibleRegion) ||
       cl->firstUpdate)) {
    if (putImageOnly)
      REGION_UNION(pScreen, &cl->alrRegion, &cl->alrRegion,
                   &cl->alrEligibleRegion);
    REGION_EMPTY(pScreen, &cl->alrEligibleRegion);
    cl->alrTimer = TimerSet(cl->alrTimer, 0,
                            (CARD32)(rfbAutoLosslessRefresh * 1000.0),
                            alrCallback, cl);
  }

  rfbUncorkSock(cl->sock);
  return TRUE;

  abort:
  if (!REGION_NIL(&updateCopyRegion))
    REGION_UNINIT(pScreen, &updateCopyRegion);
  if (rfbInterframeDebug && !REGION_NIL(&idRegion))
    REGION_UNINIT(pScreen, &idRegion);
  if (emptyUpdateRegion) {
    /* Make sure cl hasn't been freed */
    for (cl2 = rfbClientHead; cl2; cl2 = cl2->next) {
      if (cl2 == cl) {
        REGION_EMPTY(pScreen, updateRegion);
        break;
      }
    }
  } else if (!REGION_NIL(&_updateRegion)) {
    REGION_UNINIT(pScreen, &_updateRegion);
  }
  return FALSE;
}