Refactor the code

 * Split out classes for pixel buffer and virtual display
 * Move resize handling to appropriate classes
 * Use callbacks for orientation change and client resize
 * Remove unnecessary locking
diff --git a/src/AndroidDesktop.cpp b/src/AndroidDesktop.cpp
index a37500c..72c5896 100644
--- a/src/AndroidDesktop.cpp
+++ b/src/AndroidDesktop.cpp
@@ -4,10 +4,6 @@
 #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>
 
@@ -19,16 +15,16 @@
 #include <rfb/VNCServerST.h>
 
 #include "AndroidDesktop.h"
+#include "AndroidPixelBuffer.h"
 #include "InputDevice.h"
+#include "VirtualDisplay.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();
+    mDisplayRect = Rect(0, 0);
 
     mEventFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
     if (mEventFd < 0) {
@@ -43,140 +39,40 @@
 }
 
 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);
+    mPixels = new AndroidPixelBuffer();
+    mPixels->setDimensionsChangedListener(this);
 
-    mServer->setPixelBuffer(mPixels.get());
+    if (updateDisplayInfo() != NO_ERROR) {
+        ALOGE("Failed to query display!");
+        return;
+    }
 
     ALOGV("Desktop is running");
 }
 
 void AndroidDesktop::stop() {
-    Mutex::Autolock _L(mMutex);
+    Mutex::Autolock _L(mLock);
 
     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;
-    }
+    mVirtualDisplay.clear();
+    mPixels.clear();
 }
 
 void AndroidDesktop::processFrames() {
-    Mutex::Autolock _l(mMutex);
+    Mutex::Autolock _l(mLock);
 
-    // do any pending resize
-    processDesktopResize();
-
-    if (!mFrameAvailable) {
-        return;
-    }
+    updateDisplayInfo();
 
     // get a frame from the virtual display
     CpuConsumer::LockedBuffer imgBuffer;
-    status_t res = mCpuConsumer->lockNextBuffer(&imgBuffer);
+    status_t res = mVirtualDisplay->getConsumer()->lockNextBuffer(&imgBuffer);
     if (res != OK) {
         ALOGE("Failed to lock next buffer: %s (%d)", strerror(-res), res);
         return;
@@ -195,11 +91,10 @@
     // directly without copying because it is likely uncached
     mPixels->imageRect(bufRect, imgBuffer.data, imgBuffer.stride);
 
-    mCpuConsumer->unlockBuffer(imgBuffer);
+    mVirtualDisplay->getConsumer()->unlockBuffer(imgBuffer);
 
     // update clients
     mServer->add_changed(bufRect);
-    mFrameAvailable = false;
 }
 
 // notifies the server loop that we have changes
@@ -211,58 +106,35 @@
 // called when a client resizes the window
 unsigned int AndroidDesktop::setScreenLayout(int reqWidth, int reqHeight,
                                              const rfb::ScreenSet& layout) {
-    Mutex::Autolock _l(mMutex);
+    Mutex::Autolock _l(mLock);
 
     char* dbg = new char[1024];
     layout.print(dbg, 1024);
 
-    ALOGD("setScreenLayout: cur: %lux%lu  new: %dx%d %s", mWidth, mHeight, reqWidth, reqHeight, dbg);
+    ALOGD("setScreenLayout: cur: %s  new: %dx%d", dbg, reqWidth, reqHeight);
     delete[] dbg;
 
-    if (reqWidth == (int)mWidth && reqHeight == (int)mHeight) {
+    if (reqWidth == mDisplayRect.getWidth() && reqHeight == mDisplayRect.getHeight()) {
         return rfb::resultInvalid;
     }
 
     if (reqWidth > 0 && reqHeight > 0) {
-        mWidth = reqWidth;
-        mHeight = reqHeight;
+        mPixels->setWindowSize(reqWidth, reqHeight);
 
-        if (updateDisplayProjection() == NO_ERROR) {
-            // resize immediately
-            processDesktopResize();
-            notify();
-            return rfb::resultSuccess;
-        }
+        rfb::ScreenSet screens;
+        screens.add_screen(rfb::Screen(0, 0, 0, mPixels->width(), mPixels->height(), 0));
+        mServer->setScreenLayout(screens);
+        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();
+void AndroidDesktop::onFrameAvailable(const BufferItem& item) {
     ALOGV("onFrameAvailable: [%lu] mTimestamp=%ld", item.mFrameNumber, item.mTimestamp);
+
+    notify();
 }
 
 rfb::Point AndroidDesktop::getFbSize() {
@@ -274,67 +146,47 @@
 }
 
 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) {
+    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);
+    uint32_t x = pos.x * ((float)(mDisplayRect.getWidth()) / (float)mPixels->width());
+    uint32_t y = pos.y * ((float)(mDisplayRect.getHeight()) / (float)mPixels->height());
 
-    ALOGD("pointer xlate x1=%d y1=%d x2=%d y2=%d", pos.x, pos.y, x, y);
+    ALOGV("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);
+// refresh the display dimensions
+status_t AndroidDesktop::updateDisplayInfo() {
+    status_t err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mDisplayInfo);
     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);
+    mPixels->setDisplayInfo(&mDisplayInfo);
 
     return NO_ERROR;
 }
+
+void AndroidDesktop::onBufferDimensionsChanged(uint32_t width, uint32_t height) {
+    ALOGV("Dimensions changed: old=(%ux%u) new=(%ux%u)", mDisplayRect.getWidth(),
+          mDisplayRect.getHeight(), width, height);
+
+    mVirtualDisplay.clear();
+    mVirtualDisplay = new VirtualDisplay(&mDisplayInfo, mPixels->width(), mPixels->height(), this);
+
+    mDisplayRect = mVirtualDisplay->getDisplayRect();
+
+    mInputDevice->reconfigure(mDisplayRect.getWidth(), mDisplayRect.getHeight());
+
+    mServer->setPixelBuffer(mPixels.get());
+
+    rfb::ScreenSet screens;
+    screens.add_screen(rfb::Screen(0, 0, 0, mPixels->width(), mPixels->height(), 0));
+    mServer->setScreenLayout(screens);
+}
diff --git a/src/AndroidDesktop.h b/src/AndroidDesktop.h
index 0d3f8e4..768c27c 100644
--- a/src/AndroidDesktop.h
+++ b/src/AndroidDesktop.h
@@ -10,20 +10,23 @@
 
 #include <gui/CpuConsumer.h>
 
+#include <ui/DisplayInfo.h>
+
 #include <rfb/PixelBuffer.h>
 #include <rfb/SDesktop.h>
 #include <rfb/VNCServerST.h>
 
+#include "AndroidPixelBuffer.h"
 #include "InputDevice.h"
-
+#include "VirtualDisplay.h"
 
 using namespace android;
 
 namespace vncflinger {
 
-static const rfb::PixelFormat pfRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
-
-class AndroidDesktop : public rfb::SDesktop, public RefBase {
+class AndroidDesktop : public rfb::SDesktop,
+                       public CpuConsumer::FrameAvailableListener,
+                       public AndroidPixelBuffer::BufferDimensionsListener {
   public:
     AndroidDesktop();
 
@@ -44,77 +47,35 @@
         return mEventFd;
     }
 
+    virtual void onBufferDimensionsChanged(uint32_t width, uint32_t height);
+
+    virtual void onFrameAvailable(const BufferItem& item);
+
   private:
-    class FrameListener : public CpuConsumer::FrameAvailableListener {
-      public:
-        FrameListener(AndroidDesktop* desktop) : mDesktop(desktop) {
-        }
-
-        virtual void onFrameAvailable(const BufferItem& item);
-
-      private:
-        FrameListener(FrameListener&) {
-        }
-        AndroidDesktop* mDesktop;
-    };
-
-    class AndroidPixelBuffer : public RefBase, public rfb::ManagedPixelBuffer {
-      public:
-        AndroidPixelBuffer(uint64_t width, uint64_t height)
-            : rfb::ManagedPixelBuffer(sRGBX, width, height) {
-        }
-    };
-
-    virtual status_t createVirtualDisplay();
-    virtual status_t destroyVirtualDisplay();
-
     virtual void notify();
 
-    virtual status_t updateDisplayProjection();
-    virtual bool updateFBSize(uint64_t width, uint64_t height);
-    virtual void processDesktopResize();
+    virtual status_t updateDisplayInfo();
 
-    uint64_t mSourceWidth, mSourceHeight;
-    uint64_t mWidth, mHeight;
     Rect mDisplayRect;
 
-    bool mRotated;
-
-    Mutex mMutex;
-
-    bool mFrameAvailable;
-    bool mProjectionChanged;
-    bool mRotate;
-    bool mVDSActive;
+    Mutex mLock;
 
     uint64_t mFrameNumber;
-    nsecs_t mFrameStartWhen;
 
     int mEventFd;
 
-    // Android virtual display is always 32-bit
-    static const rfb::PixelFormat sRGBX;
-
     // Server instance
     rfb::VNCServerST* mServer;
 
     // Pixel buffer
     sp<AndroidPixelBuffer> mPixels;
 
+    // Virtual display controller
+    sp<VirtualDisplay> mVirtualDisplay;
+
     // Primary display
     sp<IBinder> mMainDpy;
-
-    // Virtual display
-    sp<IBinder> mDpy;
-
-    // Producer side of queue, passed into the virtual display.
-    sp<IGraphicBufferProducer> mProducer;
-
-    // This receives frames from the virtual display and makes them available
-    sp<CpuConsumer> mCpuConsumer;
-
-    // Listener for virtual display buffers
-    sp<FrameListener> mListener;
+    DisplayInfo mDisplayInfo;
 
     // Virtual input device
     sp<InputDevice> mInputDevice;
diff --git a/src/AndroidPixelBuffer.cpp b/src/AndroidPixelBuffer.cpp
new file mode 100644
index 0000000..7cbb9a7
--- /dev/null
+++ b/src/AndroidPixelBuffer.cpp
@@ -0,0 +1,119 @@
+//
+// vncflinger - Copyright (C) 2017 Steve Kondik
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#define LOG_TAG "AndroidPixelBuffer"
+#include <utils/Log.h>
+
+#include <ui/DisplayInfo.h>
+
+#include "AndroidPixelBuffer.h"
+
+using namespace vncflinger;
+using namespace android;
+
+const rfb::PixelFormat AndroidPixelBuffer::sRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
+
+AndroidPixelBuffer::AndroidPixelBuffer()
+    : ManagedPixelBuffer(), mRotated(false), mScaleX(1.0f), mScaleY(1.0f) {
+    setPF(sRGBX);
+    setSize(0, 0);
+}
+
+AndroidPixelBuffer::~AndroidPixelBuffer() {
+    mListener = nullptr;
+}
+
+bool AndroidPixelBuffer::isDisplayRotated(uint8_t orientation) {
+    return orientation != DISPLAY_ORIENTATION_0 && orientation != DISPLAY_ORIENTATION_180;
+}
+
+void AndroidPixelBuffer::setBufferRotation(bool rotated) {
+    if (rotated != mRotated) {
+        ALOGV("Orientation changed, swap width/height");
+        mRotated = rotated;
+        setSize(height_, width_);
+        std::swap(mScaleX, mScaleY);
+        stride = width_;
+
+        if (mListener != nullptr) {
+            mListener->onBufferDimensionsChanged(width_, height_);
+        }
+    }
+}
+
+void AndroidPixelBuffer::updateBufferSize(bool fromDisplay) {
+    uint32_t w = 0, h = 0;
+
+    // if this was caused by the source size changing (doesn't really
+    // happen on most Android hardware), then we need to consider
+    // a previous window size set by the client
+    if (fromDisplay) {
+        w = (uint32_t)((float)mSourceWidth * mScaleX);
+        h = (uint32_t)((float)mSourceHeight * mScaleY);
+        mClientWidth = w;
+        mClientHeight = h;
+    } else {
+        w = mClientWidth;
+        h = mClientHeight;
+    }
+
+    mScaleX = (float)mClientWidth / (float)mSourceWidth;
+    mScaleY = (float)mClientHeight / (float)mSourceHeight;
+
+    if (w == (uint32_t)width_ && h == (uint32_t)height_) {
+        return;
+    }
+
+    ALOGV("Buffer dimensions changed: old=(%dx%d) new=(%dx%d) scaleX=%f scaleY=%f", width_, height_,
+          w, h, mScaleX, mScaleY);
+
+    setSize(w, h);
+
+    if (mListener != nullptr) {
+        mListener->onBufferDimensionsChanged(width_, height_);
+    }
+}
+
+void AndroidPixelBuffer::setWindowSize(uint32_t width, uint32_t height) {
+    if (mClientWidth != width || mClientHeight != height) {
+        ALOGV("Client window size changed: old=(%dx%d) new=(%dx%d)", mClientWidth, mClientHeight,
+              width, height);
+        mClientWidth = width;
+        mClientHeight = height;
+        updateBufferSize();
+    }
+}
+
+void AndroidPixelBuffer::setDisplayInfo(DisplayInfo* info) {
+    bool rotated = isDisplayRotated(info->orientation);
+    setBufferRotation(rotated);
+
+    uint32_t w = rotated ? info->h : info->w;
+    uint32_t h = rotated ? info->w : info->h;
+
+    if (w != mSourceWidth || h != mSourceHeight) {
+        ALOGV("Display dimensions changed: old=(%dx%d) new=(%dx%d)", mSourceWidth, mSourceHeight, w,
+              h);
+        mSourceWidth = w;
+        mSourceHeight = h;
+        updateBufferSize(true);
+    }
+}
+
+Rect AndroidPixelBuffer::getSourceRect() {
+    return Rect(mSourceWidth, mSourceHeight);
+}
diff --git a/src/AndroidPixelBuffer.h b/src/AndroidPixelBuffer.h
new file mode 100644
index 0000000..5cd314c
--- /dev/null
+++ b/src/AndroidPixelBuffer.h
@@ -0,0 +1,90 @@
+//
+// vncflinger - Copyright (C) 2017 Steve Kondik
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef ANDROID_PIXEL_BUFFER_H
+#define ANDROID_PIXEL_BUFFER_H
+
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+
+#include <ui/DisplayInfo.h>
+#include <ui/Rect.h>
+
+#include <rfb/PixelBuffer.h>
+#include <rfb/PixelFormat.h>
+
+using namespace android;
+
+namespace vncflinger {
+
+class AndroidPixelBuffer : public RefBase, public rfb::ManagedPixelBuffer {
+  public:
+    AndroidPixelBuffer();
+
+    virtual void setDisplayInfo(DisplayInfo* info);
+
+    virtual void setWindowSize(uint32_t width, uint32_t height);
+
+    virtual ~AndroidPixelBuffer();
+
+    class BufferDimensionsListener {
+      public:
+        virtual void onBufferDimensionsChanged(uint32_t width, uint32_t height) = 0;
+        virtual ~BufferDimensionsListener() {
+        }
+    };
+
+    void setDimensionsChangedListener(BufferDimensionsListener* listener) {
+        mListener = listener;
+    }
+
+    bool isRotated() {
+        return mRotated;
+    }
+
+    Rect getSourceRect();
+
+  private:
+    static bool isDisplayRotated(uint8_t orientation);
+
+    virtual void setBufferRotation(bool rotated);
+
+    virtual void updateBufferSize(bool fromDisplay = false);
+
+    Mutex mLock;
+
+    // width/height is swapped due to display orientation
+    bool mRotated;
+
+    // preferred size of the client's window
+    uint32_t mClientWidth, mClientHeight;
+
+    // size of the display
+    uint32_t mSourceWidth, mSourceHeight;
+
+    // current ratio between server and client
+    float mScaleX, mScaleY;
+
+    // callback when buffer size changes
+    BufferDimensionsListener* mListener;
+
+    // Android virtual display is always 32-bit
+    static const rfb::PixelFormat sRGBX;
+};
+};
+
+#endif
diff --git a/src/VirtualDisplay.cpp b/src/VirtualDisplay.cpp
new file mode 100644
index 0000000..8000f8f
--- /dev/null
+++ b/src/VirtualDisplay.cpp
@@ -0,0 +1,94 @@
+//
+// vncflinger - Copyright (C) 2017 Steve Kondik
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#define LOG_TAG "VirtualDisplay"
+#include <utils/Log.h>
+
+#include <gui/BufferQueue.h>
+#include <gui/CpuConsumer.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/SurfaceComposerClient.h>
+
+#include "VirtualDisplay.h"
+
+using namespace vncflinger;
+
+VirtualDisplay::VirtualDisplay(DisplayInfo* info, uint32_t width, uint32_t height,
+                               sp<CpuConsumer::FrameAvailableListener> listener) {
+    mWidth = width;
+    mHeight = height;
+
+    if (info->orientation == DISPLAY_ORIENTATION_0 || info->orientation == DISPLAY_ORIENTATION_180) {
+        mSourceRect = Rect(info->w, info->h);
+    } else {
+        mSourceRect = Rect(info->h, info->w);
+    }
+
+    Rect displayRect = getDisplayRect();
+
+    sp<IGraphicBufferConsumer> consumer;
+    BufferQueue::createBufferQueue(&mProducer, &consumer);
+    mCpuConsumer = new CpuConsumer(consumer, 1);
+    mCpuConsumer->setName(String8("vds-to-cpu"));
+    mCpuConsumer->setDefaultBufferSize(width, height);
+    mProducer->setMaxDequeuedBufferCount(4);
+    consumer->setDefaultBufferFormat(PIXEL_FORMAT_RGBX_8888);
+
+    mCpuConsumer->setFrameAvailableListener(listener);
+
+    mDpy = SurfaceComposerClient::createDisplay(String8("VNC-VirtualDisplay"), false /*secure*/);
+
+    SurfaceComposerClient::openGlobalTransaction();
+    SurfaceComposerClient::setDisplaySurface(mDpy, mProducer);
+
+    SurfaceComposerClient::setDisplayProjection(mDpy, 0, mSourceRect, displayRect);
+    SurfaceComposerClient::setDisplayLayerStack(mDpy, 0);  // default stack
+    SurfaceComposerClient::closeGlobalTransaction();
+
+    ALOGV("Virtual display (%ux%u [viewport=%ux%u] created", width, height, displayRect.getWidth(),
+          displayRect.getHeight());
+}
+
+VirtualDisplay::~VirtualDisplay() {
+    mCpuConsumer.clear();
+    mProducer.clear();
+    SurfaceComposerClient::destroyDisplay(mDpy);
+
+    ALOGV("Virtual display destroyed");
+}
+
+Rect VirtualDisplay::getDisplayRect() {
+    uint32_t outWidth, outHeight;
+    if (mWidth > (uint32_t)((float)mWidth * aspectRatio())) {
+        // limited by narrow width; reduce height
+        outWidth = mWidth;
+        outHeight = (uint32_t)((float)mWidth * aspectRatio());
+    } else {
+        // limited by short height; restrict width
+        outHeight = mHeight;
+        outWidth = (uint32_t)((float)mHeight / aspectRatio());
+    }
+
+    // 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;
+    return Rect(offX, offY, offX + outWidth, offY + outHeight);
+}
diff --git a/src/VirtualDisplay.h b/src/VirtualDisplay.h
index f23d32d..002d87d 100644
--- a/src/VirtualDisplay.h
+++ b/src/VirtualDisplay.h
@@ -1,26 +1,70 @@
+//
+// vncflinger - Copyright (C) 2017 Steve Kondik
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef VIRTUAL_DISPLAY_H_
+#define VIRTUAL_DISPLAY_H_
+
+#include <utils/RefBase.h>
+
 #include <gui/CpuConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+
+#include <ui/DisplayInfo.h>
+#include <ui/Rect.h>
+
+using namespace android;
 
 namespace vncflinger {
 
-class VirtualDisplay {
+class VirtualDisplay : public RefBase {
   public:
-    VirtualDisplay();
+    VirtualDisplay(DisplayInfo* info, uint32_t width, uint32_t height,
+                   sp<CpuConsumer::FrameAvailableListener> listener);
 
-    virtual void onFrameAvailable(const BufferItem& item);
+    virtual ~VirtualDisplay();
+
+    virtual Rect getDisplayRect();
+
+    virtual Rect getSourceRect() {
+        return mSourceRect;
+    }
+
+    CpuConsumer* getConsumer() {
+        return mCpuConsumer.get();
+    }
 
   private:
-    Mutex mEventMutex;
-    Condition mEventCond;
-
-    bool mFrameAvailable;
-
-    // Virtual display
-    sp<IBinder> mDpy;
+    float aspectRatio() {
+        return (float)mSourceRect.getHeight() / (float)mSourceRect.getWidth();
+    }
 
     // Producer side of queue, passed into the virtual display.
     sp<IGraphicBufferProducer> mProducer;
 
     // This receives frames from the virtual display and makes them available
     sp<CpuConsumer> mCpuConsumer;
+
+    // Virtual display
+    sp<IBinder> mDpy;
+
+    sp<CpuConsumer::FrameAvailableListener> mListener;
+
+    uint32_t mWidth, mHeight;
+    Rect mSourceRect;
 };
 };
+#endif
diff --git a/src/main.cpp b/src/main.cpp
index c22f075..831268d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,6 +5,10 @@
 
 #include "AndroidDesktop.h"
 
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
 #include <network/Socket.h>
 #include <network/TcpSocket.h>
 #include <rfb/Configuration.h>
@@ -13,6 +17,7 @@
 #include <rfb/util.h>
 
 using namespace vncflinger;
+using namespace android;
 
 static char* gProgramName;
 static bool gCaughtSignal = false;
@@ -67,6 +72,9 @@
         usage();
     }
 
+    sp<ProcessState> self = ProcessState::self();
+    self->startThreadPool();
+
     std::list<network::TcpListener*> listeners;
 
     try {
@@ -157,6 +165,7 @@
             uint64_t eventVal;
             int status = read(eventFd, &eventVal, sizeof(eventVal));
             if (status > 0 && eventVal > 0) {
+                ALOGV("status=%d eventval=%lu", status, eventVal);
                 desktop->processFrames();
             }
         }