Rewrite server on top of TigerVNC

TigerVNC provides a much more robust server implementation vs.
libvncserver and yields higher performance and lower CPU
usage.
diff --git a/src/AndroidDesktop.cpp b/src/AndroidDesktop.cpp
new file mode 100644
index 0000000..a37500c
--- /dev/null
+++ b/src/AndroidDesktop.cpp
@@ -0,0 +1,340 @@
+#define LOG_TAG "AndroidDesktop"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <sys/eventfd.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+
+#include <ui/DisplayInfo.h>
+
+#include <rfb/PixelFormat.h>
+#include <rfb/Rect.h>
+#include <rfb/ScreenSet.h>
+#include <rfb/VNCServerST.h>
+
+#include "AndroidDesktop.h"
+#include "InputDevice.h"
+
+using namespace vncflinger;
+using namespace android;
+
+const rfb::PixelFormat AndroidDesktop::sRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
+
+AndroidDesktop::AndroidDesktop() {
+    mListener = new FrameListener(this);
+    mInputDevice = new InputDevice();
+
+    mEventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
+    if (mEventFd < 0) {
+        ALOGE("Failed to create event notifier");
+        return;
+    }
+}
+
+AndroidDesktop::~AndroidDesktop() {
+    mInputDevice->stop();
+    close(mEventFd);
+}
+
+void AndroidDesktop::start(rfb::VNCServer* vs) {
+    Mutex::Autolock _l(mMutex);
+
+    sp<ProcessState> self = ProcessState::self();
+    self->startThreadPool();
+
+    mMainDpy = SurfaceComposerClient::getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain);
+    if (updateDisplayProjection() == NO_INIT) {
+        ALOGE("Failed to query display!");
+        return;
+    }
+    mProjectionChanged = false;
+
+    status_t err = createVirtualDisplay();
+    if (err != NO_ERROR) {
+        ALOGE("Failed to create virtual display: err=%d", err);
+        return;
+    }
+
+    mInputDevice->start_async(mWidth, mHeight);
+
+    mServer = (rfb::VNCServerST*)vs;
+
+    updateFBSize(mWidth, mHeight);
+
+    mServer->setPixelBuffer(mPixels.get());
+
+    ALOGV("Desktop is running");
+}
+
+void AndroidDesktop::stop() {
+    Mutex::Autolock _L(mMutex);
+
+    ALOGV("Shutting down");
+
+    mServer->setPixelBuffer(0);
+    destroyVirtualDisplay();
+    mWidth = 0;
+    mHeight = 0;
+}
+
+status_t AndroidDesktop::createVirtualDisplay() {
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&mProducer, &consumer);
+    mCpuConsumer = new CpuConsumer(consumer, 1);
+    mCpuConsumer->setName(String8("vds-to-cpu"));
+    mCpuConsumer->setDefaultBufferSize(mWidth, mHeight);
+    mProducer->setMaxDequeuedBufferCount(4);
+    consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBX_8888);
+
+    mCpuConsumer->setFrameAvailableListener(mListener);
+
+    mDpy = SurfaceComposerClient::createDisplay(String8("VNC-VirtualDisplay"), false /*secure*/);
+
+    // aspect ratio
+    float displayAspect = (float) mSourceHeight / (float) mSourceWidth;
+
+    uint32_t outWidth, outHeight;
+    if (mWidth > (uint32_t)(mWidth * displayAspect)) {
+        // limited by narrow width; reduce height
+        outWidth = mWidth;
+        outHeight = (uint32_t)(mWidth * displayAspect);
+    } else {
+        // limited by short height; restrict width
+        outHeight = mHeight;
+        outWidth = (uint32_t)(mHeight / displayAspect);
+    }
+
+    // position the desktop in the viewport while preserving
+    // the source aspect ratio. we do this in case the client
+    // has resized the window and to deal with orientation
+    // changes set up by updateDisplayProjection
+    uint32_t offX, offY;
+    offX = (mWidth - outWidth) / 2;
+    offY = (mHeight - outHeight) / 2;
+    mDisplayRect = Rect(offX, offY, offX + outWidth, offY + outHeight);
+    Rect sourceRect(0, 0, mSourceWidth, mSourceHeight);
+
+    SurfaceComposerClient::openGlobalTransaction();
+    SurfaceComposerClient::setDisplaySurface(mDpy, mProducer);
+    SurfaceComposerClient::setDisplayProjection(mDpy, 0, sourceRect, mDisplayRect);
+    SurfaceComposerClient::setDisplayLayerStack(mDpy, 0);  // default stack
+    SurfaceComposerClient::closeGlobalTransaction();
+
+    mVDSActive = true;
+
+    ALOGV("Virtual display (%lux%lu [viewport=%ux%u] created", mWidth, mHeight,
+            outWidth, outHeight);
+
+    return NO_ERROR;
+}
+
+status_t AndroidDesktop::destroyVirtualDisplay() {
+    if (!mVDSActive) {
+        return NO_INIT;
+    }
+
+    mCpuConsumer.clear();
+    mProducer.clear();
+    SurfaceComposerClient::destroyDisplay(mDpy);
+
+    mVDSActive = false;
+
+    ALOGV("Virtual display destroyed");
+
+    return NO_ERROR;
+}
+
+void AndroidDesktop::processDesktopResize() {
+    if (mProjectionChanged) {
+        destroyVirtualDisplay();
+        createVirtualDisplay();
+        updateFBSize(mWidth, mHeight);
+        mInputDevice->reconfigure(mDisplayRect.getWidth(), mDisplayRect.getHeight());
+        rfb::ScreenSet screens;
+        screens.add_screen(rfb::Screen(0, 0, 0, mWidth, mHeight, 0));
+        mServer->setScreenLayout(screens);
+
+        mProjectionChanged = false;
+    }
+}
+
+void AndroidDesktop::processFrames() {
+    Mutex::Autolock _l(mMutex);
+
+    // do any pending resize
+    processDesktopResize();
+
+    if (!mFrameAvailable) {
+        return;
+    }
+
+    // get a frame from the virtual display
+    CpuConsumer::LockedBuffer imgBuffer;
+    status_t res = mCpuConsumer->lockNextBuffer(&imgBuffer);
+    if (res != OK) {
+        ALOGE("Failed to lock next buffer: %s (%d)", strerror(-res), res);
+        return;
+    }
+
+    mFrameNumber = imgBuffer.frameNumber;
+    ALOGV("processFrame: [%lu] format: %x (%dx%d, stride=%d)", mFrameNumber, imgBuffer.format,
+          imgBuffer.width, imgBuffer.height, imgBuffer.stride);
+
+    // we don't know if there was a stride change until we get
+    // a buffer from the queue. if it changed, we need to resize
+
+    rfb::Rect bufRect(0, 0, imgBuffer.width, imgBuffer.height);
+
+    // performance is extremely bad if the gpu memory is used
+    // directly without copying because it is likely uncached
+    mPixels->imageRect(bufRect, imgBuffer.data, imgBuffer.stride);
+
+    mCpuConsumer->unlockBuffer(imgBuffer);
+
+    // update clients
+    mServer->add_changed(bufRect);
+    mFrameAvailable = false;
+}
+
+// notifies the server loop that we have changes
+void AndroidDesktop::notify() {
+    static uint64_t notify = 1;
+    write(mEventFd, &notify, sizeof(notify));
+}
+
+// called when a client resizes the window
+unsigned int AndroidDesktop::setScreenLayout(int reqWidth, int reqHeight,
+                                             const rfb::ScreenSet& layout) {
+    Mutex::Autolock _l(mMutex);
+
+    char* dbg = new char[1024];
+    layout.print(dbg, 1024);
+
+    ALOGD("setScreenLayout: cur: %lux%lu  new: %dx%d %s", mWidth, mHeight, reqWidth, reqHeight, dbg);
+    delete[] dbg;
+
+    if (reqWidth == (int)mWidth && reqHeight == (int)mHeight) {
+        return rfb::resultInvalid;
+    }
+
+    if (reqWidth > 0 && reqHeight > 0) {
+        mWidth = reqWidth;
+        mHeight = reqHeight;
+
+        if (updateDisplayProjection() == NO_ERROR) {
+            // resize immediately
+            processDesktopResize();
+            notify();
+            return rfb::resultSuccess;
+        }
+    }
+    return rfb::resultInvalid;
+}
+
+// updates the pixelbuffer dimensions
+bool AndroidDesktop::updateFBSize(uint64_t width, uint64_t height) {
+    if (mPixels == nullptr || mPixels->height() != (int)height || mPixels->width() != (int)width) {
+        if (mPixels != nullptr) {
+            ALOGD("updateFBSize: old=[%dx%d] new=[%lux%lu]", mPixels->width(), mPixels->height(),
+                  width, height);
+        }
+        if (mPixels != nullptr && (int)width <= mPixels->width() &&
+            (int)height <= mPixels->height()) {
+            mPixels->setSize(width, height);
+        } else {
+            mPixels = new AndroidPixelBuffer(width, height);
+            mServer->setPixelBuffer(mPixels.get());
+        }
+        return true;
+    }
+    return false;
+}
+
+// cpuconsumer frame listener, called from binder thread
+void AndroidDesktop::FrameListener::onFrameAvailable(const BufferItem& item) {
+    Mutex::Autolock _l(mDesktop->mMutex);
+    mDesktop->updateDisplayProjection();
+    mDesktop->mFrameAvailable = true;
+    mDesktop->notify();
+    ALOGV("onFrameAvailable: [%lu] mTimestamp=%ld", item.mFrameNumber, item.mTimestamp);
+}
+
+rfb::Point AndroidDesktop::getFbSize() {
+    return rfb::Point(mPixels->width(), mPixels->height());
+}
+
+void AndroidDesktop::keyEvent(rdr::U32 key, bool down) {
+    mInputDevice->keyEvent(down, key);
+}
+
+void AndroidDesktop::pointerEvent(const rfb::Point& pos, int buttonMask) {
+    if (pos.x < mDisplayRect.left || pos.x > mDisplayRect.right ||
+            pos.y < mDisplayRect.top || pos.y > mDisplayRect.bottom) {
+        // outside viewport
+        return;
+    }
+    uint32_t x = pos.x * ((float)(mDisplayRect.getWidth()) / (float)mWidth);
+    uint32_t y = pos.y * ((float)(mDisplayRect.getHeight()) / (float)mHeight);
+
+    ALOGD("pointer xlate x1=%d y1=%d x2=%d y2=%d", pos.x, pos.y, x, y);
+
+    mServer->setCursorPos(rfb::Point(x, y));
+    mInputDevice->pointerEvent(buttonMask, x, y);
+}
+
+// figure out the dimensions of the display. deal with orientation
+// changes, client-side window resize, server-side scaling, and
+// maintaining aspect ratio.
+status_t AndroidDesktop::updateDisplayProjection() {
+    DisplayInfo info;
+    status_t err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &info);
+    if (err != NO_ERROR) {
+        ALOGE("Failed to get display characteristics\n");
+        return err;
+    }
+
+    bool deviceRotated =
+        info.orientation != DISPLAY_ORIENTATION_0 && info.orientation != DISPLAY_ORIENTATION_180;
+
+    // if orientation changed, swap width/height
+    uint32_t sourceWidth, sourceHeight;
+    if (!deviceRotated) {
+        sourceWidth = info.w;
+        sourceHeight = info.h;
+    } else {
+        sourceHeight = info.w;
+        sourceWidth = info.h;
+    }
+
+    if (mWidth == 0 && mHeight == 0) {
+        mWidth = sourceWidth;
+        mHeight = sourceHeight;
+    }
+
+    if (deviceRotated != mRotated) {
+        std::swap(mWidth, mHeight);
+        mRotated = deviceRotated;
+    }
+
+    // if nothing changed, we're done
+    if (mSourceWidth == sourceWidth && mSourceHeight == sourceHeight &&
+        (int)mWidth == mPixels->width() && (int)mHeight == mPixels->height()) {
+        return NO_ERROR;
+    }
+
+    // update all the values and flag for an update
+    mSourceWidth = sourceWidth;
+    mSourceHeight = sourceHeight;
+    mProjectionChanged = true;
+
+    ALOGV("Dimensions: %lux%lu [out: %lux%lu] rotated=%d", mSourceWidth, mSourceHeight, mWidth,
+          mHeight, mRotated);
+
+    return NO_ERROR;
+}