Add input handling class

The Input class depends on the RFB server and V4L2 video classes, so
add outlines for those as well.

Change-Id: I2826f3da78dee10826e378dfc2c773b891da1f03
Signed-off-by: Eddie James <eajames@linux.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index d113997..e3bb48a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,10 +1,16 @@
 bin_PROGRAMS = obmc-ikvm
 
 noinst_HEADERS = \
-	ikvm_args.hpp
+	ikvm_args.hpp \
+	ikvm_input.hpp \
+	ikvm_server.hpp \
+	ikvm_video.hpp
 
 obmc_ikvm_SOURCES = \
 	ikvm_args.cpp \
+	ikvm_input.cpp \
+	ikvm_server.cpp \
+	ikvm_video.cpp \
 	obmc-ikvm.cpp
 
 obmc_ikvm_CXXFLAGS = \
diff --git a/ikvm_input.cpp b/ikvm_input.cpp
new file mode 100644
index 0000000..31a7be4
--- /dev/null
+++ b/ikvm_input.cpp
@@ -0,0 +1,363 @@
+#include "ikvm_input.hpp"
+
+#include "ikvm_server.hpp"
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <rfb/keysym.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+#include "scancodes.h"
+
+namespace ikvm
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::File::Error;
+
+const char Input::keyboardID = 1;
+const char Input::pointerID = 2;
+
+const char Input::shiftCtrlMap[NUM_MODIFIER_BITS] = {
+    0x02, // left shift
+    0x20, // right shift
+    0x01, // left control
+    0x10  // right control
+};
+
+const char Input::metaAltMap[NUM_MODIFIER_BITS] = {
+    0x08,       // left meta
+    (char)0x80, // right meta
+    0x04,       // left alt
+    0x40        // right alt
+};
+
+Input::Input(const std::string& p) :
+    keyboardReport{0}, pointerReport{0}, path(p)
+{
+    fd = open(path.c_str(), O_RDWR);
+    if (fd < 0)
+    {
+        log<level::ERR>("Failed to open input device",
+                        entry("PATH=%s", path.c_str()),
+                        entry("ERROR=%s", strerror(errno)));
+        elog<Open>(
+            xyz::openbmc_project::Common::File::Open::ERRNO(errno),
+            xyz::openbmc_project::Common::File::Open::PATH(path.c_str()));
+    }
+
+    // set the HID identifier byte because device is combined pointer/keyboard
+    keyboardReport[0] = keyboardID;
+    pointerReport[0] = pointerID;
+}
+
+Input::~Input()
+{
+    close(fd);
+}
+
+void Input::keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl)
+{
+    Server::ClientData* cd = (Server::ClientData*)cl->clientData;
+    Input* input = cd->input;
+
+    if (down)
+    {
+        char sc = keyToScancode(key);
+
+        if (sc)
+        {
+            if (input->keysDown.find(key) == input->keysDown.end())
+            {
+                for (unsigned int i = 3; i < REPORT_LENGTH; ++i)
+                {
+                    if (!input->keyboardReport[i])
+                    {
+                        input->keyboardReport[i] = sc;
+                        input->keysDown.insert(std::make_pair(key, i));
+                        input->sendKeyboard = true;
+                        break;
+                    }
+                }
+            }
+        }
+        else
+        {
+            char mod = keyToMod(key);
+
+            if (mod)
+            {
+                input->keyboardReport[1] |= mod;
+                input->sendKeyboard = true;
+            }
+        }
+    }
+    else
+    {
+        auto it = input->keysDown.find(key);
+
+        if (it != input->keysDown.end())
+        {
+            input->keyboardReport[it->second] = 0;
+            input->keysDown.erase(it);
+            input->sendKeyboard = true;
+        }
+        else
+        {
+            char mod = keyToMod(key);
+
+            if (mod)
+            {
+                input->keyboardReport[1] &= ~mod;
+                input->sendKeyboard = true;
+            }
+        }
+    }
+}
+
+void Input::pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl)
+{
+    Server::ClientData* cd = (Server::ClientData*)cl->clientData;
+    Input* input = cd->input;
+    Server* server = (Server*)cl->screen->screenData;
+    const Video& video = server->getVideo();
+
+    input->pointerReport[1] = buttonMask & 0xFF;
+
+    if (x >= 0 && (unsigned int)x < video.getWidth())
+    {
+        unsigned short xx = x * ((SHRT_MAX + 1) / video.getWidth());
+
+        memcpy(&input->pointerReport[2], &xx, 2);
+    }
+
+    if (y >= 0 && (unsigned int)y < video.getHeight())
+    {
+        unsigned short yy = y * ((SHRT_MAX + 1) / video.getHeight());
+
+        memcpy(&input->pointerReport[4], &yy, 2);
+    }
+
+    input->sendPointer = true;
+    rfbDefaultPtrAddEvent(buttonMask, x, y, cl);
+}
+
+void Input::sendRaw(char* data, int size)
+{
+    if (write(fd, data, size) != size)
+    {
+        log<level::ERR>("Failed to write report",
+                        entry("ERROR=%s", strerror(errno)));
+    }
+}
+
+void Input::sendReport()
+{
+    if (sendKeyboard)
+    {
+        if (write(fd, keyboardReport, REPORT_LENGTH) != REPORT_LENGTH)
+        {
+            log<level::ERR>("Failed to write keyboard report",
+                            entry("ERROR=%s", strerror(errno)));
+        }
+
+        sendKeyboard = false;
+    }
+
+    if (sendPointer)
+    {
+        if (write(fd, pointerReport, POINTER_LENGTH) != POINTER_LENGTH)
+        {
+            log<level::ERR>("Failed to write pointer report",
+                            entry("ERROR=%s", strerror(errno)));
+        }
+
+        sendPointer = false;
+    }
+}
+
+char Input::keyToMod(rfbKeySym key)
+{
+    char mod = 0;
+
+    if (key >= XK_Shift_L && key <= XK_Control_R)
+    {
+        mod = shiftCtrlMap[key - XK_Shift_L];
+    }
+    else if (key >= XK_Meta_L && key <= XK_Alt_R)
+    {
+        mod = metaAltMap[key - XK_Meta_L];
+    }
+
+    return mod;
+}
+
+char Input::keyToScancode(rfbKeySym key)
+{
+    char scancode = 0;
+
+    if ((key >= 'A' && key <= 'Z') || (key >= 'a' && key <= 'z'))
+    {
+        scancode = USBHID_KEY_A + ((key & 0x5F) - 'A');
+    }
+    else if (key >= '1' && key <= '9')
+    {
+        scancode = USBHID_KEY_1 + (key - '1');
+    }
+    else if (key >= XK_F1 && key <= XK_F12)
+    {
+        scancode = USBHID_KEY_F1 + (key - XK_F1);
+    }
+    else
+    {
+        switch (key)
+        {
+            case XK_exclam:
+                scancode = USBHID_KEY_1;
+                break;
+            case XK_at:
+                scancode = USBHID_KEY_2;
+                break;
+            case XK_numbersign:
+                scancode = USBHID_KEY_3;
+                break;
+            case XK_dollar:
+                scancode = USBHID_KEY_4;
+                break;
+            case XK_percent:
+                scancode = USBHID_KEY_5;
+                break;
+            case XK_asciicircum:
+                scancode = USBHID_KEY_6;
+                break;
+            case XK_ampersand:
+                scancode = USBHID_KEY_7;
+                break;
+            case XK_asterisk:
+                scancode = USBHID_KEY_8;
+                break;
+            case XK_parenleft:
+                scancode = USBHID_KEY_9;
+                break;
+            case XK_0:
+            case XK_parenright:
+                scancode = USBHID_KEY_0;
+                break;
+            case XK_Return:
+                scancode = USBHID_KEY_RETURN;
+                break;
+            case XK_Escape:
+                scancode = USBHID_KEY_ESC;
+                break;
+            case XK_BackSpace:
+                scancode = USBHID_KEY_BACKSPACE;
+                break;
+            case XK_Tab:
+                scancode = USBHID_KEY_TAB;
+                break;
+            case XK_space:
+                scancode = USBHID_KEY_SPACE;
+                break;
+            case XK_minus:
+            case XK_underscore:
+                scancode = USBHID_KEY_MINUS;
+                break;
+            case XK_plus:
+            case XK_equal:
+                scancode = USBHID_KEY_EQUAL;
+                break;
+            case XK_bracketleft:
+            case XK_braceleft:
+                scancode = USBHID_KEY_LEFTBRACE;
+                break;
+            case XK_bracketright:
+            case XK_braceright:
+                scancode = USBHID_KEY_RIGHTBRACE;
+                break;
+            case XK_backslash:
+            case XK_bar:
+                scancode = USBHID_KEY_BACKSLASH;
+                break;
+            case XK_colon:
+            case XK_semicolon:
+                scancode = USBHID_KEY_SEMICOLON;
+                break;
+            case XK_quotedbl:
+            case XK_apostrophe:
+                scancode = USBHID_KEY_APOSTROPHE;
+                break;
+            case XK_grave:
+            case XK_asciitilde:
+                scancode = USBHID_KEY_GRAVE;
+                break;
+            case XK_comma:
+            case XK_less:
+                scancode = USBHID_KEY_COMMA;
+                break;
+            case XK_period:
+            case XK_greater:
+                scancode = USBHID_KEY_DOT;
+                break;
+            case XK_slash:
+            case XK_question:
+                scancode = USBHID_KEY_SLASH;
+                break;
+            case XK_Caps_Lock:
+                scancode = USBHID_KEY_CAPSLOCK;
+                break;
+            case XK_Print:
+                scancode = USBHID_KEY_PRINT;
+                break;
+            case XK_Scroll_Lock:
+                scancode = USBHID_KEY_SCROLLLOCK;
+                break;
+            case XK_Pause:
+                scancode = USBHID_KEY_PAUSE;
+                break;
+            case XK_Insert:
+                scancode = USBHID_KEY_INSERT;
+                break;
+            case XK_Home:
+                scancode = USBHID_KEY_HOME;
+                break;
+            case XK_Page_Up:
+                scancode = USBHID_KEY_PAGEUP;
+                break;
+            case XK_Delete:
+                scancode = USBHID_KEY_DELETE;
+                break;
+            case XK_End:
+                scancode = USBHID_KEY_END;
+                break;
+            case XK_Page_Down:
+                scancode = USBHID_KEY_PAGEDOWN;
+                break;
+            case XK_Right:
+                scancode = USBHID_KEY_RIGHT;
+                break;
+            case XK_Left:
+                scancode = USBHID_KEY_LEFT;
+                break;
+            case XK_Down:
+                scancode = USBHID_KEY_DOWN;
+                break;
+            case XK_Up:
+                scancode = USBHID_KEY_UP;
+                break;
+            case XK_Num_Lock:
+                scancode = USBHID_KEY_NUMLOCK;
+                break;
+        }
+    }
+
+    return scancode;
+}
+
+} // namespace ikvm
diff --git a/ikvm_input.hpp b/ikvm_input.hpp
new file mode 100644
index 0000000..7c8120d
--- /dev/null
+++ b/ikvm_input.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <rfb/rfb.h>
+
+#include <map>
+#include <string>
+
+namespace ikvm
+{
+
+/*
+ * @class Input
+ * @brief Receives events from RFB clients and sends reports to the USB input
+ *        device
+ */
+class Input
+{
+  public:
+    /*
+     * @brief Constructs Input object
+     *
+     * @param[in] p - Path to the USB input device
+     */
+    Input(const std::string& p);
+    ~Input();
+    Input(const Input&) = default;
+    Input& operator=(const Input&) = default;
+    Input(Input&&) = default;
+    Input& operator=(Input&&) = default;
+
+    /*
+     * @brief RFB client key event handler
+     *
+     * @param[in] down - Boolean indicating whether key is pressed or not
+     * @param[in] key  - Key code
+     * @param[in] cl   - Handle to the RFB client
+     */
+    static void keyEvent(rfbBool down, rfbKeySym key, rfbClientPtr cl);
+    /*
+     * @brief RFB client pointer event handler
+     *
+     * @param[in] buttonMask - Bitmask indicating which buttons have been
+     *                         pressed
+     * @param[in] x          - Pointer x-coordinate
+     * @param[in] y          - Pointer y-coordinate
+     * @param[in] cl         - Handle to the RFB client
+     */
+    static void pointerEvent(int buttonMask, int x, int y, rfbClientPtr cl);
+
+    /*
+     * @brief Sends a data packet to the USB input device
+     *
+     * @param[in] data - pointer to data
+     * @param[in] size - number of bytes to send
+     */
+    void sendRaw(char* data, int size);
+    /* @brief Sends an HID report to the USB input device */
+    void sendReport();
+
+  private:
+    enum
+    {
+        NUM_MODIFIER_BITS = 4,
+        POINTER_LENGTH = 6,
+        REPORT_LENGTH = 8
+    };
+
+    /* @brief Keyboard HID identifier byte */
+    static const char keyboardID;
+    /* @brief Pointer HID identifier byte */
+    static const char pointerID;
+    /* @brief HID modifier bits mapped to shift and control key codes */
+    static const char shiftCtrlMap[NUM_MODIFIER_BITS];
+    /* @brief HID modifier bits mapped to meta and alt key codes */
+    static const char metaAltMap[NUM_MODIFIER_BITS];
+
+    /*
+     * @brief Translates a RFB-specific key code to HID modifier bit
+     *
+     * @param[in] key - key code
+     */
+    static char keyToMod(rfbKeySym key);
+    /*
+     * @brief Translates a RFB-specific key code to HID scancode
+     *
+     * @param[in] key - key code
+     */
+    static char keyToScancode(rfbKeySym key);
+
+    /* @brief Indicates whether or not to send a keyboard report */
+    bool sendKeyboard;
+    /* @brief Indicates whether or not to send a pointer report */
+    bool sendPointer;
+    /* @brief File descriptor for the USB input device */
+    int fd;
+    /* @brief Data for keyboard report */
+    char keyboardReport[REPORT_LENGTH];
+    /* @brief Data for pointer report */
+    char pointerReport[REPORT_LENGTH];
+    /* @brief Path to the USB input device */
+    std::string path;
+    /*
+     * @brief Mapping of RFB key code to report data index to keep track
+     *        of which keys are down
+     */
+    std::map<int, int> keysDown;
+};
+
+} // namespace ikvm
diff --git a/ikvm_server.cpp b/ikvm_server.cpp
new file mode 100644
index 0000000..679f47c
--- /dev/null
+++ b/ikvm_server.cpp
@@ -0,0 +1,15 @@
+#include "ikvm_server.hpp"
+
+namespace ikvm
+{
+
+Server::Server(const Args& args, Input& i, Video& v) :
+    input(i), video(v)
+{
+}
+
+Server::~Server()
+{
+}
+
+} // namespace ikvm
diff --git a/ikvm_server.hpp b/ikvm_server.hpp
new file mode 100644
index 0000000..67602b0
--- /dev/null
+++ b/ikvm_server.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "ikvm_args.hpp"
+#include "ikvm_input.hpp"
+#include "ikvm_video.hpp"
+
+namespace ikvm
+{
+
+/*
+ * @class Server
+ * @brief Manages the RFB server connection and updates
+ */
+class Server
+{
+  public:
+    /*
+     * @struct ClientData
+     * @brief Store necessary data for each connected RFB client
+     */
+    struct ClientData
+    {
+        /*
+         * @brief Constructs ClientData object
+         *
+         * @param[in] s - Number of frames to skip when client connects
+         * @param[in] i - Pointer to Input object
+         */
+        ClientData(int s, Input* i) : skipFrame(s), input(i)
+        {
+        }
+        ~ClientData() = default;
+        ClientData(const ClientData&) = default;
+        ClientData& operator=(const ClientData&) = default;
+        ClientData(ClientData&&) = default;
+        ClientData& operator=(ClientData&&) = default;
+
+        int skipFrame;
+        Input* input;
+    };
+
+    /*
+     * @brief Constructs Server object
+     *
+     * @param[in] args - Reference to Args object
+     * @param[in] i    - Reference to Input object
+     * @param[in] v    - Reference to Video object
+     */
+    Server(const Args& args, Input& i, Video& v);
+    ~Server();
+    Server(const Server&) = default;
+    Server& operator=(const Server&) = default;
+    Server(Server&&) = default;
+    Server& operator=(Server&&) = default;
+
+    /*
+     * @brief Get the Video object
+     *
+     * @return Reference to the Video object
+     */
+    inline const Video& getVideo() const
+    {
+        return video;
+    }
+
+  private:
+    /* @brief Reference to the Input object */
+    Input& input;
+    /* @brief Reference to the Video object */
+    Video& video;
+};
+
+} // namespace ikvm
diff --git a/ikvm_video.cpp b/ikvm_video.cpp
new file mode 100644
index 0000000..46505a8
--- /dev/null
+++ b/ikvm_video.cpp
@@ -0,0 +1,15 @@
+#include "ikvm_video.hpp"
+
+namespace ikvm
+{
+
+Video::Video(const std::string& p, Input& input, int fr) :
+    height(600), width(800), input(input), path(p)
+{
+}
+
+Video::~Video()
+{
+}
+
+} // namespace ikvm
diff --git a/ikvm_video.hpp b/ikvm_video.hpp
new file mode 100644
index 0000000..1ff6c61
--- /dev/null
+++ b/ikvm_video.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "ikvm_input.hpp"
+
+#include <string>
+
+namespace ikvm
+{
+
+/*
+ * @class Video
+ * @brief Sets up the V4L2 video device and performs read operations
+ */
+class Video
+{
+  public:
+    /*
+     * @brief Constructs Video object
+     *
+     * @param[in] p     - Path to the V4L2 video device
+     * @param[in] input - Reference to the Input object
+     * @param[in] fr    - desired frame rate of the video
+     */
+    Video(const std::string& p, Input& input, int fr = 30);
+    ~Video();
+    Video(const Video&) = default;
+    Video& operator=(const Video&) = default;
+    Video(Video&&) = default;
+    Video& operator=(Video&&) = default;
+
+    /*
+     * @brief Gets the height of the video frame
+     *
+     * @return Value of the height of video frame in pixels
+     */
+    inline size_t getHeight() const
+    {
+        return height;
+    }
+    /*
+     * @brief Gets the width of the video frame
+     *
+     * @return Value of the width of video frame in pixels
+     */
+    inline size_t getWidth() const
+    {
+        return width;
+    }
+
+  private:
+    /* @brief Height in pixels of the video frame */
+    size_t height;
+    /* @brief Width in pixels of the video frame */
+    size_t width;
+    /* @brief Reference to the Input object */
+    Input& input;
+    /* @brief Path to the V4L2 video device */
+    const std::string path;
+};
+
+} // namespace ikvm
diff --git a/scancodes.h b/scancodes.h
new file mode 100644
index 0000000..db79231
--- /dev/null
+++ b/scancodes.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#define USBHID_KEY_A 0x04
+#define USBHID_KEY_B 0x05
+#define USBHID_KEY_C 0x06
+#define USBHID_KEY_D 0x07
+#define USBHID_KEY_E 0x08
+#define USBHID_KEY_F 0x09
+#define USBHID_KEY_G 0x0a
+#define USBHID_KEY_H 0x0b
+#define USBHID_KEY_I 0x0c
+#define USBHID_KEY_J 0x0d
+#define USBHID_KEY_K 0x0e
+#define USBHID_KEY_L 0x0f
+#define USBHID_KEY_M 0x10
+#define USBHID_KEY_N 0x11
+#define USBHID_KEY_O 0x12
+#define USBHID_KEY_P 0x13
+#define USBHID_KEY_Q 0x14
+#define USBHID_KEY_R 0x15
+#define USBHID_KEY_S 0x16
+#define USBHID_KEY_T 0x17
+#define USBHID_KEY_U 0x18
+#define USBHID_KEY_V 0x19
+#define USBHID_KEY_W 0x1a
+#define USBHID_KEY_X 0x1b
+#define USBHID_KEY_Y 0x1c
+#define USBHID_KEY_Z 0x1d
+#define USBHID_KEY_1 0x1e
+#define USBHID_KEY_2 0x1f
+#define USBHID_KEY_3 0x20
+#define USBHID_KEY_4 0x21
+#define USBHID_KEY_5 0x22
+#define USBHID_KEY_6 0x23
+#define USBHID_KEY_7 0x24
+#define USBHID_KEY_8 0x25
+#define USBHID_KEY_9 0x26
+#define USBHID_KEY_0 0x27
+#define USBHID_KEY_RETURN 0x28
+#define USBHID_KEY_ESC 0x29
+#define USBHID_KEY_BACKSPACE 0x2a
+#define USBHID_KEY_TAB 0x2b
+#define USBHID_KEY_SPACE 0x2c
+#define USBHID_KEY_MINUS 0x2d
+#define USBHID_KEY_EQUAL 0x2e
+#define USBHID_KEY_LEFTBRACE 0x2f
+#define USBHID_KEY_RIGHTBRACE 0x30
+#define USBHID_KEY_BACKSLASH 0x31
+#define USBHID_KEY_HASH 0x32
+#define USBHID_KEY_SEMICOLON 0x33
+#define USBHID_KEY_APOSTROPHE 0x34
+#define USBHID_KEY_GRAVE 0x35
+#define USBHID_KEY_COMMA 0x36
+#define USBHID_KEY_DOT 0x37
+#define USBHID_KEY_SLASH 0x38
+#define USBHID_KEY_CAPSLOCK 0x39
+#define USBHID_KEY_F1 0x3a
+#define USBHID_KEY_F2 0x3b
+#define USBHID_KEY_F3 0x3c
+#define USBHID_KEY_F4 0x3d
+#define USBHID_KEY_F5 0x3e
+#define USBHID_KEY_F6 0x3f
+#define USBHID_KEY_F7 0x40
+#define USBHID_KEY_F8 0x41
+#define USBHID_KEY_F9 0x42
+#define USBHID_KEY_F10 0x43
+#define USBHID_KEY_F11 0x44
+#define USBHID_KEY_F12 0x45
+#define USBHID_KEY_PRINT 0x46
+#define USBHID_KEY_SCROLLLOCK 0x47
+#define USBHID_KEY_PAUSE 0x48
+#define USBHID_KEY_INSERT 0x49
+#define USBHID_KEY_HOME 0x4a
+#define USBHID_KEY_PAGEUP 0x4b
+#define USBHID_KEY_DELETE 0x4c
+#define USBHID_KEY_END 0x4d
+#define USBHID_KEY_PAGEDOWN 0x4e
+#define USBHID_KEY_RIGHT 0x4f
+#define USBHID_KEY_LEFT 0x50
+#define USBHID_KEY_DOWN 0x51
+#define USBHID_KEY_UP 0x52
+#define USBHID_KEY_NUMLOCK 0x53