| // Copyright 2020 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "screen-capture-utils/uinput.h" |
| |
| #include <cstdint> |
| #include <utility> |
| |
| #include <fcntl.h> |
| #include <linux/uinput.h> |
| #include <sys/ioctl.h> |
| #include <sys/time.h> |
| #include <unistd.h> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/scoped_file.h> |
| #include <base/logging.h> |
| |
| namespace screenshot { |
| namespace { |
| |
| // TODO(shaochuan): Possibly generate this list from the keymaps. |
| // clang-format off |
| constexpr const uint16_t kAllKeys[] = { |
| KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, |
| KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, |
| KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, |
| KEY_W, KEY_X, KEY_Y, KEY_Z, |
| KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, |
| KEY_F10, KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, |
| KEY_F18, |
| KEY_UP, KEY_DOT, KEY_END, KEY_ESC, KEY_TAB, KEY_DOWN, KEY_HOME, KEY_LEFT, |
| KEY_CLEAR, KEY_COMMA, KEY_ENTER, KEY_EQUAL, KEY_GRAVE, KEY_MINUS, KEY_PAUSE, |
| KEY_RIGHT, KEY_SLASH, KEY_SPACE, KEY_SYSRQ, KEY_PAGEUP, KEY_LEFTALT, |
| KEY_CAPSLOCK, KEY_LEFTCTRL, KEY_LEFTMETA, KEY_LINEFEED, KEY_PAGEDOWN, |
| KEY_RIGHTALT, KEY_BACKSLASH, KEY_BACKSPACE, KEY_LEFTBRACE, KEY_LEFTSHIFT, |
| KEY_RIGHTCTRL, KEY_RIGHTMETA, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_RIGHTBRACE, |
| KEY_RIGHTSHIFT, KEY_SCROLLLOCK, KEY_NUMLOCK, |
| KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, |
| KEY_KP8, KEY_KP9, KEY_KPASTERISK, KEY_KPCOMMA, KEY_KPDOT, KEY_KPENTER, |
| KEY_KPEQUAL, KEY_KPMINUS, KEY_KPPLUS, KEY_KPSLASH, |
| 0 |
| }; |
| // clang-format on |
| |
| // Manages a file descriptor representing an uinput device. |
| class ScopedUinputFD final { |
| public: |
| ScopedUinputFD() : fd_(open("/dev/uinput", O_WRONLY | O_NONBLOCK)) { |
| PCHECK(fd_.is_valid()); |
| |
| // Ensure protocol version. |
| unsigned int ver; |
| Ioctl(UI_GET_VERSION, &ver); |
| CHECK_GE(ver, 5) << "Unsupported protocol version, should be at least 5"; |
| } |
| ScopedUinputFD(const ScopedUinputFD&) = delete; |
| ScopedUinputFD& operator=(const ScopedUinputFD&) = delete; |
| |
| ~ScopedUinputFD() { |
| if (created_) { |
| Ioctl(UI_DEV_DESTROY); |
| } |
| } |
| |
| void CreateDevice() { |
| Ioctl(UI_DEV_CREATE); |
| created_ = true; |
| } |
| |
| // Emits an input event with the current device. |
| void Emit(uint16_t type, uint16_t code, int32_t value) const { |
| input_event ev{ |
| {}, // time (struct timeval), filled in later |
| type, |
| code, |
| value, |
| }; |
| gettimeofday(&ev.time, nullptr); |
| if (write(fd_.get(), &ev, sizeof(ev)) < 0) { |
| PLOG(ERROR) << "Failed emitting input event"; |
| } |
| } |
| |
| // Wrapped ioctl call to not expose |fd_|. |
| template <typename... Args> |
| void Ioctl(unsigned long req, Args... args) const { // NOLINT(runtime/int) |
| PCHECK(ioctl(fd_.get(), req, args...) == 0); |
| } |
| |
| private: |
| const base::ScopedFD fd_; |
| bool created_{false}; |
| }; |
| |
| // The actual implementation returned by Uinput::Create. |
| class UinputImpl final : public Uinput { |
| public: |
| UinputImpl(rfbScreenInfoPtr server, bool rotate); |
| ~UinputImpl() override; |
| |
| private: |
| void SetupKeyboard(); |
| void SetupPointer(int32_t width, int32_t height); |
| |
| void OnKbdAddEvent(rfbBool down, rfbKeySym keySym, rfbClientPtr cl) const; |
| void OnPtrAddEvent(int buttonMask, int x, int y, rfbClientPtr cl) const; |
| |
| ScopedUinputFD keyboard_; |
| ScopedUinputFD pointer_; |
| const bool rotate_; |
| const uint32_t vnc_width_; |
| }; |
| |
| // The current live instance, or nullptr if no instances exist. |
| UinputImpl* g_uinput{nullptr}; |
| |
| UinputImpl::UinputImpl(rfbScreenInfoPtr server, bool rotate) |
| : rotate_(rotate), vnc_width_(server->width) { |
| CHECK(!g_uinput) << "Only one Uinput instance may exist at a time"; |
| g_uinput = this; |
| |
| // Setup devices. |
| SetupKeyboard(); |
| uint32_t width = server->width; |
| uint32_t height = server->height; |
| if (rotate) { |
| std::swap(width, height); |
| } |
| SetupPointer(width, height); |
| |
| // Setup callbacks. |
| server->kbdAddEvent = [](rfbBool down, rfbKeySym keySym, rfbClientPtr cl) { |
| CHECK(g_uinput) << "uinput not set up"; |
| g_uinput->OnKbdAddEvent(down, keySym, cl); |
| }; |
| server->ptrAddEvent = [](int buttonMask, int x, int y, rfbClientPtr cl) { |
| CHECK(g_uinput) << "uinput not set up"; |
| g_uinput->OnPtrAddEvent(buttonMask, x, y, cl); |
| }; |
| } |
| |
| UinputImpl::~UinputImpl() { |
| g_uinput = nullptr; |
| } |
| |
| // Sets up a uinput keyboard device. We simulate a standard 104-key keyboard in |
| // US layout. |
| void UinputImpl::SetupKeyboard() { |
| // Enable key events. |
| keyboard_.Ioctl(UI_SET_EVBIT, EV_KEY); |
| for (auto* pkey = kAllKeys; *pkey; pkey++) { |
| keyboard_.Ioctl(UI_SET_KEYBIT, *pkey); |
| } |
| |
| const uinput_setup usetup{ |
| {BUS_USB /*bustype*/, 0, 0, 0}, // id (struct input_id) |
| "kmsvnc keyboard", // name |
| }; |
| keyboard_.Ioctl(UI_DEV_SETUP, &usetup); |
| |
| keyboard_.CreateDevice(); |
| } |
| |
| // Sets up a uinput pointer device. We simulate a touch device with absolutely |
| // positioned "tap" events. Only the left mouse button works. |
| void UinputImpl::SetupPointer(int32_t width, int32_t height) { |
| // Enable key events. |
| pointer_.Ioctl(UI_SET_EVBIT, EV_KEY); |
| pointer_.Ioctl(UI_SET_KEYBIT, BTN_LEFT); |
| |
| // Enable absolute events. |
| pointer_.Ioctl(UI_SET_EVBIT, EV_ABS); |
| pointer_.Ioctl(UI_SET_ABSBIT, ABS_X); |
| pointer_.Ioctl(UI_SET_ABSBIT, ABS_Y); |
| |
| // Set up X/Y bounds. |
| uinput_abs_setup uabs{}; |
| uabs.code = ABS_X; |
| uabs.absinfo.maximum = width - 1; |
| pointer_.Ioctl(UI_ABS_SETUP, &uabs); |
| uabs.code = ABS_Y; |
| uabs.absinfo.maximum = height - 1; |
| pointer_.Ioctl(UI_ABS_SETUP, &uabs); |
| |
| const uinput_setup usetup{ |
| {BUS_USB /*bustype*/, 0, 0, 0}, // id (struct input_id) |
| "kmsvnc touchscreen", // name |
| }; |
| pointer_.Ioctl(UI_DEV_SETUP, &usetup); |
| |
| pointer_.CreateDevice(); |
| } |
| |
| void UinputImpl::OnKbdAddEvent(rfbBool down, |
| rfbKeySym keySym, |
| rfbClientPtr cl) const { |
| const int scancode = KeySymToScancode(keySym); |
| if (!scancode) { |
| LOG(WARNING) << "Received unknown keysym 0x" << std::hex << keySym; |
| return; |
| } |
| |
| VLOG(1) << "keysym 0x" << std::hex << keySym |
| << " down:" << (down ? "true" : "false"); |
| keyboard_.Emit(EV_KEY, scancode, down); |
| keyboard_.Emit(EV_SYN, SYN_REPORT, 0); |
| } |
| |
| void UinputImpl::OnPtrAddEvent(int buttonMask, |
| int x, |
| int y, |
| rfbClientPtr cl) const { |
| if (rotate_) { |
| x = vnc_width_ - x - 1; |
| std::swap(x, y); |
| } |
| pointer_.Emit(EV_KEY, BTN_LEFT, buttonMask & 1); |
| pointer_.Emit(EV_ABS, ABS_X, x); |
| pointer_.Emit(EV_ABS, ABS_Y, y); |
| pointer_.Emit(EV_SYN, SYN_REPORT, 0); |
| |
| rfbDefaultPtrAddEvent(buttonMask, x, y, cl); |
| } |
| |
| } // namespace |
| |
| // Convert RFB keysyms (identical to X11 keysyms) to input event codes. |
| // ASCII chars are mapped back to their corresponding keys in US layout. |
| // Spec: https://tools.ietf.org/html/rfc6143#section-7.5.4 |
| int KeySymToScancode(rfbKeySym key) { |
| // We need custom formatting for the keymaps to make them readable. |
| // clang-format off |
| if ((0x20 <= key && key <= 0x7f) || (0xff00 <= key && key <= 0xff1f)) { |
| static const uint16_t map[] = { |
| /*0x00*/ 0, 0, 0, 0, 0, 0, 0, 0, |
| /*0x08*/ KEY_BACKSPACE, KEY_TAB, KEY_LINEFEED, KEY_CLEAR, 0, KEY_ENTER, 0, |
| 0, |
| /*0x10*/ 0, 0, 0, KEY_PAUSE, KEY_SCROLLLOCK, KEY_SYSRQ, 0, 0, |
| /*0x18*/ 0, 0, 0, KEY_ESC, 0, 0, 0, 0, |
| /*0x20*/ KEY_SPACE, KEY_1, KEY_APOSTROPHE, KEY_3, KEY_4, KEY_5, KEY_7, |
| KEY_APOSTROPHE, |
| /*0x28*/ KEY_9, KEY_0, KEY_8, KEY_EQUAL, KEY_COMMA, KEY_MINUS, KEY_DOT, |
| KEY_SLASH, |
| /*0x30*/ KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, |
| /*0x38*/ KEY_8, KEY_9, KEY_SEMICOLON, KEY_SEMICOLON, KEY_COMMA, KEY_EQUAL, |
| KEY_DOT, KEY_SLASH, |
| /*0x40*/ KEY_2, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, |
| /*0x48*/ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, |
| /*0x50*/ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, |
| /*0x58*/ KEY_X, KEY_Y, KEY_Z, KEY_LEFTBRACE, KEY_BACKSLASH, |
| KEY_RIGHTBRACE, KEY_6, KEY_MINUS, |
| /*0x60*/ KEY_GRAVE, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, |
| /*0x68*/ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, |
| /*0x70*/ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, |
| /*0x78*/ KEY_X, KEY_Y, KEY_Z, KEY_LEFTBRACE, KEY_BACKSLASH, |
| KEY_RIGHTBRACE, KEY_GRAVE, KEY_NUMLOCK, |
| }; |
| return map[key & 0x7f]; |
| } |
| if (0xff50 <= key && key <= 0xff5f) { |
| static const uint16_t map[] = { |
| /*0x0*/ KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_PAGEUP, |
| KEY_PAGEDOWN, KEY_END, |
| /*0x8*/ /*XK_Begin*/0, 0, 0, 0, 0, 0, 0, 0, |
| }; |
| return map[key & 0xf]; |
| } |
| if (0xff80 <= key && key <= 0xffbf) { |
| static const uint16_t map[] = { |
| /*0x80*/ 0, 0, 0, 0, 0, 0, 0, 0, |
| /*0x88*/ 0, 0, 0, 0, 0, KEY_KPENTER, 0, 0, |
| /*0x90*/ 0, 0, 0, 0, 0, KEY_KP7, KEY_KP4, KEY_KP8, |
| /*0x98*/ KEY_KP6, KEY_KP2, KEY_KP9, KEY_KP3, KEY_KP1, 0, KEY_KP0, |
| KEY_KPDOT, |
| /*0xa0*/ 0, 0, 0, 0, 0, 0, 0, 0, |
| /*0xa8*/ 0, 0, KEY_KPASTERISK, KEY_KPPLUS, KEY_KPCOMMA, KEY_KPMINUS, |
| KEY_KPDOT, KEY_KPSLASH, |
| /*0xb0*/ KEY_KP0, KEY_KP1, KEY_KP2, KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, |
| KEY_KP7, |
| /*0xb8*/ KEY_KP8, KEY_KP9, 0, 0, 0, KEY_KPEQUAL, KEY_F1, KEY_F2, |
| }; |
| return map[key & 0x3f]; |
| } |
| if (0xffc0 <= key && key <= 0xffcf) { |
| static const uint16_t map[] = { |
| /*0x0*/ KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, |
| /*0x8*/ KEY_F11, KEY_F12, KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, |
| KEY_F18, |
| }; |
| return map[key & 0xf]; |
| } |
| if (0xffe0 <= key && key <= 0xffef) { |
| static const uint16_t map[] = { |
| /*0x0*/ /*XK_F35*/0, KEY_LEFTSHIFT, KEY_RIGHTSHIFT, KEY_LEFTCTRL, |
| /*0x4*/ KEY_RIGHTCTRL, KEY_CAPSLOCK, /*XK_Shift_Lock*/0, KEY_LEFTMETA, |
| /*0x8*/ KEY_RIGHTMETA, KEY_LEFTALT, KEY_RIGHTALT, /*XK_Super_L*/0, |
| /*0xc*/ /*XK_Super_R*/0, /*XK_Hyper_L*/0, /*XK_Hyper_R*/0, 0, |
| }; |
| return map[key & 0xf]; |
| } |
| // clang-format on |
| return 0; |
| } |
| |
| // static |
| std::unique_ptr<Uinput> Uinput::Create(rfbScreenInfoPtr server, bool rotate) { |
| return std::make_unique<UinputImpl>(server, rotate); |
| } |
| |
| } // namespace screenshot |