diff --git a/Android.mk b/Android.mk
index f7575fe459f6a793687d6ebb5b6d822b9953f26d..b6b8b2677c97847e54dbcbdf7f2d421104c7cf6d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -4,7 +4,8 @@ include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := \
     src/EglWindow.cpp \
-	src/EventQueue.cpp \
+    src/EventQueue.cpp \
+    src/InputDevice.cpp \
     src/Program.cpp \
     src/VirtualDisplay.cpp \
     src/VNCFlinger.cpp \
diff --git a/src/InputDevice.cpp b/src/InputDevice.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e74204ee6800b9969c521512f7860daa8434a180
--- /dev/null
+++ b/src/InputDevice.cpp
@@ -0,0 +1,466 @@
+#define LOG_TAG "VNC-InputDevice"
+#include <utils/Log.h>
+
+#include "InputDevice.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/input.h>
+#include <linux/uinput.h>
+
+#include <rfb/keysym.h>
+
+using namespace android;
+
+static const struct UInputOptions {
+    int cmd;
+    int bit;
+} kOptions[] = {
+    {UI_SET_EVBIT, EV_KEY},
+    {UI_SET_EVBIT, EV_REP},
+    {UI_SET_EVBIT, EV_ABS},
+    {UI_SET_EVBIT, EV_SYN},
+    {UI_SET_ABSBIT, ABS_X},
+    {UI_SET_ABSBIT, ABS_Y},
+    {UI_SET_PROPBIT, INPUT_PROP_DIRECT},
+};
+
+int InputDevice::sFD = -1;
+
+status_t InputDevice::start(uint32_t width, uint32_t height) {
+    status_t err = OK;
+    struct uinput_user_dev user_dev;
+
+    struct input_id id = {
+        BUS_VIRTUAL, /* Bus type */
+        1,           /* Vendor */
+        1,           /* Product */
+        1,           /* Version */
+    };
+
+    if (sFD >= 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);
+        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) {
+            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) {
+            ALOGE("UI_SET_KEYBIT failed");
+            goto err_ioctl;
+        }
+    }
+
+    memset(&user_dev, 0, sizeof(user_dev));
+    strncpy(user_dev.name, "VNC", UINPUT_MAX_NAME_SIZE);
+
+    user_dev.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;
+
+    if (write(sFD, &user_dev, sizeof(user_dev)) != sizeof(user_dev)) {
+        ALOGE("Failed to configure uinput device");
+        goto err_ioctl;
+    }
+
+    if (ioctl(sFD, UI_DEV_CREATE) == -1) {
+        ALOGE("UI_DEV_CREATE failed");
+        goto err_ioctl;
+    }
+
+    return OK;
+
+err_ioctl:
+    int prev_errno = errno;
+    ::close(sFD);
+    errno = prev_errno;
+    sFD = -1;
+    return NO_INIT;
+}
+
+status_t InputDevice::stop() {
+    if (sFD < 0) {
+        return OK;
+    }
+
+    sleep(2);
+
+    ioctl(sFD, UI_DEV_DESTROY);
+    close(sFD);
+    sFD = -1;
+
+    return OK;
+}
+
+status_t InputDevice::inject(uint16_t type, uint16_t code, int32_t value) {
+    struct input_event event;
+    memset(&event, 0, sizeof(event));
+    gettimeofday(&event.time, 0); /* This should not be able to fail ever.. */
+    event.type = type;
+    event.code = code;
+    event.value = value;
+    if (write(sFD, &event, sizeof(event)) != sizeof(event)) return BAD_VALUE;
+    return OK;
+}
+
+status_t InputDevice::injectSyn(uint16_t type, uint16_t code, int32_t value) {
+    if (inject(type, code, value) != OK) {
+        return BAD_VALUE;
+    }
+    return inject(EV_SYN, SYN_REPORT, 0);
+}
+
+status_t InputDevice::movePointer(int32_t x, int32_t y) {
+    if (inject(EV_REL, REL_X, x) != OK) {
+        return BAD_VALUE;
+    }
+    return injectSyn(EV_REL, REL_Y, y);
+}
+
+status_t InputDevice::setPointer(int32_t x, int32_t y) {
+    if (inject(EV_ABS, ABS_X, x) != OK) {
+        return BAD_VALUE;
+    }
+    return injectSyn(EV_ABS, ABS_Y, y);
+}
+
+status_t InputDevice::press(uint16_t code) {
+    return inject(EV_KEY, code, 1);
+}
+
+status_t InputDevice::release(uint16_t code) {
+    return inject(EV_KEY, code, 0);
+}
+
+status_t InputDevice::click(uint16_t code) {
+    if (press(code) != OK) {
+        return BAD_VALUE;
+    }
+    return release(code);
+}
+
+void InputDevice::keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl) {
+    int code;
+    int sh = 0;
+    int alt = 0;
+
+    if (sFD < 0) return;
+
+    if ((code = keysym2scancode(key, cl, &sh, &alt))) {
+        int ret = 0;
+
+        if (key && down) {
+            if (sh) press(42);   // left shift
+            if (alt) press(56);  // left alt
+
+            inject(EV_SYN, SYN_REPORT, 0);
+
+            ret = press(code);
+            if (ret != 0) {
+                ALOGE("Error: %d (%s)\n", errno, strerror(errno));
+            }
+
+            inject(EV_SYN, SYN_REPORT, 0);
+
+            ret = release(code);
+            if (ret != 0) {
+                ALOGE("Error: %d (%s)\n", errno, strerror(errno));
+            }
+
+            inject(EV_SYN, SYN_REPORT, 0);
+
+            if (alt) release(56);  // left alt
+            if (sh) release(42);   // left shift
+
+            inject(EV_SYN, SYN_REPORT, 0);
+        }
+    }
+}
+
+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 ((buttonMask & 1) && leftClicked) {  // left btn clicked and moving
+        static int i = 0;
+        i = i + 1;
+
+        if (i % 10 == 1)  // some tweak to not report every move event
+        {
+            inject(EV_ABS, ABS_X, x);
+            inject(EV_ABS, ABS_Y, y);
+            inject(EV_SYN, SYN_REPORT, 0);
+        }
+    } else if (buttonMask & 1)  // left btn clicked
+    {
+        leftClicked = 1;
+
+        inject(EV_ABS, ABS_X, x);
+        inject(EV_ABS, ABS_Y, y);
+        inject(EV_KEY, BTN_TOUCH, 1);
+        inject(EV_SYN, SYN_REPORT, 0);
+    } else if (leftClicked)  // left btn released
+    {
+        leftClicked = 0;
+        inject(EV_ABS, ABS_X, x);
+        inject(EV_ABS, ABS_Y, y);
+        inject(EV_KEY, BTN_TOUCH, 0);
+        inject(EV_SYN, SYN_REPORT, 0);
+    }
+
+    if (buttonMask & 4)  // right btn clicked
+    {
+        rightClicked = 1;
+        press(158);  // back key
+        inject(EV_SYN, SYN_REPORT, 0);
+    } else if (rightClicked)  // right button released
+    {
+        rightClicked = 0;
+        release(158);
+        inject(EV_SYN, SYN_REPORT, 0);
+    }
+
+    if (buttonMask & 2)  // mid btn clicked
+    {
+        middleClicked = 1;
+        press(KEY_END);
+        inject(EV_SYN, SYN_REPORT, 0);
+    } else if (middleClicked)  // mid btn released
+    {
+        middleClicked = 0;
+        release(KEY_END);
+        inject(EV_SYN, SYN_REPORT, 0);
+    }
+}
+
+// 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
+static const int qwerty[] = {30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50,
+                             49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44};
+// ,!,",#,$,%,&,',(,),*,+,,,-,.,/
+static const int spec1[] = {57, 2, 40, 4, 5, 6, 8, 40, 10, 11, 9, 13, 51, 12, 52, 52};
+static const int spec1sh[] = {0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1};
+// :,;,<,=,>,?,@
+static const int spec2[] = {39, 39, 227, 13, 228, 53, 3};
+static const int spec2sh[] = {1, 0, 1, 1, 1, 1, 1};
+// [,\,],^,_,`
+static const int spec3[] = {26, 43, 27, 7, 12, 399};
+static const int spec3sh[] = {0, 0, 0, 1, 1, 0};
+// {,|,},~
+static const int spec4[] = {26, 43, 27, 215, 14};
+static const int spec4sh[] = {1, 1, 1, 1, 0};
+
+int InputDevice::keysym2scancode(rfbKeySym c, rfbClientPtr cl, int* sh, int* alt) {
+    int real = 1;
+    if ('a' <= c && c <= 'z') return qwerty[c - 'a'];
+    if ('A' <= c && c <= 'Z') {
+        (*sh) = 1;
+        return qwerty[c - 'A'];
+    }
+    if ('1' <= c && c <= '9') return c - '1' + 2;
+    if (c == '0') return 11;
+    if (32 <= c && c <= 47) {
+        (*sh) = spec1sh[c - 32];
+        return spec1[c - 32];
+    }
+    if (58 <= c && c <= 64) {
+        (*sh) = spec2sh[c - 58];
+        return spec2[c - 58];
+    }
+    if (91 <= c && c <= 96) {
+        (*sh) = spec3sh[c - 91];
+        return spec3[c - 91];
+    }
+    if (123 <= c && c <= 127) {
+        (*sh) = spec4sh[c - 123];
+        return spec4[c - 123];
+    }
+    switch (c) {
+        case 0xff08:
+            return 14;  // backspace
+        case 0xff09:
+            return 15;  // tab
+        case 1:
+            (*alt) = 1;
+            return 34;  // ctrl+a
+        case 3:
+            (*alt) = 1;
+            return 46;  // ctrl+c
+        case 4:
+            (*alt) = 1;
+            return 32;  // ctrl+d
+        case 18:
+            (*alt) = 1;
+            return 31;  // ctrl+r
+        case 0xff0D:
+            return 28;  // enter
+        case 0xff1B:
+            return 158;  // esc -> back
+        case 0xFF51:
+            return 105;  // left -> DPAD_LEFT
+        case 0xFF53:
+            return 106;  // right -> DPAD_RIGHT
+        case 0xFF54:
+            return 108;  // down -> DPAD_DOWN
+        case 0xFF52:
+            return 103;  // up -> DPAD_UP
+        // case 360:
+        //	return 232;// end -> DPAD_CENTER (ball click)
+        case 0xff50:
+            return KEY_HOME;  // home
+        case 0xFFC8:
+            rfbShutdownServer(cl->screen, TRUE);
+            return 0;  // F11 disconnect
+        case 0xffff:
+            return 158;  // del -> back
+        case 0xff55:
+            return 229;  // PgUp -> menu
+        case 0xffcf:
+            return 127;  // F2 -> search
+        case 0xffe3:
+            return 127;  // left ctrl -> search
+        case 0xff56:
+            return 61;  // PgUp -> call
+        case 0xff57:
+            return 107;  // End -> endcall
+        case 0xffc2:
+            return 211;  // F5 -> focus
+        case 0xffc3:
+            return 212;  // F6 -> camera
+        case 0xffc4:
+            return 150;  // F7 -> explorer
+        case 0xffc5:
+            return 155;  // F8 -> envelope
+
+        case 50081:
+        case 225:
+            (*alt) = 1;
+            if (real) return 48;  // a with acute
+            return 30;            // a with acute -> a with ring above
+
+        case 50049:
+        case 193:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 48;  // A with acute
+            return 30;            // A with acute -> a with ring above
+
+        case 50089:
+        case 233:
+            (*alt) = 1;
+            return 18;  // e with acute
+
+        case 50057:
+        case 201:
+            (*sh) = 1;
+            (*alt) = 1;
+            return 18;  // E with acute
+
+        case 50093:
+        case 0xffbf:
+            (*alt) = 1;
+            if (real) return 36;  // i with acute
+            return 23;            // i with acute -> i with grave
+
+        case 50061:
+        case 205:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 36;  // I with acute
+            return 23;            // I with acute -> i with grave
+
+        case 50099:
+        case 243:
+            (*alt) = 1;
+            if (real) return 16;  // o with acute
+            return 24;            // o with acute -> o with grave
+
+        case 50067:
+        case 211:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 16;  // O with acute
+            return 24;            // O with acute -> o with grave
+
+        case 50102:
+        case 246:
+            (*alt) = 1;
+            return 25;  // o with diaeresis
+
+        case 50070:
+        case 214:
+            (*sh) = 1;
+            (*alt) = 1;
+            return 25;  // O with diaeresis
+
+        case 50577:
+        case 245:
+            (*alt) = 1;
+            if (real) return 19;  // Hungarian o
+            return 25;            // Hungarian o -> o with diaeresis
+
+        case 50576:
+        case 213:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 19;  // Hungarian O
+            return 25;            // Hungarian O -> O with diaeresis
+
+        case 50106:
+        // case 0xffbe:
+        //	(*alt)=1;
+        // 	if (real)
+        //		return 17; //u with acute
+        // 	return 22; //u with acute -> u with grave
+        case 50074:
+        case 218:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 17;  // U with acute
+            return 22;            // U with acute -> u with grave
+        case 50108:
+        case 252:
+            (*alt) = 1;
+            return 47;  // u with diaeresis
+
+        case 50076:
+        case 220:
+            (*sh) = 1;
+            (*alt) = 1;
+            return 47;  // U with diaeresis
+
+        case 50609:
+        case 251:
+            (*alt) = 1;
+            if (real) return 45;  // Hungarian u
+            return 47;            // Hungarian u -> u with diaeresis
+
+        case 50608:
+        case 219:
+            (*sh) = 1;
+            (*alt) = 1;
+            if (real) return 45;  // Hungarian U
+            return 47;            // Hungarian U -> U with diaeresis
+    }
+    return 0;
+}
diff --git a/src/InputDevice.h b/src/InputDevice.h
new file mode 100644
index 0000000000000000000000000000000000000000..97055811cf7565592c56bc47baf09c7aa9640a1e
--- /dev/null
+++ b/src/InputDevice.h
@@ -0,0 +1,39 @@
+#ifndef INPUT_DEVICE_H
+#define INPUT_DEVICE_H
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+
+#include <rfb/rfb.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();
+
+    static void keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+    static void pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
+
+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);
+
+    static int keysym2scancode(rfbKeySym c, rfbClientPtr cl, int* sh, int* alt);
+
+    static int sFD;
+
+};
+
+};
+#endif
diff --git a/src/VNCFlinger.cpp b/src/VNCFlinger.cpp
index 0ca27841df20cbdc474029207bf8ceb78de68618..dfb1a38cec0d8f1be73a1f0f3085fd36d05a93ef 100644
--- a/src/VNCFlinger.cpp
+++ b/src/VNCFlinger.cpp
@@ -9,6 +9,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <gui/IGraphicBufferProducer.h>
 
+#include "InputDevice.h"
 #include "VNCFlinger.h"
 
 using namespace android;
@@ -74,6 +75,8 @@ status_t VNCFlinger::setup_l() {
     mVNCScreen->httpDir = NULL;
     mVNCScreen->port = VNC_PORT;
     mVNCScreen->newClientHook = (rfbNewClientHookPtr) VNCFlinger::onNewClient;
+    mVNCScreen->kbdAddEvent = InputDevice::keyEvent;
+    mVNCScreen->ptrAddEvent = InputDevice::pointerEvent;
     mVNCScreen->serverFormat.trueColour = true;
     mVNCScreen->serverFormat.bitsPerPixel = 32;
     mVNCScreen->handleEventsEagerly = true;
@@ -111,6 +114,7 @@ void VNCFlinger::onEvent(const Event& event) {
     switch(event.mId) {
         case EVENT_CLIENT_CONNECT:
             if (mClientCount == 0) {
+                InputDevice::start(mWidth, mHeight);
                 mVirtualDisplay->start(mMainDpyInfo, sQueue);
             }
             mClientCount++;
@@ -123,6 +127,7 @@ void VNCFlinger::onEvent(const Event& event) {
                 mClientCount--;
                 if (mClientCount == 0) {
                     mVirtualDisplay->stop();
+                    InputDevice::stop();
                 }
             }