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