vnc: Orientation change support
* Detect orientation changes and reconfigure appropriately.
* Clean up the uinput code.
* FIXME: The virtual mouse isn't reconfiguring correctly yet.
diff --git a/src/InputDevice.cpp b/src/InputDevice.cpp
index 5a3aa9f..1f07f5a 100644
--- a/src/InputDevice.cpp
+++ b/src/InputDevice.cpp
@@ -32,98 +32,105 @@
using namespace android;
+ANDROID_SINGLETON_STATIC_INSTANCE(InputDevice)
+
static const struct UInputOptions {
int cmd;
int bit;
} kOptions[] = {
{UI_SET_EVBIT, EV_KEY},
- {UI_SET_EVBIT, EV_REP},
+ {UI_SET_EVBIT, EV_REL},
{UI_SET_EVBIT, EV_ABS},
{UI_SET_EVBIT, EV_SYN},
{UI_SET_ABSBIT, ABS_X},
{UI_SET_ABSBIT, ABS_Y},
+ {UI_SET_RELBIT, REL_WHEEL},
{UI_SET_PROPBIT, INPUT_PROP_DIRECT},
};
-int InputDevice::sFD = -1;
-
status_t InputDevice::start(uint32_t width, uint32_t height) {
+ Mutex::Autolock _l(mLock);
+
status_t err = OK;
- struct uinput_user_dev user_dev;
struct input_id id = {
BUS_VIRTUAL, /* Bus type */
1, /* Vendor */
1, /* Product */
- 1, /* Version */
+ 4, /* Version */
};
- if (sFD >= 0) {
+ if (mFD >= 0) {
ALOGE("Input device already open!");
return NO_INIT;
}
- sFD = open(UINPUT_DEVICE, O_WRONLY | O_NONBLOCK);
- if (sFD < 0) {
- ALOGE("Failed to open %s: err=%d", UINPUT_DEVICE, sFD);
+ mFD = open(UINPUT_DEVICE, O_WRONLY | O_NONBLOCK);
+ if (mFD < 0) {
+ ALOGE("Failed to open %s: err=%d", UINPUT_DEVICE, mFD);
return NO_INIT;
}
unsigned int idx = 0;
for (idx = 0; idx < sizeof(kOptions) / sizeof(kOptions[0]); idx++) {
- if (ioctl(sFD, kOptions[idx].cmd, kOptions[idx].bit) < 0) {
+ if (ioctl(mFD, kOptions[idx].cmd, kOptions[idx].bit) < 0) {
ALOGE("uinput ioctl failed: %d %d", kOptions[idx].cmd, kOptions[idx].bit);
goto err_ioctl;
}
}
for (idx = 0; idx < KEY_MAX; idx++) {
- if (ioctl(sFD, UI_SET_KEYBIT, idx) < 0) {
+ if (ioctl(mFD, UI_SET_KEYBIT, idx) < 0) {
ALOGE("UI_SET_KEYBIT failed");
goto err_ioctl;
}
}
- memset(&user_dev, 0, sizeof(user_dev));
- strncpy(user_dev.name, "VNC", UINPUT_MAX_NAME_SIZE);
+ memset(&mUserDev, 0, sizeof(mUserDev));
+ strncpy(mUserDev.name, "VNC-RemoteInput", UINPUT_MAX_NAME_SIZE);
- user_dev.id = id;
+ mUserDev.id = id;
- user_dev.absmin[ABS_X] = 0;
- user_dev.absmax[ABS_X] = width;
- user_dev.absmin[ABS_Y] = 0;
- user_dev.absmax[ABS_Y] = height;
+ mUserDev.absmin[ABS_X] = 0;
+ mUserDev.absmax[ABS_X] = width;
+ mUserDev.absmin[ABS_Y] = 0;
+ mUserDev.absmax[ABS_Y] = height;
- if (write(sFD, &user_dev, sizeof(user_dev)) != sizeof(user_dev)) {
+ if (write(mFD, &mUserDev, sizeof(mUserDev)) != sizeof(mUserDev)) {
ALOGE("Failed to configure uinput device");
goto err_ioctl;
}
- if (ioctl(sFD, UI_DEV_CREATE) == -1) {
+ if (ioctl(mFD, UI_DEV_CREATE) == -1) {
ALOGE("UI_DEV_CREATE failed");
goto err_ioctl;
}
- return OK;
+ ALOGD("Virtual input device created successfully (%dx%d)", width, height);
+ return NO_ERROR;
err_ioctl:
int prev_errno = errno;
- ::close(sFD);
+ ::close(mFD);
errno = prev_errno;
- sFD = -1;
+ mFD = -1;
return NO_INIT;
}
+status_t InputDevice::reconfigure(uint32_t width, uint32_t height) {
+ stop();
+ return start(width, height);
+}
+
status_t InputDevice::stop() {
- if (sFD < 0) {
+ Mutex::Autolock _l(mLock);
+ if (mFD < 0) {
return OK;
}
- sleep(2);
-
- ioctl(sFD, UI_DEV_DESTROY);
- close(sFD);
- sFD = -1;
+ ioctl(mFD, UI_DEV_DESTROY);
+ close(mFD);
+ mFD = -1;
return OK;
}
@@ -135,7 +142,7 @@
event.type = type;
event.code = code;
event.value = value;
- if (write(sFD, &event, sizeof(event)) != sizeof(event)) return BAD_VALUE;
+ if (write(mFD, &event, sizeof(event)) != sizeof(event)) return BAD_VALUE;
return OK;
}
@@ -175,12 +182,18 @@
return release(code);
}
+void InputDevice::onKeyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl) {
+ InputDevice::getInstance().keyEvent(down, key, cl);
+}
+
void InputDevice::keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl) {
int code;
int sh = 0;
int alt = 0;
- if (sFD < 0) return;
+ if (mFD < 0) return;
+
+ Mutex::Autolock _l(mLock);
if ((code = keysym2scancode(key, cl, &sh, &alt))) {
int ret = 0;
@@ -213,11 +226,19 @@
}
}
+void InputDevice::onPointerEvent(int buttonMask, int x, int y, rfbClientPtr cl) {
+ InputDevice::getInstance().pointerEvent(buttonMask, x, y, cl);
+}
+
void InputDevice::pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl) {
static int leftClicked = 0, rightClicked = 0, middleClicked = 0;
(void)cl;
- if (sFD < 0) return;
+ if (mFD < 0) return;
+
+ ALOGV("pointerEvent: buttonMask=%x x=%d y=%d", buttonMask, x, y);
+
+ Mutex::Autolock _l(mLock);
if ((buttonMask & 1) && leftClicked) { // left btn clicked and moving
static int i = 0;
@@ -229,8 +250,7 @@
inject(EV_ABS, ABS_Y, y);
inject(EV_SYN, SYN_REPORT, 0);
}
- } else if (buttonMask & 1) // left btn clicked
- {
+ } else if (buttonMask & 1) { // left btn clicked
leftClicked = 1;
inject(EV_ABS, ABS_X, x);
@@ -269,6 +289,14 @@
release(KEY_END);
inject(EV_SYN, SYN_REPORT, 0);
}
+
+ if (buttonMask & 8) {
+ inject(EV_REL, REL_WHEEL, 1);
+ }
+
+ if (buttonMask & 0x10) {
+ inject(EV_REL, REL_WHEEL, -1);
+ }
}
// q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m
diff --git a/src/InputDevice.h b/src/InputDevice.h
index 19595a2..20f5063 100644
--- a/src/InputDevice.h
+++ b/src/InputDevice.h
@@ -19,37 +19,51 @@
#define INPUT_DEVICE_H
#include <utils/Errors.h>
-#include <utils/RefBase.h>
+#include <utils/Mutex.h>
+#include <utils/Singleton.h>
#include <rfb/rfb.h>
-
+#include <linux/uinput.h>
#define UINPUT_DEVICE "/dev/uinput"
namespace android {
-class InputDevice : public RefBase {
-public:
- static status_t start(uint32_t width, uint32_t height);
- static status_t stop();
+class InputDevice : public Singleton<InputDevice> {
- static void keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
- static void pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
+friend class Singleton;
+
+public:
+ virtual status_t start(uint32_t width, uint32_t height);
+ virtual status_t stop();
+ virtual status_t reconfigure(uint32_t width, uint32_t height);
+
+ static void onKeyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+ static void onPointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
+
+ InputDevice() : mFD(-1) {}
+ virtual ~InputDevice() {}
private:
- static status_t inject(uint16_t type, uint16_t code, int32_t value);
- static status_t injectSyn(uint16_t type, uint16_t code, int32_t value);
- static status_t movePointer(int32_t x, int32_t y);
- static status_t setPointer(int32_t x, int32_t y);
- static status_t press(uint16_t code);
- static status_t release(uint16_t code);
- static status_t click(uint16_t code);
+ status_t inject(uint16_t type, uint16_t code, int32_t value);
+ status_t injectSyn(uint16_t type, uint16_t code, int32_t value);
+ status_t movePointer(int32_t x, int32_t y);
+ status_t setPointer(int32_t x, int32_t y);
+ status_t press(uint16_t code);
+ status_t release(uint16_t code);
+ status_t click(uint16_t code);
- static int keysym2scancode(rfbKeySym c, rfbClientPtr cl, int* sh, int* alt);
+ void keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+ void pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
- static int sFD;
+ int keysym2scancode(rfbKeySym c, rfbClientPtr cl, int* sh, int* alt);
+ Mutex mLock;
+
+ int mFD;
+
+ struct uinput_user_dev mUserDev;
};
};
diff --git a/src/VNCFlinger.cpp b/src/VNCFlinger.cpp
index 4517e35..fa28bbd 100644
--- a/src/VNCFlinger.cpp
+++ b/src/VNCFlinger.cpp
@@ -36,28 +36,21 @@
sp<ProcessState> self = ProcessState::self();
self->startThreadPool();
- status_t err = NO_ERROR;
-
mMainDpy = SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain);
- err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &mMainDpyInfo);
- if (err != NO_ERROR) {
- ALOGE("Failed to get display characteristics\n");
- return err;
- }
- mHeight = mMainDpyInfo.h;
- mWidth = mMainDpyInfo.w;
- err = createVNCServer();
+ updateDisplayProjection();
+
+ status_t err = createVNCServer();
if (err != NO_ERROR) {
ALOGE("Failed to start VNCFlinger: err=%d", err);
return err;
}
- ALOGD("VNCFlinger is running!");
-
rfbRunEventLoop(mVNCScreen, -1, true);
+ ALOGD("VNCFlinger is running!");
+
eventLoop();
return NO_ERROR;
@@ -86,12 +79,14 @@
while (mClientCount > 0) {
mEventCond.wait(mEventMutex);
if (mFrameAvailable) {
- processFrame();
+ if (!updateDisplayProjection()) {
+ processFrame();
+ }
mFrameAvailable = false;
}
}
-
- destroyVirtualDisplay();
+ Mutex::Autolock _l(mUpdateMutex);
+ destroyVirtualDisplayLocked();
}
}
@@ -115,14 +110,23 @@
SurfaceComposerClient::setDisplayLayerStack(mDpy, 0); // default stack
SurfaceComposerClient::closeGlobalTransaction();
- ALOGV("Virtual display created");
+ mVDSActive = true;
+
+ ALOGV("Virtual display (%dx%d) created", mWidth, mHeight);
+
return NO_ERROR;
}
-status_t VNCFlinger::destroyVirtualDisplay() {
+status_t VNCFlinger::destroyVirtualDisplayLocked() {
+
mCpuConsumer.clear();
mProducer.clear();
SurfaceComposerClient::destroyDisplay(mDpy);
+
+ mVDSActive = false;
+
+ ALOGV("Virtual display destroyed");
+
return NO_ERROR;
}
@@ -147,14 +151,14 @@
mVNCScreen->httpDir = NULL;
mVNCScreen->port = VNC_PORT;
mVNCScreen->newClientHook = (rfbNewClientHookPtr) VNCFlinger::onNewClient;
- mVNCScreen->kbdAddEvent = InputDevice::keyEvent;
- mVNCScreen->ptrAddEvent = InputDevice::pointerEvent;
+ mVNCScreen->kbdAddEvent = InputDevice::onKeyEvent;
+ mVNCScreen->ptrAddEvent = InputDevice::onPointerEvent;
mVNCScreen->displayHook = (rfbDisplayHookPtr) VNCFlinger::onFrameStart;
mVNCScreen->displayFinishedHook = (rfbDisplayFinishedHookPtr) VNCFlinger::onFrameDone;
mVNCScreen->serverFormat.trueColour = true;
mVNCScreen->serverFormat.bitsPerPixel = 32;
mVNCScreen->handleEventsEagerly = true;
- mVNCScreen->deferUpdateTime = 0;
+ mVNCScreen->deferUpdateTime = 1;
mVNCScreen->screenData = this;
rfbInitServer(mVNCScreen);
@@ -179,7 +183,8 @@
Mutex::Autolock _l(mEventMutex);
if (mClientCount == 0) {
mClientCount++;
- InputDevice::start(mWidth, mHeight);
+ updateFBSize(mWidth, mHeight, mWidth);
+ InputDevice::getInstance().start(mWidth, mHeight);
mEventCond.signal();
}
@@ -193,7 +198,7 @@
if (mClientCount > 0) {
mClientCount--;
if (mClientCount == 0) {
- InputDevice::stop();
+ InputDevice::getInstance().stop();
mEventCond.signal();
}
}
@@ -226,6 +231,11 @@
void VNCFlinger::onFrameDone(rfbClientPtr cl, int status) {
VNCFlinger *vf = (VNCFlinger *)cl->screen->screenData;
+
+ if (vf->mInputReconfigPending) {
+ //InputDevice::getInstance().reconfigure(vf->mWidth, vf->mHeight);
+ vf->mInputReconfigPending = false;
+ }
vf->mUpdateMutex.unlock();
ALOGV("frame done! %d", status);
}
@@ -268,22 +278,89 @@
imgBuffer.data, imgBuffer.format, imgBuffer.width,
imgBuffer.height, imgBuffer.stride);
- void* vncbuf = mVNCScreen->frameBuffer;
- void* imgbuf = imgBuffer.data;
+ updateFBSize(imgBuffer.width, imgBuffer.height, imgBuffer.stride);
- // Copy the frame to the server's buffer
- if (imgBuffer.stride > mWidth) {
- // Image has larger stride, so we need to copy row by row
- for (size_t y = 0; y < mHeight; y++) {
- memcpy(vncbuf, imgbuf, mWidth * 4);
- vncbuf = (void *)((char *)vncbuf + mWidth * 4);
- imgbuf = (void *)((char *)imgbuf + imgBuffer.stride * 4);
- }
- } else {
- memcpy(vncbuf, imgbuf, mWidth * mHeight * 4);
- }
+ memcpy(mVNCBuf, imgBuffer.data, imgBuffer.stride * imgBuffer.height * 4);
- rfbMarkRectAsModified(mVNCScreen, 0, 0, mWidth, mHeight);
+ rfbMarkRectAsModified(mVNCScreen, 0, 0, imgBuffer.width, imgBuffer.height);
mCpuConsumer->unlockBuffer(imgBuffer);
}
+
+/*
+ * Returns "true" if the device is rotated 90 degrees.
+ */
+bool VNCFlinger::isDeviceRotated(int orientation) {
+ return orientation != DISPLAY_ORIENTATION_0 &&
+ orientation != DISPLAY_ORIENTATION_180;
+}
+
+/*
+ * Sets the display projection, based on the display dimensions, video size,
+ * and device orientation.
+ */
+bool VNCFlinger::updateDisplayProjection() {
+
+ DisplayInfo info;
+ status_t err = SurfaceComposerClient::getDisplayInfo(mMainDpy, &info);
+ if (err != NO_ERROR) {
+ ALOGE("Failed to get display characteristics\n");
+ return true;
+ }
+
+ if (info.orientation == mOrientation) {
+ return false;
+ }
+
+ // Set the region of the layer stack we're interested in, which in our
+ // case is "all of it". If the app is rotated (so that the width of the
+ // app is based on the height of the display), reverse width/height.
+ bool deviceRotated = isDeviceRotated(info.orientation);
+ int sourceWidth, sourceHeight;
+ if (!deviceRotated) {
+ sourceWidth = info.w;
+ sourceHeight = info.h;
+ } else {
+ ALOGV("using rotated width/height");
+ sourceHeight = info.w;
+ sourceWidth = info.h;
+ }
+
+ Mutex::Autolock _l(mUpdateMutex);
+ mWidth = sourceWidth;
+ mHeight = sourceHeight;
+ mOrientation = info.orientation;
+
+ if (!mVDSActive) {
+ return true;
+ }
+
+ destroyVirtualDisplayLocked();
+ createVirtualDisplay();
+ return true;
+}
+
+status_t VNCFlinger::updateFBSize(int width, int height, int stride) {
+ if ((mVNCScreen->paddedWidthInBytes / 4) != stride ||
+ mVNCScreen->height != height ||
+ mVNCScreen->width != width) {
+
+ ALOGD("updateFBSize: old=[%dx%d %d] new=[%dx%d %d]",
+ mVNCScreen->width, mVNCScreen->height, mVNCScreen->paddedWidthInBytes / 4,
+ width, height, stride);
+
+ delete[] mVNCBuf;
+ mVNCBuf = new uint8_t[stride * height * 4];
+ memset(mVNCBuf, 0, stride * height * 4);
+
+ // little dance here to avoid an ugly immediate resize
+ if (mVNCScreen->height != height || mVNCScreen->width != width) {
+ mInputReconfigPending = true;
+ rfbNewFramebuffer(mVNCScreen, (char *)mVNCBuf, width, height, 8, 3, 4);
+ } else {
+ mVNCScreen->frameBuffer = (char *)mVNCBuf;
+ }
+ mVNCScreen->paddedWidthInBytes = stride * 4;
+ }
+ return NO_ERROR;
+}
diff --git a/src/VNCFlinger.h b/src/VNCFlinger.h
index a446c81..0682a2f 100644
--- a/src/VNCFlinger.h
+++ b/src/VNCFlinger.h
@@ -32,7 +32,8 @@
VNCFlinger(int argc, char **argv) :
mArgc(argc),
mArgv(argv),
- mClientCount(0) {
+ mClientCount(0),
+ mOrientation(-1) {
}
virtual ~VNCFlinger() {}
@@ -60,11 +61,15 @@
virtual void eventLoop();
virtual status_t createVirtualDisplay();
- virtual status_t destroyVirtualDisplay();
+ virtual status_t destroyVirtualDisplayLocked();
virtual status_t createVNCServer();
virtual void processFrame();
+ virtual bool isDeviceRotated(int orientation);
+ virtual bool updateDisplayProjection();
+ virtual status_t updateFBSize(int width, int height, int stride);
+
// vncserver callbacks
static ClientGoneHookPtr onClientGone(rfbClientPtr cl);
static enum rfbNewClientAction onNewClient(rfbClientPtr cl);
@@ -74,6 +79,9 @@
bool mRunning;
bool mFrameAvailable;
+ bool mRotate;
+ bool mVDSActive;
+ bool mInputReconfigPending;
Mutex mEventMutex;
Mutex mUpdateMutex;
@@ -83,16 +91,17 @@
rfbScreenInfoPtr mVNCScreen;
uint8_t *mVNCBuf;
- uint32_t mWidth, mHeight;
+ int mWidth, mHeight;
sp<IBinder> mMainDpy;
- DisplayInfo mMainDpyInfo;
int mArgc;
char **mArgv;
size_t mClientCount;
+ int mOrientation;
+
sp<FrameListener> mListener;
// Producer side of queue, passed into the virtual display.