Change cursor API to use RGBA data

This will allow us to use better formats that preserve the entire
alpha channel.
diff --git a/common/rfb/CMsgHandler.h b/common/rfb/CMsgHandler.h
index 7d2cdc2..2686712 100644
--- a/common/rfb/CMsgHandler.h
+++ b/common/rfb/CMsgHandler.h
@@ -50,7 +50,7 @@
                                         int w, int h,
                                         const ScreenSet& layout);
     virtual void setCursor(int width, int height, const Point& hotspot,
-                           void* data, void* mask) = 0;
+                           const rdr::U8* data) = 0;
     virtual void setPixelFormat(const PixelFormat& pf);
     virtual void setName(const char* name);
     virtual void fence(rdr::U32 flags, unsigned len, const char data[]);
diff --git a/common/rfb/CMsgReader.cxx b/common/rfb/CMsgReader.cxx
index 96ddf44..bbb9909 100644
--- a/common/rfb/CMsgReader.cxx
+++ b/common/rfb/CMsgReader.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2009-2014 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 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
@@ -198,10 +198,35 @@
   rdr::U8Array data(data_len);
   rdr::U8Array mask(mask_len);
 
+  int x, y;
+  rdr::U8 buf[width*height*4];
+  rdr::U8* in;
+  rdr::U8* out;
+
   is->readBytes(data.buf, data_len);
   is->readBytes(mask.buf, mask_len);
 
-  handler->setCursor(width, height, hotspot, data.buf, mask.buf);
+  int maskBytesPerRow = (width+7)/8;
+  in = data.buf;
+  out = buf;
+  for (y = 0;y < height;y++) {
+    for (x = 0;x < width;x++) {
+      int byte = y * maskBytesPerRow + x / 8;
+      int bit = 7 - x % 8;
+
+      handler->cp.pf().rgbFromBuffer(out, in, 1);
+
+      if (mask.buf[byte] & (1 << bit))
+        out[3] = 255;
+      else
+        out[3] = 0;
+
+      in += handler->cp.pf().bpp/8;
+      out += 4;
+    }
+  }
+
+  handler->setCursor(width, height, hotspot, buf);
 }
 
 void CMsgReader::readSetDesktopName(int x, int y, int w, int h)
diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx
index ab3b884..dc9ebf5 100644
--- a/common/rfb/ConnParams.cxx
+++ b/common/rfb/ConnParams.cxx
@@ -39,11 +39,13 @@
     subsampling(subsampleUndefined), name_(0), verStrPos(0)
 {
   setName("");
+  cursor_ = new Cursor(0, 0, Point(), NULL);
 }
 
 ConnParams::~ConnParams()
 {
   delete [] name_;
+  delete cursor_;
 }
 
 bool ConnParams::readVersion(rdr::InStream* is, bool* done)
@@ -86,17 +88,8 @@
 
 void ConnParams::setCursor(const Cursor& other)
 {
-  const rdr::U8* data;
-  int stride;
-
-  cursor_.hotspot = other.hotspot;
-  cursor_.setPF(other.getPF());
-  cursor_.setSize(other.width(), other.height());
-
-  data = other.getBuffer(other.getRect(), &stride);
-  cursor_.imageRect(cursor_.getRect(), data, stride);
-
-  memcpy(cursor_.mask.buf, other.mask.buf, cursor_.maskLen());
+  delete cursor_;
+  cursor_ = new Cursor(other);
 }
 
 bool ConnParams::supportsEncoding(rdr::S32 encoding) const
diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h
index 9e647ba..517e649 100644
--- a/common/rfb/ConnParams.h
+++ b/common/rfb/ConnParams.h
@@ -77,7 +77,7 @@
     const char* name() const { return name_; }
     void setName(const char* name);
 
-    const Cursor& cursor() const { return cursor_; }
+    const Cursor& cursor() const { return *cursor_; }
     void setCursor(const Cursor& cursor);
 
     bool supportsEncoding(rdr::S32 encoding) const;
@@ -106,7 +106,7 @@
 
     PixelFormat pf_;
     char* name_;
-    Cursor cursor_;
+    Cursor* cursor_;
     std::set<rdr::S32> encodings_;
     char verStr[13];
     int verStrPos;
diff --git a/common/rfb/Cursor.cxx b/common/rfb/Cursor.cxx
index e226118..a79f046 100644
--- a/common/rfb/Cursor.cxx
+++ b/common/rfb/Cursor.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2017 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
@@ -26,71 +26,31 @@
 
 static LogWriter vlog("Cursor");
 
-void Cursor::setSize(int w, int h) {
-  int oldMaskLen = maskLen();
-  ManagedPixelBuffer::setSize(w, h);
-  if (maskLen() > oldMaskLen) {
-    delete [] mask.buf;
-    mask.buf = new rdr::U8[maskLen()];
-  }
+Cursor::Cursor(int width, int height, const Point& hotspot,
+               const rdr::U8* data) :
+  width_(width), height_(height), hotspot_(hotspot)
+{
+  this->data = new rdr::U8[width_*height_*4];
+  memcpy(this->data, data, width_*height_*4);
 }
 
-void Cursor::drawOutline(const Pixel& c)
+Cursor::Cursor(const Cursor& other) :
+  width_(other.width_), height_(other.height_),
+  hotspot_(other.hotspot_)
 {
-  Cursor outlined;
-  rdr::U8 cbuf[4];
+  data = new rdr::U8[width_*height_*4];
+  memcpy(data, other.data, width_*height_*4);
+}
 
-  // Create a mirror of the existing cursor
-  outlined.setPF(getPF());
-  outlined.setSize(width(), height());
-  outlined.hotspot = hotspot;
-
-  // Clear the mirror's background to the outline colour
-  outlined.getPF().bufferFromPixel(cbuf, c);
-  outlined.fillRect(getRect(), cbuf);
-
-  // Blit the existing cursor, using its mask
-  outlined.maskRect(getRect(), data, mask.buf);
-
-  // Now just adjust the mask to add the outline.  The outline pixels
-  // will already be the right colour. :)
-  int maskBytesPerRow = (width() + 7) / 8;
-  for (int y = 0; y < height(); y++) {
-    for (int byte=0; byte<maskBytesPerRow; byte++) {
-      rdr::U8 m8 = mask.buf[y*maskBytesPerRow + byte];
-
-      // Handle above & below outline
-      if (y > 0) m8 |= mask.buf[(y-1)*maskBytesPerRow + byte];
-      if (y < height()-1) m8 |= mask.buf[(y+1)*maskBytesPerRow + byte];
-
-      // Left outline
-      m8 |= mask.buf[y*maskBytesPerRow + byte] << 1;
-      if (byte < maskBytesPerRow-1)
-        m8 |= (mask.buf[y*maskBytesPerRow + byte + 1] >> 7) & 1;
-
-      // Right outline
-      m8 |= mask.buf[y*maskBytesPerRow + byte] >> 1;
-      if (byte > 0)
-        m8 |= (mask.buf[y*maskBytesPerRow + byte - 1] << 7) & 128;
-
-      outlined.mask.buf[y*maskBytesPerRow + byte] = m8;
-    }
-  }
-
-  // Replace the existing cursor & mask with the new one
+Cursor::~Cursor()
+{
   delete [] data;
-  delete [] mask.buf;
-  data = outlined.data; outlined.data = 0;
-  mask.buf = outlined.mask.buf; outlined.mask.buf = 0;
 }
 
-rdr::U8* Cursor::getBitmap(Pixel* pix0, Pixel* pix1) const
+rdr::U8* Cursor::getBitmap() const
 {
-  bool gotPix0 = false;
-  bool gotPix1 = false;
-  *pix0 = *pix1 = 0;
-  rdr::U8Array source(maskLen());
-  memset(source.buf, 0, maskLen());
+  rdr::U8Array source((width()+7)/8*height());
+  memset(source.buf, 0, (width()+7)/8*height());
 
   int maskBytesPerRow = (width() + 7) / 8;
   const rdr::U8 *data_ptr = data;
@@ -98,26 +58,47 @@
     for (int x = 0; x < width(); x++) {
       int byte = y * maskBytesPerRow + x / 8;
       int bit = 7 - x % 8;
-      if (mask.buf[byte] & (1 << bit)) {
-        Pixel pix = getPF().pixelFromBuffer(data_ptr);
-        if (!gotPix0 || pix == *pix0) {
-          gotPix0 = true;
-          *pix0 = pix;
-        } else if (!gotPix1 || pix == *pix1) {
-          gotPix1 = true;
-          *pix1 = pix;
+      if (data_ptr[3] >= 0x80) {
+        // Use Luma with BT.709 coefficients for grayscale
+        unsigned luma;
+
+        luma = 0;
+        luma += (unsigned)data_ptr[0] * 13933; // 0.2126
+        luma += (unsigned)data_ptr[1] * 46871; // 0.7152
+        luma += (unsigned)data_ptr[2] * 4732;  // 0.0722
+        luma /= 65536;
+
+        // Gamma compensated half intensity gray
+        if (luma > 187)
           source.buf[byte] |= (1 << bit);
-        } else {
-          // not a bitmap
-          return 0;
-        }
       }
-      data_ptr += getPF().bpp/8;
+      data_ptr += 4;
     }
   }
+
   return source.takeBuf();
 }
 
+rdr::U8* Cursor::getMask() const
+{
+  rdr::U8Array mask((width()+7)/8*height());
+  memset(mask.buf, 0, (width()+7)/8*height());
+
+  int maskBytesPerRow = (width() + 7) / 8;
+  const rdr::U8 *data_ptr = data;
+  for (int y = 0; y < height(); y++) {
+    for (int x = 0; x < width(); x++) {
+      int byte = y * maskBytesPerRow + x / 8;
+      int bit = 7 - x % 8;
+      if (data_ptr[3] >= 0x80)
+        mask.buf[byte] |= (1 << bit);
+      data_ptr += 4;
+    }
+  }
+
+  return mask.takeBuf();
+}
+
 // crop() determines the "busy" rectangle for the cursor - the minimum bounding
 // rectangle containing actual pixels.  This isn't the most efficient algorithm
 // but it's short.  For sanity, we make sure that the busy rectangle always
@@ -126,58 +107,40 @@
 
 void Cursor::crop()
 {
-  Rect busy = getRect().intersect(Rect(hotspot.x, hotspot.y,
-                                       hotspot.x+1, hotspot.y+1));
-  int maskBytesPerRow = (width() + 7) / 8;
+  Rect busy = Rect(0, 0, width_, height_);
+  busy = busy.intersect(Rect(hotspot_.x, hotspot_.y,
+                             hotspot_.x+1, hotspot_.y+1));
   int x, y;
+  rdr::U8 *data_ptr = data;
   for (y = 0; y < height(); y++) {
     for (x = 0; x < width(); x++) {
-      int byte = y * maskBytesPerRow + x / 8;
-      int bit = 7 - x % 8;
-      if (mask.buf[byte] & (1 << bit)) {
+      if (data_ptr[3] > 0) {
         if (x < busy.tl.x) busy.tl.x = x;
         if (x+1 > busy.br.x) busy.br.x = x+1;
         if (y < busy.tl.y) busy.tl.y = y;
         if (y+1 > busy.br.y) busy.br.y = y+1;
       }
+      data_ptr += 4;
     }
   }
 
   if (width() == busy.width() && height() == busy.height()) return;
 
-  vlog.debug("cropping %dx%d to %dx%d", width(), height(),
-             busy.width(), busy.height());
-
   // Copy the pixel data
-  int newDataLen = busy.area() * (getPF().bpp/8);
+  int newDataLen = busy.area() * 4;
   rdr::U8* newData = new rdr::U8[newDataLen];
-  getImage(newData, busy);
-
-  // Copy the mask
-  int newMaskBytesPerRow = (busy.width()+7)/8;
-  int newMaskLen = newMaskBytesPerRow * busy.height();
-  rdr::U8* newMask = new rdr::U8[newMaskLen];
-  memset(newMask, 0, newMaskLen);
-  for (y = 0; y < busy.height(); y++) {
-    int newByte, newBit;
-    for (x = 0; x < busy.width(); x++) {
-      int oldByte = (y+busy.tl.y) * maskBytesPerRow + (x+busy.tl.x) / 8;
-      int oldBit = 7 - (x+busy.tl.x) % 8;
-      newByte = y * newMaskBytesPerRow + x / 8;
-      newBit = 7 - x % 8;
-      if (mask.buf[oldByte] & (1 << oldBit))
-        newMask[newByte] |= (1 << newBit);
-    }
+  data_ptr = newData;
+  for (y = busy.tl.y; y < busy.br.y; y++) {
+    memcpy(data_ptr, data + y*width()*4 + busy.tl.x*4, busy.width()*4);
+    data_ptr += busy.width()*4;
   }
 
   // Set the size and data to the new, cropped cursor.
-  setSize(busy.width(), busy.height());
-  hotspot = hotspot.subtract(busy.tl);
+  width_ = busy.width();
+  height_ = busy.height();
+  hotspot_ = hotspot_.subtract(busy.tl);
   delete [] data;
-  delete [] mask.buf;
-  datasize = newDataLen;
   data = newData;
-  mask.buf = newMask;
 }
 
 RenderedCursor::RenderedCursor()
@@ -207,26 +170,48 @@
   assert(framebuffer);
   assert(cursor);
 
-  if (!framebuffer->getPF().equal(cursor->getPF()))
-    throw Exception("RenderedCursor: Trying to render cursor on incompatible frame buffer");
-
   format = framebuffer->getPF();
   width_ = framebuffer->width();
   height_ = framebuffer->height();
 
-  rawOffset = pos.subtract(cursor->hotspot);
-  clippedRect = cursor->getRect(rawOffset).intersect(framebuffer->getRect());
+  rawOffset = pos.subtract(cursor->hotspot());
+  clippedRect = Rect(0, 0, cursor->width(), cursor->height())
+                .translate(rawOffset)
+                .intersect(framebuffer->getRect());
   offset = clippedRect.tl;
 
-  buffer.setPF(cursor->getPF());
+  buffer.setPF(format);
   buffer.setSize(clippedRect.width(), clippedRect.height());
 
   data = framebuffer->getBuffer(buffer.getRect(offset), &stride);
   buffer.imageRect(buffer.getRect(), data, stride);
 
   diff = offset.subtract(rawOffset);
-  data = cursor->getBuffer(buffer.getRect(diff), &stride);
+  for (int y = 0;y < buffer.height();y++) {
+    for (int x = 0;x < buffer.width();x++) {
+      size_t idx;
+      rdr::U8 bg[4], fg[4];
+      rdr::U8 rgb[3];
 
-  buffer.maskRect(buffer.getRect(), data, cursor->mask.buf, diff,
-                  stride, (cursor->width() + 7) / 8);
+      idx = (y+diff.y)*cursor->width() + (x+diff.x);
+      memcpy(fg, cursor->getBuffer() + idx*4, 4);
+
+      if (fg[3] == 0x00)
+        continue;
+      else if (fg[3] == 0xff) {
+        memcpy(rgb, fg, 3);
+      } else {
+        buffer.getImage(bg, Rect(x, y, x+1, y+1));
+        format.rgbFromBuffer(rgb, bg, 1);
+        // FIXME: Gamma aware blending
+        for (int i = 0;i < 3;i++) {
+          rgb[i] = (unsigned)rgb[i]*(255-fg[3])/255 +
+                   (unsigned)fg[i]*fg[3]/255;
+        }
+      }
+
+      format.bufferFromRGB(bg, rgb, 1);
+      buffer.imageRect(Rect(x, y, x+1, y+1), bg);
+    }
+  }
 }
diff --git a/common/rfb/Cursor.h b/common/rfb/Cursor.h
index 560e4d8..6c6db7e 100644
--- a/common/rfb/Cursor.h
+++ b/common/rfb/Cursor.h
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2014 Pierre Ossman for Cendio AB
+ * Copyright 2014-2017 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
@@ -28,29 +28,30 @@
 
 namespace rfb {
 
-  class Cursor : public ManagedPixelBuffer {
+  class Cursor {
   public:
-    Cursor() {}
-    rdr::U8Array mask;
-    Point hotspot;
+    Cursor(int width, int height, const Point& hotspot, const rdr::U8* data);
+    Cursor(const Cursor& other);
+    ~Cursor();
 
-    int maskLen() const { return (width() + 7) / 8 * height(); }
+    int width() const { return width_; };
+    int height() const { return height_; };
+    const Point& hotspot() const { return hotspot_; };
+    const rdr::U8* getBuffer() const { return data; };
 
-    // setSize() resizes the cursor.  The contents of the data and mask are
-    // undefined after this call.
-    virtual void setSize(int w, int h);
-
-    // drawOutline() adds an outline to the cursor in the given colour.
-    void drawOutline(const Pixel& c);
-
-    // getBitmap() tests whether the cursor is monochrome, and if so returns a
-    // bitmap together with background and foreground colours.  The size and
-    // layout of the bitmap are the same as the mask.
-    rdr::U8* getBitmap(Pixel* pix0, Pixel* pix1) const;
+    // getBitmap() returns a monochrome version of the cursor
+    rdr::U8* getBitmap() const;
+    // getMask() returns a simple mask version of the alpha channel
+    rdr::U8* getMask() const;
 
     // crop() crops the cursor down to the smallest possible size, based on the
     // mask.
     void crop();
+
+  protected:
+    int width_, height_;
+    Point hotspot_;
+    rdr::U8* data;
   };
 
   class RenderedCursor : public PixelBuffer {
diff --git a/common/rfb/PixelBuffer.cxx b/common/rfb/PixelBuffer.cxx
index e788fca..1edd9c3 100644
--- a/common/rfb/PixelBuffer.cxx
+++ b/common/rfb/PixelBuffer.cxx
@@ -200,110 +200,6 @@
   commitBufferRW(r);
 }
 
-void ModifiablePixelBuffer::maskRect(const Rect& r,
-                                     const void* pixels,
-                                     const void* mask_,
-                                     const Point& maskPos,
-                                     int pStride, int mStride)
-{
-  int stride;
-  U8* data;
-  U8* mask;
-  int w, h, bpp;
-
-  if (!r.enclosed_by(getRect()))
-    throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
-                         r.width(), r.height(),
-                         r.tl.x, r.tl.y, width_, height_);
-
-  data = getBufferRW(r, &stride);
-  mask = (U8*) mask_;
-
-  w = r.width();
-  h = r.height();
-  bpp = getPF().bpp;
-  if (pStride == 0)
-    pStride = r.width();
-  if (mStride == 0)
-    mStride = (r.width() + 7) / 8;
-
-  mask += maskPos.y * mStride;
-  for (int y = 0; y < h; y++) {
-    for (int x = 0; x < w; x++) {
-      int cx = maskPos.x + x;
-      U8* byte = mask + (cx / 8);
-      int bit = 7 - cx % 8;
-      if ((*byte) & (1 << bit)) {
-        switch (bpp) {
-        case 8:
-          ((U8*)data)[y * stride + x] = ((U8*)pixels)[y * pStride + x];
-          break;
-        case 16:
-          ((U16*)data)[y * stride + x] = ((U16*)pixels)[y * pStride + x];
-          break;
-        case 32:
-          ((U32*)data)[y * stride + x] = ((U32*)pixels)[y * pStride + x];
-          break;
-        }
-      }
-    }
-    mask += mStride;
-  }
-
-  commitBufferRW(r);
-}
-
-void ModifiablePixelBuffer::maskRect(const Rect& r,
-                                     Pixel pixel,
-                                     const void* mask_,
-                                     const Point& maskPos,
-                                     int mStride)
-{
-  int stride;
-  U8* data;
-  U8* mask;
-  int w, h, bpp;
-
-  if (!r.enclosed_by(getRect()))
-    throw rfb::Exception("Destination rect %dx%d at %d,%d exceeds framebuffer %dx%d",
-                         r.width(), r.height(),
-                         r.tl.x, r.tl.y, width_, height_);
-
-  data = getBufferRW(r, &stride);
-  mask = (U8*) mask_;
-
-  w = r.width();
-  h = r.height();
-  bpp = getPF().bpp;
-  if (mStride == 0)
-    mStride = (r.width() + 7) / 8;
-
-  mask += maskPos.y * mStride;
-  for (int y = 0; y < h; y++) {
-    for (int x = 0; x < w; x++) {
-      int cx = maskPos.x + x;
-      U8* byte = mask + (cx / 8);
-      int bit = 7 - cx % 8;
-      if ((*byte) & (1 << bit)) {
-        switch (bpp) {
-        case 8:
-          ((U8*)data)[y * stride + x] = pixel;
-          break;
-        case 16:
-          ((U16*)data)[y * stride + x] = pixel;
-          break;
-        case 32:
-          ((U32*)data)[y * stride + x] = pixel;
-          break;
-        }
-      }
-    }
-    mask += mStride;
-  }
-
-  commitBufferRW(r);
-}
-
 void ModifiablePixelBuffer::copyRect(const Rect &rect,
                                      const Point &move_by_delta)
 {
diff --git a/common/rfb/PixelBuffer.h b/common/rfb/PixelBuffer.h
index b38999a..75caa63 100644
--- a/common/rfb/PixelBuffer.h
+++ b/common/rfb/PixelBuffer.h
@@ -127,20 +127,6 @@
     // Copy pixel data from one PixelBuffer location to another
     void copyRect(const Rect &dest, const Point& move_by_delta);
 
-    // Copy pixel data to the buffer through a mask
-    //   pixels is a pointer to the pixel to be copied to r.tl.
-    //   maskPos specifies the pixel offset in the mask to start from.
-    //   mask_ is a pointer to the mask bits at (0,0).
-    //   pStride and mStride are the strides of the pixel and mask buffers.
-    void maskRect(const Rect& r, const void* pixels, const void* mask_,
-                  const Point& maskPos=Point(0, 0),
-                  int pStride=0, int mStride=0);
-
-    //   pixel is the Pixel value to be used where mask_ is set
-    void maskRect(const Rect& r, Pixel pixel, const void* mask_,
-                  const Point& maskPos=Point(0, 0),
-                  int mStride=0);
-
     // Render in a specific format
     //   Does the exact same thing as the above methods, but the given
     //   pixel values are defined by the given PixelFormat.
diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx
index 5040b65..51e1105 100644
--- a/common/rfb/SMsgWriter.cxx
+++ b/common/rfb/SMsgWriter.cxx
@@ -1,6 +1,6 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
  * Copyright (C) 2011 D. R. Commander.  All Rights Reserved.
- * Copyright 2009-2014 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 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
@@ -301,38 +301,36 @@
 void SMsgWriter::writePseudoRects()
 {
   if (needSetCursor) {
-    rdr::U8* data;
-
     const Cursor& cursor = cp->cursor();
 
-    data = new rdr::U8[cursor.area() * cp->pf().bpp/8];
-    cursor.getImage(cp->pf(), data, cursor.getRect());
+    rdr::U8Array data(cursor.width()*cursor.height() * cp->pf().bpp/8);
+    rdr::U8Array mask(cursor.getMask());
+
+    const rdr::U8* in;
+    rdr::U8* out;
+
+    in = cursor.getBuffer();
+    out = data.buf;
+    for (int i = 0;i < cursor.width()*cursor.height();i++) {
+      cp->pf().bufferFromRGB(out, in, 1);
+      in += 4;
+      out += cp->pf().bpp/8;
+    }
 
     writeSetCursorRect(cursor.width(), cursor.height(),
-                       cursor.hotspot.x, cursor.hotspot.y,
-                       data, cursor.mask.buf);
+                       cursor.hotspot().x, cursor.hotspot().y,
+                       data.buf, mask.buf);
     needSetCursor = false;
-
-    delete [] data;
   }
 
   if (needSetXCursor) {
     const Cursor& cursor = cp->cursor();
-    Pixel pix0, pix1;
-    rdr::U8 rgb0[3], rgb1[3];
-    rdr::U8Array bitmap(cursor.getBitmap(&pix0, &pix1));
-
-    if (!bitmap.buf) {
-      // FIXME: We could reduce to two colors.
-      throw Exception("SMsgWriter::writePseudoRects: Unable to send multicolor cursor: RichCursor not supported by client");
-    }
-
-    cp->pf().rgbFromPixel(pix0, &rgb0[0], &rgb0[1], &rgb0[2]);
-    cp->pf().rgbFromPixel(pix1, &rgb1[0], &rgb1[1], &rgb1[2]);
+    rdr::U8Array bitmap(cursor.getBitmap());
+    rdr::U8Array mask(cursor.getMask());
 
     writeSetXCursorRect(cursor.width(), cursor.height(),
-                        cursor.hotspot.x, cursor.hotspot.y,
-                        rgb0, rgb1, bitmap.buf, cursor.mask.buf);
+                        cursor.hotspot().x, cursor.hotspot().y,
+                        bitmap.buf, mask.buf);
     needSetXCursor = false;
   }
 
@@ -452,8 +450,6 @@
 
 void SMsgWriter::writeSetXCursorRect(int width, int height,
                                      int hotspotX, int hotspotY,
-                                     const rdr::U8 pix0[],
-                                     const rdr::U8 pix1[],
                                      const void* data, const void* mask)
 {
   if (!cp->supportsLocalXCursor)
@@ -467,12 +463,12 @@
   os->writeU16(height);
   os->writeU32(pseudoEncodingXCursor);
   if (width * height) {
-    os->writeU8(pix0[0]);
-    os->writeU8(pix0[1]);
-    os->writeU8(pix0[2]);
-    os->writeU8(pix1[0]);
-    os->writeU8(pix1[1]);
-    os->writeU8(pix1[2]);
+    os->writeU8(255);
+    os->writeU8(255);
+    os->writeU8(255);
+    os->writeU8(0);
+    os->writeU8(0);
+    os->writeU8(0);
     os->writeBytes(data, (width+7)/8 * height);
     os->writeBytes(mask, (width+7)/8 * height);
   }
diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h
index 917b933..a516e10 100644
--- a/common/rfb/SMsgWriter.h
+++ b/common/rfb/SMsgWriter.h
@@ -126,7 +126,6 @@
                             const void* data, const void* mask);
     void writeSetXCursorRect(int width, int height,
                              int hotspotX, int hotspotY,
-                             const rdr::U8 pix0[], const rdr::U8 pix1[],
                              const void* data, const void* mask);
 
     ConnParams* cp;
diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx
index 74c40d1..e7a5dcf 100644
--- a/common/rfb/VNCSConnectionST.cxx
+++ b/common/rfb/VNCSConnectionST.cxx
@@ -1119,7 +1119,7 @@
   if (state() != RFBSTATE_NORMAL)
     return;
 
-  cp.setCursor(server->cursor);
+  cp.setCursor(*server->cursor);
 
   if (!writer()->writeSetCursor()) {
     if (!writer()->writeSetXCursor()) {
diff --git a/common/rfb/VNCServer.h b/common/rfb/VNCServer.h
index c76e5c9..982a4ff 100644
--- a/common/rfb/VNCServer.h
+++ b/common/rfb/VNCServer.h
@@ -64,16 +64,10 @@
     virtual void closeClients(const char* reason) = 0;
 
     // setCursor() tells the server that the cursor has changed.  The
-    // cursorData argument contains width*height pixel values in the pixel
-    // buffer's format.  The mask argument is a bitmask with a 1-bit meaning
-    // the corresponding pixel in cursorData is valid.  The mask consists of
-    // left-to-right, top-to-bottom scanlines, where each scanline is padded to
-    // a whole number of bytes [(width+7)/8].  Within each byte the most
-    // significant bit represents the leftmost pixel, and the bytes are simply
-    // in left-to-right order.  The server takes its own copy of the data in
-    // cursorData and mask.
+    // cursorData argument contains width*height rgba quadruplets with
+    // non-premultiplied alpha.
     virtual void setCursor(int width, int height, const Point& hotspot,
-                           const void* cursorData, const void* mask) = 0;
+                           const rdr::U8* cursorData) = 0;
 
     // setCursorPos() tells the server the current position of the cursor.
     virtual void setCursorPos(const Point& p) = 0;
diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx
index e15cd70..81eed37 100644
--- a/common/rfb/VNCServerST.cxx
+++ b/common/rfb/VNCServerST.cxx
@@ -1,5 +1,5 @@
 /* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
- * Copyright 2009-2014 Pierre Ossman for Cendio AB
+ * Copyright 2009-2017 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
@@ -82,6 +82,7 @@
   : blHosts(&blacklist), desktop(desktop_), desktopStarted(false),
     blockCounter(0), pb(0),
     name(strDup(name_)), pointerClient(0), comparer(0),
+    cursor(new Cursor(0, 0, Point(), NULL)),
     renderedCursorInvalid(false),
     queryConnectionHandler(0), keyRemapper(&KeyRemapper::defInstance),
     lastConnectionTime(0), disableclients(false),
@@ -113,6 +114,8 @@
   if (comparer)
     comparer->logStats();
   delete comparer;
+
+  delete cursor;
 }
 
 
@@ -314,7 +317,6 @@
   }
 
   comparer = new ComparingUpdateTracker(pb);
-  cursor.setPF(pb->getPF());
   renderedCursorInvalid = true;
 
   // Make sure that we have at least one screen
@@ -430,14 +432,11 @@
 }
 
 void VNCServerST::setCursor(int width, int height, const Point& newHotspot,
-                            const void* data, const void* mask)
+                            const rdr::U8* data)
 {
-  cursor.hotspot = newHotspot;
-  cursor.setSize(width, height);
-  cursor.imageRect(cursor.getRect(), data);
-  memcpy(cursor.mask.buf, mask, cursor.maskLen());
-
-  cursor.crop();
+  delete cursor;
+  cursor = new Cursor(width, height, newHotspot, data);
+  cursor->crop();
 
   renderedCursorInvalid = true;
 
@@ -618,8 +617,9 @@
   Region toCheck = ui.changed.union_(ui.copied);
 
   if (renderCursor) {
-    Rect clippedCursorRect
-      = cursor.getRect(cursorPos.subtract(cursor.hotspot)).intersect(pb->getRect());
+    Rect clippedCursorRect = Rect(0, 0, cursor->width(), cursor->height())
+                             .translate(cursorPos.subtract(cursor->hotspot()))
+                             .intersect(pb->getRect());
 
     if (!renderedCursorInvalid && (toCheck.intersect(clippedCursorRect)
                                    .is_empty())) {
@@ -640,7 +640,7 @@
     comparer->getUpdateInfo(&ui, pb->getRect());
 
   if (renderCursor) {
-    renderedCursor.update(pb, &cursor, cursorPos);
+    renderedCursor.update(pb, cursor, cursorPos);
     renderedCursorInvalid = false;
   }
 
diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h
index 0ced12a..4975716 100644
--- a/common/rfb/VNCServerST.h
+++ b/common/rfb/VNCServerST.h
@@ -98,7 +98,7 @@
     virtual void add_changed(const Region &region);
     virtual void add_copied(const Region &dest, const Point &delta);
     virtual void setCursor(int width, int height, const Point& hotspot,
-                           const void* cursorData, const void* mask);
+                           const rdr::U8* data);
     virtual void setCursorPos(const Point& p);
 
     virtual void bell();
@@ -218,7 +218,7 @@
     ComparingUpdateTracker* comparer;
 
     Point cursorPos;
-    Cursor cursor;
+    Cursor* cursor;
     RenderedCursor renderedCursor;
     bool renderedCursorInvalid;