diff options
| author | Eddie James <eajames@linux.ibm.com> | 2018-12-11 13:14:46 -0600 |
|---|---|---|
| committer | Eddie James <eajames@linux.ibm.com> | 2018-12-11 13:32:34 -0600 |
| commit | 21b177e050fe91525e34d20cb32fc1b605da5405 (patch) | |
| tree | 3ae2edd5fc2e58f607485aaafaaa5bbc2c86de7f | |
| parent | 9d7ff842406acdb76a1c7bed96781715729b8891 (diff) | |
| download | obmc-ikvm-21b177e050fe91525e34d20cb32fc1b605da5405.tar.gz obmc-ikvm-21b177e050fe91525e34d20cb32fc1b605da5405.zip | |
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>
| -rw-r--r-- | Makefile.am | 8 | ||||
| -rw-r--r-- | ikvm_input.cpp | 363 | ||||
| -rw-r--r-- | ikvm_input.hpp | 109 | ||||
| -rw-r--r-- | ikvm_server.cpp | 15 | ||||
| -rw-r--r-- | ikvm_server.hpp | 73 | ||||
| -rw-r--r-- | ikvm_video.cpp | 15 | ||||
| -rw-r--r-- | ikvm_video.hpp | 61 | ||||
| -rw-r--r-- | scancodes.h | 82 |
8 files changed, 725 insertions, 1 deletions
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 |

