ref:b5ae5cd6151c7e22fcc8f72fda83bcd830a963ad

feat(linux): send keyboard, mouse and touch input to the VM with capture = qemu

With capture = qemu, platf::input() also creates a qemu::input_t, and the keyboard, mouse, scroll, text, touch and pen functions in virtualhid_input.cpp dispatch to it with a short #ifdef branch. Gamepads stay on libvirtualhid. - Keys: Windows virtual-key codes go through the VK -> KEY_* table Sunshine uses on Linux (ported from libvirtualhid, extended with Pause, Menu, media, browser and IME keys) and QEMU's keycodemapdb KEY_* -> qnum table, vendored as unmodified keymap-gen output in third-party/qemu-keycodemapdb with a provenance README. Unmapped keys are logged once; a key held by several virtual keys is released with the last one. - Mouse: follows Mouse.IsAbsolute. Absolute positions are scaled to the console size; relative input for an absolute guest moves a tracked pointer; absolute input for a relative guest becomes RelMotion from QEMU's visible MouseSet position, with sub-pixel remainders, or is dropped without one. 120 scroll units are one wheel step, the rest is kept; horizontal scrolling and text input log once. - Touch: contacts get MultiTouch slots up to MaxSlots; without the interface the first contact drives the mouse. Pens drive the mouse. get_capabilities() advertises pen/touch from native_pen_touch. - The input follows the captured session and console (set_capture_console), connects on its own thread, and sends through session_t::open_input(): a FIFO drained by a GLib thread that only makes asynchronous calls, so a slow QEMU never blocks Sunshine's input thread and events keep their order. - streaming_will_stop() releases every key, button and touch the QEMU inputs still hold. - The fake QEMU exports Keyboard, Mouse and MultiTouch, records calls, refuses them like QEMU, and can switch IsAbsolute, resize or stall. Refs #5 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SHA: b5ae5cd6151c7e22fcc8f72fda83bcd830a963ad
Author: Cole Christensen <cole.christensen@gmail.com>
Date: 2026-09-13 00:41
Parents: 97389de
21 files changed +3768 -10
Type
cmake/compile_definitions/linux.cmake +7 −1
@@ -329,12 +329,18 @@
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/capture.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/frame_store.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/frame_store.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/input.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/input.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/keymap.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/keymap.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/pixel_format.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/pixel_format.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/render_node.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/render_node.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/session.h"
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/session.cpp")
"${CMAKE_SOURCE_DIR}/src/platform/linux/qemu/session.cpp"
"${CMAKE_SOURCE_DIR}/third-party/qemu-keycodemapdb/linux_to_qnum.cpp"
"${CMAKE_SOURCE_DIR}/third-party/qemu-keycodemapdb/qnum_to_linux.cpp")
endif()
if(NOT ${CUDA_FOUND}
docs/configuration.md +8 −0
@@ -2233,6 +2233,14 @@
The guest's sound is streamed too when QEMU plays it to D-Bus: start QEMU with
`-audiodev dbus,id=snd0`, a sound device such as `-device intel-hda -device hda-output,audiodev=snd0`,
and `-display dbus,audiodev=snd0`. Sunshine's audio sink settings don't apply to a VM.
Keyboard, mouse and touch input go to the streamed console through QEMU's D-Bus input interfaces
(gamepads still use virtual host devices). An absolute pointer such as `-device virtio-tablet-pci`
works best; with a relative mouse (PS/2) Moonlight's absolute pointer positions only work while the
guest shows a hardware cursor. Horizontal scrolling and text input aren't supported.
Native touch (see [native_pen_touch](#native_pen_touch)) needs `-device virtio-multitouch-pci` in the VM;
QEMU then sends mouse buttons to the touch device and drops wheel events, so add it only for touch-first
guests. QEMU can't tell Sunshine whether that device exists: for a VM without it, disable
native_pen_touch so Moonlight sends touches as mouse input. Pens always move the mouse.
@note{Applies to Linux only.}</td>
</tr>
<tr>
src/platform/linux/input/virtualhid.cpp +14 −1
@@ -15,17 +15,30 @@
// local includes
#include "src/platform/virtualhid_input.h"
#ifdef SUNSHINE_BUILD_QEMU
#include "src/config.h"
#endif
using namespace std::literals;
namespace platf {
platform_caps::caps_t get_capabilities() {
platform_caps::caps_t caps = 0;
#ifdef SUNSHINE_BUILD_QEMU
// with capture = qemu, touch and pen go to QEMU's MultiTouch or mouse, not to host devices
const bool qemu = config::video.capture == "qemu";
if (qemu && config::input.native_pen_touch) {
caps |= platform_caps::pen_touch;
}
#else
const bool qemu = false;
#endif
const auto runtime = virtualhid::create_runtime();
if (!runtime) {
return caps;
}
if (const auto &capabilities = runtime->capabilities(); !qemu && config::input.native_pen_touch && (capabilities.supports_touchscreen || capabilities.supports_pen_tablet)) {
if (const auto &capabilities = runtime->capabilities(); config::input.native_pen_touch && (capabilities.supports_touchscreen || capabilities.supports_pen_tablet)) {
caps |= platform_caps::pen_touch;
}
if (virtualhid::configured_gamepad_supports_controller_extensions()) {
src/platform/linux/misc.cpp +8 −1
@@ -64,6 +64,10 @@
#include "src/platform/common.h"
#include "vaapi.h"
#ifdef SUNSHINE_BUILD_QEMU
#include "qemu/input.h"
#endif
#ifdef __GNUC__
#define SUNSHINE_GNUC_EXTENSION __extension__
#else
@@ -545,7 +549,10 @@
* @brief Restore Linux platform state after streaming stops.
*/
void streaming_will_stop() {
#ifdef SUNSHINE_BUILD_QEMU
// Nothing to do
// the last client left: nothing it pressed may stay held in the VM
qemu::release_all_inputs();
#endif
}
/**
src/platform/linux/qemu/capture.cpp +3 −0
@@ -15,6 +15,7 @@
// local includes
#include "frame_store.h"
#include "input.h"
#include "render_node.h"
#include "session.h"
#include "src/config.h"
@@ -92,6 +93,8 @@
if (!conn.registration) {
return std::nullopt;
}
// keyboard, mouse and touch go to the console being streamed
set_capture_console(conn.session, console->id, conn.store);
// QEMU sends the current surface right after registration
if (conn.store->wait_for_frame(2s)) {
src/platform/linux/qemu/frame_store.cpp +9 −0
@@ -293,6 +293,7 @@
cursor_state.x = x;
cursor_state.y = y;
cursor_state.visible = visible;
mouse_set_count += 1;
if (redraw && frame_width > 0) {
touch_locked();
}
@@ -332,6 +333,14 @@
copy.pixels = cursor_state.pixels;
}
return copy;
}
std::optional<pointer_position_t> frame_store_t::pointer() const {
std::lock_guard lock {mutex};
if (mouse_set_count == 0) {
return std::nullopt;
}
return pointer_position_t {cursor_state.x, cursor_state.y, mouse_set_count, cursor_state.visible};
}
void frame_store_t::disconnected() {
src/platform/linux/qemu/frame_store.h +18 −0
@@ -73,6 +73,16 @@
};
/**
* @brief Guest pointer position reported by `MouseSet`.
*/
struct pointer_position_t {
std::int32_t x {0}; ///< Pointer X position in frame pixels.
std::int32_t y {0}; ///< Pointer Y position in frame pixels.
std::uint64_t sequence {0}; ///< Number of `MouseSet` calls so far; changes with every report.
bool visible {false}; ///< Whether QEMU reported the cursor as shown.
};
/**
* @brief Alpha-blend a cursor onto a B, G, R, X frame.
*
* @param cursor Cursor to draw; nothing happens when it isn't drawable.
@@ -207,6 +217,13 @@
*/
[[nodiscard]] cursor_state_t cursor(std::uint64_t known_serial) const;
/**
* @brief Latest guest pointer position, for converting absolute input for a relative mouse.
*
* @return Position and report count, or nothing before the first `MouseSet`.
*/
[[nodiscard]] std::optional<pointer_position_t> pointer() const;
private:
/**
* @brief Resize the frame, logging size or transport changes.
@@ -269,5 +286,6 @@
bool is_disconnected {false}; ///< Whether QEMU closed the listener connection.
int logged_transport {-1}; ///< Transport of the last logged scanout: messages, map or DMABUF.
cursor_state_t cursor_state; ///< Latest guest cursor.
std::uint64_t mouse_set_count {0}; ///< Number of `MouseSet` calls received.
scanout_kind_e scanout_kind {scanout_kind_e::none}; ///< How the latest scanout arrived.
bool is_disabled {false}; ///< Whether QEMU turned the display off since the last scanout.
src/platform/linux/qemu/input.cpp +654 −0
@@ -1,0 +1,654 @@
/**
* @file src/platform/linux/qemu/input.cpp
* @brief Definitions for sending Moonlight keyboard, mouse and touch input to a QEMU VM.
*/
// class header include
#include "input.h"
// standard includes
#include <algorithm>
#include <atomic>
#include <cmath>
#include <ios>
#include <limits>
// local includes
#include "keymap.h"
#include "src/config.h"
#include "src/logging.h"
using namespace std::literals;
namespace qemu {
namespace {
constexpr int wheel_delta = 120; ///< High-resolution scroll units per wheel step.
constexpr auto reconnect_delay = 1s; ///< Pause after a failed connection attempt.
constexpr auto pointer_settle_time = 100ms; ///< Motion pause after which `MouseSet` reports are trusted again.
constexpr auto drop_log_interval = 5s; ///< Minimum time between "not connected" logs.
constexpr std::uint32_t pen_contact = std::numeric_limits<std::uint32_t>::max(); ///< Contact id used for the pen.
/**
* @brief The session and console the capture streams.
*/
struct capture_binding_t {
std::weak_ptr<session_t> session; ///< Capture session.
std::uint32_t console_id {0}; ///< Captured console.
std::weak_ptr<frame_store_t> store; ///< Frame store of the capture, for `MouseSet`.
};
std::mutex binding_mutex; ///< Guards `binding`.
capture_binding_t binding; ///< Latest capture binding.
std::atomic<std::uint64_t> binding_generation {0}; ///< Incremented when the captured session or console changes.
std::mutex registry_mutex; ///< Guards `inputs`.
std::set<input_t *> inputs; ///< Live inputs, for `release_all_inputs()`.
/**
* @brief Copy the capture binding.
*
* @return Binding and its generation.
*/
std::pair<capture_binding_t, std::uint64_t> capture_binding() {
std::lock_guard lock {binding_mutex};
return {binding, binding_generation.load()};
}
/**
* @brief Find the capture's frame store for a console.
*
* @param session Session the console belongs to.
* @param console_id Console id.
* @return The store, or nullptr when no capture runs on that console.
*/
std::shared_ptr<frame_store_t> capture_store(const std::weak_ptr<session_t> &session, std::uint32_t console_id) {
std::lock_guard lock {binding_mutex};
const bool same_session = !binding.session.owner_before(session) && !session.owner_before(binding.session);
if (!same_session || binding.console_id != console_id) {
return nullptr;
}
return binding.store.lock();
}
/**
* @brief Translate a Moonlight mouse button.
*
* @param button Moonlight button number.
* @return QEMU button, or nothing for unknown buttons.
*/
std::optional<mouse_button_e> qemu_button(int button) {
switch (button) {
case BUTTON_LEFT:
return mouse_button_e::left;
case BUTTON_MIDDLE:
return mouse_button_e::middle;
case BUTTON_RIGHT:
return mouse_button_e::right;
case BUTTON_X1:
return mouse_button_e::side;
case BUTTON_X2:
return mouse_button_e::extra;
default:
return std::nullopt;
}
}
} // namespace
void set_capture_console(const std::shared_ptr<session_t> &session, std::uint32_t console_id, const std::shared_ptr<frame_store_t> &store) {
std::lock_guard lock {binding_mutex};
const auto previous = binding.session.lock();
if (previous != session || binding.console_id != console_id) {
binding_generation += 1;
}
// a capture re-created after a mode change keeps the console: only the store changes
binding = {session, console_id, store};
}
void release_all_inputs() {
std::lock_guard lock {registry_mutex};
for (auto input : inputs) {
input->release_all();
}
}
std::size_t input_count() {
std::lock_guard lock {registry_mutex};
return inputs.size();
}
input_t::input_t(std::string address):
address {std::move(address)} {
worker = std::thread([this]() {
run();
});
std::lock_guard lock {registry_mutex};
inputs.insert(this);
}
input_t::~input_t() {
{
std::lock_guard lock {registry_mutex};
inputs.erase(this);
}
{
std::lock_guard lock {mutex};
stopping = true;
}
wake.notify_all();
worker.join();
}
bool input_t::wait_for_console(std::chrono::milliseconds timeout) {
std::unique_lock lock {mutex};
const auto ready = [&]() {
if (target && target->device->alive() && target->binding == binding_generation) {
return true;
}
if (!connecting && !connect_requested) {
connect_requested = true;
next_attempt = {};
wake.notify_all();
}
return false;
};
return ready() || wake.wait_for(lock, timeout, ready);
}
std::optional<bool> input_t::guest_absolute() {
std::lock_guard lock {mutex};
if (!target || !target->device->alive()) {
return std::nullopt;
}
return target->device->is_absolute();
}
std::optional<std::pair<std::uint32_t, std::uint32_t>> input_t::console_size() {
std::lock_guard lock {mutex};
if (!target || !target->device->alive()) {
return std::nullopt;
}
return std::pair {target->device->width(), target->device->height()};
}
bool input_t::flush(std::chrono::milliseconds timeout) {
std::shared_ptr<console_input_t> device;
{
std::lock_guard lock {mutex};
if (target) {
device = target->device;
}
}
return !device || device->flush(timeout);
}
console_input_t *input_t::device_locked() {
if (target && target->device->alive()) {
if (target->binding != binding_generation && !connecting && !connect_requested) {
// the capture moved to another console or session; keep sending here until connected there
connect_requested = true;
wake.notify_all();
}
return target->device.get();
}
if (!connecting && !connect_requested) {
connect_requested = true;
wake.notify_all();
}
const auto now = std::chrono::steady_clock::now();
if (now - last_drop_log >= drop_log_interval) {
last_drop_log = now;
BOOST_LOG(debug) << "qemu: not connected to a VM console yet; dropping input"sv;
}
return nullptr;
}
std::optional<input_t::target_t> input_t::connect() {
auto [capture, generation] = capture_binding();
auto session = capture.session.lock();
auto console_id = capture.console_id;
if (!session || !session->alive()) {
// no stream yet: use the console output_name selects, like the capture will
session = shared_session(address);
if (!session) {
return std::nullopt;
}
const auto vm = session->vm();
auto console = find_console(vm, config::video.output_name);
if (!console) {
console = find_console(vm, "");
}
if (!console) {
BOOST_LOG(error) << "qemu: VM ["sv << vm.name << "] has no graphical console for input"sv;
return std::nullopt;
}
console_id = console->id;
}
auto device = session->open_input(console_id);
if (!device) {
return std::nullopt;
}
return target_t {std::move(device), session, generation};
}
void input_t::run() {
std::unique_lock lock {mutex};
while (true) {
wake.wait(lock, [&]() {
return stopping || connect_requested;
});
if (stopping) {
break;
}
if (std::chrono::steady_clock::now() < next_attempt) {
wake.wait_until(lock, next_attempt, [&]() {
return stopping || std::chrono::steady_clock::now() >= next_attempt;
});
continue;
}
connect_requested = false;
connecting = true;
lock.unlock();
auto connected = connect();
lock.lock();
connecting = false;
if (!connected) {
next_attempt = std::chrono::steady_clock::now() + reconnect_delay;
wake.notify_all();
continue;
}
std::optional<target_t> previous = std::move(target);
if (previous && previous->device->alive()) {
const bool same_console = previous->session.lock() == connected->session.lock() && previous->device->console_id() == connected->device->console_id();
if (!same_console) {
release_all_locked(*previous->device);
pointer_x.reset();
pointer_y.reset();
pointer_sequence = 0;
}
} else {
// a new VM: nothing we pressed exists there
forget_state_locked();
pointer_x.reset();
pointer_y.reset();
pointer_sequence = 0;
}
target = std::move(connected);
wake.notify_all();
// the previous console input waits for its pending replies when destroyed
lock.unlock();
previous.reset();
lock.lock();
}
auto last = std::move(target);
lock.unlock();
last.reset();
}
void input_t::forget_state_locked() {
pressed_keys.clear();
pressed_buttons.clear();
touch_contacts.clear();
mouse_contact.reset();
scroll_remainder = 0;
motion_remainder_x = 0;
motion_remainder_y = 0;
}
void input_t::release_all_locked(console_input_t &device) {
for (const auto &[qnum, holders] : pressed_keys) {
device.key(qnum, false);
}
for (auto button : pressed_buttons) {
device.button(button, false);
}
for (const auto &[id, contact] : touch_contacts) {
device.touch(touch_kind_e::cancel, contact.slot, contact.x, contact.y);
}
forget_state_locked();
}
void input_t::release_all() {
std::lock_guard lock {mutex};
if (target && target->device->alive()) {
release_all_locked(*target->device);
} else {
forget_state_locked();
}
}
void input_t::sync_guest_pointer_locked(const console_input_t &device) {
const auto store = capture_store(target->session, device.console_id());
if (!store) {
return;
}
// a hidden cursor's position means nothing (QEMU reports 0,0 for virtio-gpu before the guest shows one)
const auto pointer = store->pointer();
if (!pointer || !pointer->visible || pointer->sequence == pointer_sequence) {
return;
}
if (pointer_x && std::chrono::steady_clock::now() - last_relative_motion < pointer_settle_time) {
return;
}
BOOST_LOG(verbose) << "qemu: guest pointer at "sv << pointer->x << ',' << pointer->y << " from MouseSet"sv;
pointer_sequence = pointer->sequence;
pointer_x = pointer->x;
pointer_y = pointer->y;
motion_remainder_x = 0;
motion_remainder_y = 0;
}
bool input_t::move_to_locked(console_input_t &device, double x, double y) {
const auto width = device.width();
const auto height = device.height();
if (width == 0 || height == 0) {
return false;
}
x = std::clamp(x, 0.0, width - 1.0);
y = std::clamp(y, 0.0, height - 1.0);
if (device.is_absolute()) {
device.set_abs_position((std::uint32_t) x, (std::uint32_t) y);
pointer_x = x;
pointer_y = y;
motion_remainder_x = 0;
motion_remainder_y = 0;
return true;
}
sync_guest_pointer_locked(device);
if (!pointer_x || !pointer_y) {
if (!logged_no_pointer) {
logged_no_pointer = true;
BOOST_LOG(debug) << "qemu: the guest mouse is relative and QEMU hasn't reported the pointer position (MouseSet) yet; dropping absolute moves"sv;
}
return false;
}
// carry fractions of a pixel over, so small steps on a scaled stream still add up
motion_remainder_x += x - *pointer_x;
motion_remainder_y += y - *pointer_y;
const auto dx = (std::int32_t) std::trunc(motion_remainder_x);
const auto dy = (std::int32_t) std::trunc(motion_remainder_y);
motion_remainder_x -= dx;
motion_remainder_y -= dy;
pointer_x = x;
pointer_y = y;
if (dx != 0 || dy != 0) {
device.rel_motion(dx, dy);
last_relative_motion = std::chrono::steady_clock::now();
}
return true;
}
void input_t::button_locked(console_input_t &device, mouse_button_e button, bool pressed) {
if (pressed) {
pressed_buttons.insert(button);
device.button(button, true);
} else if (pressed_buttons.erase(button) > 0) {
device.button(button, false);
}
}
void input_t::move_mouse(int delta_x, int delta_y) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device || (delta_x == 0 && delta_y == 0)) {
return;
}
sync_guest_pointer_locked(*device);
if (device->is_absolute()) {
if (!pointer_x || !pointer_y) {
// nothing known about the pointer yet: start from the middle of the screen
pointer_x = device->width() / 2.0;
pointer_y = device->height() / 2.0;
}
move_to_locked(*device, *pointer_x + delta_x, *pointer_y + delta_y);
return;
}
device->rel_motion(delta_x, delta_y);
last_relative_motion = std::chrono::steady_clock::now();
if (pointer_x && pointer_y && device->width() > 0 && device->height() > 0) {
pointer_x = std::clamp(*pointer_x + delta_x, 0.0, device->width() - 1.0);
pointer_y = std::clamp(*pointer_y + delta_y, 0.0, device->height() - 1.0);
}
}
void input_t::abs_mouse(const platf::touch_port_t &touch_port, float x, float y) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device || touch_port.width <= 0 || touch_port.height <= 0) {
return;
}
// Moonlight's coordinates cover the captured display; scale them to the console's current size
const double console_x = (x - touch_port.offset_x) * device->width() / touch_port.width;
const double console_y = (y - touch_port.offset_y) * device->height() / touch_port.height;
move_to_locked(*device, console_x, console_y);
}
void input_t::button_mouse(int button, bool release) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device) {
return;
}
const auto converted = qemu_button(button);
if (!converted) {
BOOST_LOG(debug) << "qemu: ignoring unknown mouse button "sv << button;
return;
}
button_locked(*device, *converted, !release);
}
void input_t::scroll(int high_res_distance) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device) {
return;
}
scroll_remainder += high_res_distance;
while (scroll_remainder >= wheel_delta) {
scroll_remainder -= wheel_delta;
device->button(mouse_button_e::wheel_up, true);
device->button(mouse_button_e::wheel_up, false);
}
while (scroll_remainder <= -wheel_delta) {
scroll_remainder += wheel_delta;
device->button(mouse_button_e::wheel_down, true);
device->button(mouse_button_e::wheel_down, false);
}
}
void input_t::hscroll(int high_res_distance) {
std::lock_guard lock {mutex};
if (!logged_hscroll && high_res_distance != 0) {
logged_hscroll = true;
BOOST_LOG(info) << "qemu: horizontal scrolling isn't supported: QEMU's D-Bus mouse has no horizontal wheel buttons"sv;
}
}
void input_t::keyboard_update(std::uint16_t modcode, bool release, std::uint8_t flags) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device) {
return;
}
// Moonlight has no extended-key bit, and SS_KBE_FLAG_NON_NORMALIZED doesn't change the physical key
const std::uint16_t vk = modcode & 0xFF;
const auto qnum = keymap::vk_to_qnum(vk);
if (!qnum) {
if (!logged_keys.test(vk)) {
logged_keys.set(vk);
BOOST_LOG(info) << "qemu: no QEMU key for virtual-key code 0x"sv << std::hex << vk << std::dec << "; ignoring it"sv;
}
return;
}
if (!release) {
// several virtual keys can hold one key (VK_SHIFT and VK_LSHIFT); a repeated press is key repeat
pressed_keys[*qnum].insert(vk);
device->key(*qnum, true);
return;
}
const auto held = pressed_keys.find(*qnum);
if (held == pressed_keys.end()) {
return;
}
held->second.erase(vk);
if (held->second.empty()) {
pressed_keys.erase(held);
device->key(*qnum, false);
}
}
void input_t::unicode(std::string_view text) {
std::lock_guard lock {mutex};
if (!logged_unicode && !text.empty()) {
logged_unicode = true;
BOOST_LOG(info) << "qemu: text input isn't supported: QEMU's D-Bus keyboard only takes key numbers"sv;
}
}
void input_t::emulate_mouse_locked(console_input_t &device, std::uint32_t pointer_id, std::uint8_t event_type, double x, double y) {
const bool is_active = mouse_contact == pointer_id;
switch (event_type) {
case LI_TOUCH_EVENT_DOWN:
if (mouse_contact && !is_active) {
return;
}
move_to_locked(device, x, y);
if (!mouse_contact) {
mouse_contact = pointer_id;
button_locked(device, mouse_button_e::left, true);
}
return;
case LI_TOUCH_EVENT_MOVE:
if (is_active) {
move_to_locked(device, x, y);
}
return;
case LI_TOUCH_EVENT_HOVER:
if (!mouse_contact) {
move_to_locked(device, x, y);
}
return;
case LI_TOUCH_EVENT_UP:
if (is_active) {
move_to_locked(device, x, y);
button_locked(device, mouse_button_e::left, false);
mouse_contact.reset();
}
return;
case LI_TOUCH_EVENT_CANCEL:
case LI_TOUCH_EVENT_HOVER_LEAVE:
if (is_active) {
button_locked(device, mouse_button_e::left, false);
mouse_contact.reset();
}
return;
case LI_TOUCH_EVENT_CANCEL_ALL:
if (mouse_contact) {
button_locked(device, mouse_button_e::left, false);
mouse_contact.reset();
}
return;
default:
return;
}
}
void input_t::touch_update(const platf::touch_port_t &touch_port, const platf::touch_input_t &touch) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device) {
return;
}
// Sunshine normalizes touch coordinates to the captured display
const double x = std::clamp(touch.x, 0.0f, 1.0f) * (double) device->width();
const double y = std::clamp(touch.y, 0.0f, 1.0f) * (double) device->height();
if (!device->has_multitouch() || device->max_slots() <= 0) {
emulate_mouse_locked(*device, touch.pointerId, touch.eventType, x, y);
return;
}
if (!logged_touch) {
logged_touch = true;
BOOST_LOG(info) << "qemu: sending touch to QEMU's MultiTouch interface; the VM needs -device virtio-multitouch-pci, otherwise set native_pen_touch = disabled"sv;
}
const auto contact = touch_contacts.find(touch.pointerId);
switch (touch.eventType) {
case LI_TOUCH_EVENT_DOWN:
case LI_TOUCH_EVENT_MOVE:
{
if (contact != touch_contacts.end()) {
contact->second.x = x;
contact->second.y = y;
device->touch(touch_kind_e::update, contact->second.slot, x, y);
return;
}
std::uint64_t slot = 0;
const auto slot_taken = [&](std::uint64_t candidate) {
return std::ranges::any_of(touch_contacts, [&](const auto &entry) {
return entry.second.slot == candidate;
});
};
while (slot < (std::uint64_t) device->max_slots() && slot_taken(slot)) {
slot += 1;
}
if (slot >= (std::uint64_t) device->max_slots()) {
BOOST_LOG(debug) << "qemu: all "sv << device->max_slots() << " touch slots are in use; dropping contact "sv << touch.pointerId;
return;
}
touch_contacts[touch.pointerId] = {slot, x, y};
device->touch(touch_kind_e::begin, slot, x, y);
return;
}
case LI_TOUCH_EVENT_UP:
if (contact != touch_contacts.end()) {
device->touch(touch_kind_e::end, contact->second.slot, x, y);
touch_contacts.erase(contact);
}
return;
case LI_TOUCH_EVENT_CANCEL:
if (contact != touch_contacts.end()) {
// only the pointer id of a cancel is valid; use the last position
device->touch(touch_kind_e::cancel, contact->second.slot, contact->second.x, contact->second.y);
touch_contacts.erase(contact);
}
return;
case LI_TOUCH_EVENT_CANCEL_ALL:
for (const auto &[id, active] : touch_contacts) {
device->touch(touch_kind_e::cancel, active.slot, active.x, active.y);
}
touch_contacts.clear();
return;
default:
// hovering and button-only events have no contact
return;
}
}
void input_t::pen_update(const platf::touch_port_t &touch_port, const platf::pen_input_t &pen) {
std::lock_guard lock {mutex};
const auto device = device_locked();
if (!device) {
return;
}
const double x = std::clamp(pen.x, 0.0f, 1.0f) * (double) device->width();
const double y = std::clamp(pen.y, 0.0f, 1.0f) * (double) device->height();
emulate_mouse_locked(*device, pen_contact, pen.eventType, x, y);
}
} // namespace qemu
src/platform/linux/qemu/input.h +322 −0
@@ -1,0 +1,322 @@
/**
* @file src/platform/linux/qemu/input.h
* @brief Declarations for sending Moonlight keyboard, mouse and touch input to a QEMU VM (`capture = qemu`).
* @details `input_t` receives the events Sunshine's platform input functions get and turns them
* into calls on QEMU's `org.qemu.Display1.Keyboard`, `Mouse` and `MultiTouch` interfaces of the
* console that is being captured. Gamepads are not handled here; they stay on libvirtualhid.
*/
#pragma once
// standard includes
#include <bitset>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
// local includes
#include "frame_store.h"
#include "session.h"
#include "src/platform/common.h"
namespace qemu {
/**
* @brief Record which session and console the capture streams, so input follows it.
* @details Called by the capture backend whenever it registers on a console. The input sends to
* the same console and uses the store's `MouseSet` position to convert absolute input for a
* relative guest mouse. Only weak references are kept.
*
* @param session Session the capture uses.
* @param console_id Captured console.
* @param store Frame store registered on the console.
*/
void set_capture_console(const std::shared_ptr<session_t> &session, std::uint32_t console_id, const std::shared_ptr<frame_store_t> &store);
/**
* @brief Release every key, button and touch that the live QEMU inputs hold.
* @details Called when streaming stops, so nothing stays pressed in the guest after the last
* client disconnected.
*/
void release_all_inputs();
/**
* @brief Count the live QEMU inputs.
*
* @return Number of `input_t` objects.
*/
std::size_t input_count();
/**
* @brief Moonlight keyboard, mouse and touch input for one QEMU VM.
* @details Nothing here blocks the caller on QEMU: connecting happens on a background thread
* (events that arrive before it finished are dropped), and every D-Bus call is asynchronous and
* ordered. All methods are thread-safe.
*
* - Keys: Windows virtual-key codes are translated to QEMU key numbers (`keymap.h`); keys with
* no QEMU number are logged once and ignored.
* - Mouse: follows `Mouse.IsAbsolute`. Absolute input for an absolute guest is scaled to console
* pixels; relative input for a relative guest is passed on. Relative input for an absolute
* guest moves a tracked pointer position. Absolute input for a relative guest becomes relative
* motion from the guest pointer position QEMU reported with `MouseSet`, and is dropped until
* there is one.
* - Scroll: every 120 units are one wheel step; the remainder is kept. QEMU has no horizontal
* wheel button, so horizontal scrolling is ignored.
* - Touch: contacts get `MultiTouch` slots up to `MaxSlots`; without `MultiTouch` the first
* contact drives the mouse (position and left button). Pens drive the mouse the same way.
* QEMU exports `MultiTouch` whether or not the VM has a multi-touch device, so touches reach
* the guest only with `-device virtio-multitouch-pci`.
*/
class input_t {
public:
/**
* @brief Create the input and start connecting to QEMU in the background.
*
* @param address D-Bus address of QEMU's bus, or empty for the session bus.
*/
explicit input_t(std::string address);
~input_t();
input_t(const input_t &) = delete;
input_t &operator=(const input_t &) = delete;
/**
* @brief Wait until the input is connected to a console, connecting if needed.
*
* @param timeout Maximum time to wait.
* @return True when events are delivered.
*/
bool wait_for_console(std::chrono::milliseconds timeout);
/**
* @brief Mouse mode of the connected console.
*
* @return `Mouse.IsAbsolute`, or nothing while not connected.
*/
[[nodiscard]] std::optional<bool> guest_absolute();
/**
* @brief Size of the connected console.
*
* @return Width and height in pixels, or nothing while not connected.
*/
[[nodiscard]] std::optional<std::pair<std::uint32_t, std::uint32_t>> console_size();
/**
* @brief Wait until QEMU answered every input call sent so far.
*
* @param timeout Maximum time to wait.
* @return True when nothing is pending (also when not connected).
*/
bool flush(std::chrono::milliseconds timeout);
/**
* @brief Move the mouse by a relative amount.
*
* @param delta_x Horizontal motion in client pixels.
* @param delta_y Vertical motion in client pixels.
*/
void move_mouse(int delta_x, int delta_y);
/**
* @brief Move the mouse to an absolute position.
*
* @param touch_port Area the coordinates refer to (the captured display).
* @param x X position within the touch port.
* @param y Y position within the touch port.
*/
void abs_mouse(const platf::touch_port_t &touch_port, float x, float y);
/**
* @brief Press or release a mouse button.
*
* @param button Moonlight button: 1 left, 2 middle, 3 right, 4 side (X1), 5 extra (X2).
* @param release True for a release.
*/
void button_mouse(int button, bool release);
/**
* @brief Scroll vertically.
*
* @param high_res_distance Distance in 1/120 wheel steps; positive scrolls up.
*/
void scroll(int high_res_distance);
/**
* @brief Scroll horizontally; QEMU's D-Bus mouse has no horizontal wheel, so this only logs once.
*
* @param high_res_distance Distance in 1/120 wheel steps.
*/
void hscroll(int high_res_distance);
/**
* @brief Press or release a key.
*
* @param modcode Windows virtual-key code in the low byte.
* @param release True for a release.
* @param flags Moonlight keyboard flags (`SS_KBE_FLAG_*`); none changes the key on this path.
*/
void keyboard_update(std::uint16_t modcode, bool release, std::uint8_t flags);
/**
* @brief Type text; QEMU's D-Bus keyboard only takes key numbers, so this only logs once.
*
* @param text UTF-8 text.
*/
void unicode(std::string_view text);
/**
* @brief Handle a touch event.
*
* @param touch_port Area the coordinates refer to.
* @param touch Touch event with coordinates normalized to 0..1.
*/
void touch_update(const platf::touch_port_t &touch_port, const platf::touch_input_t &touch);
/**
* @brief Handle a pen event by moving the mouse and pressing the left button while touching.
*
* @param touch_port Area the coordinates refer to.
* @param pen Pen event with coordinates normalized to 0..1.
*/
void pen_update(const platf::touch_port_t &touch_port, const platf::pen_input_t &pen);
/**
* @brief Release every key, button and touch contact this input holds in the guest.
*/
void release_all();
private:
/**
* @brief A console connection with the capture binding it was made for.
*/
struct target_t {
std::shared_ptr<console_input_t> device; ///< Console input.
std::weak_ptr<session_t> session; ///< Session of the device.
std::uint64_t binding {0}; ///< Capture binding generation the target was chosen from.
};
/**
* @brief A touch contact holding a `MultiTouch` slot.
*/
struct touch_contact_t {
std::uint64_t slot {0}; ///< Slot number.
double x {0}; ///< Last X position in console pixels.
double y {0}; ///< Last Y position in console pixels.
};
/**
* @brief Get the console to send to, asking the worker to (re)connect when needed.
* @details Caller holds `mutex`.
*
* @return Console input, or nullptr while not connected (the event is dropped).
*/
console_input_t *device_locked();
/**
* @brief Connect to the captured console, or the configured one; runs on the worker thread.
*
* @return New target, or nothing on failure.
*/
std::optional<target_t> connect();
/**
* @brief Worker thread: connects whenever asked to.
*/
void run();
/**
* @brief Forget all pressed state without sending releases (the guest is gone or changed).
* @details Caller holds `mutex`.
*/
void forget_state_locked();
/**
* @brief Release everything held on a device.
* @details Caller holds `mutex`.
*
* @param device Device to send the releases to.
*/
void release_all_locked(console_input_t &device);
/**
* @brief Take the guest pointer position from QEMU's latest `MouseSet`, when it is newer.
* @details Only reports of a visible cursor count. Reports that arrive while our own relative
* motion is still on its way describe an older position, so they are only taken after a pause
* in motion. Caller holds `mutex`.
*
* @param device Target device.
*/
void sync_guest_pointer_locked(const console_input_t &device);
/**
* @brief Move the guest pointer to a position in console pixels, converting for a relative mouse.
* @details Caller holds `mutex`.
*
* @param device Target device.
* @param x X position in console pixels.
* @param y Y position in console pixels.
* @return False when a relative guest's pointer position is unknown and the move was dropped.
*/
bool move_to_locked(console_input_t &device, double x, double y);
/**
* @brief Press or release a button, tracking it for `release_all()`.
* @details Caller holds `mutex`.
*
* @param device Target device.
* @param button Button.
* @param pressed True for a press.
*/
void button_locked(console_input_t &device, mouse_button_e button, bool pressed);
/**
* @brief Drive the mouse from a single touch or pen contact.
* @details Caller holds `mutex`.
*
* @param device Target device.
* @param pointer_id Contact id; only the first active contact moves the mouse.
* @param event_type Moonlight touch event type.
* @param x X position in console pixels.
* @param y Y position in console pixels.
*/
void emulate_mouse_locked(console_input_t &device, std::uint32_t pointer_id, std::uint8_t event_type, double x, double y);
std::string address; ///< D-Bus address of QEMU's bus.
std::mutex mutex; ///< Guards everything below.
std::condition_variable wake; ///< Wakes the worker or `wait_for_console()`.
std::thread worker; ///< Connects in the background.
bool stopping {false}; ///< Whether the worker should exit.
bool connect_requested {true}; ///< Whether the worker should (re)connect.
bool connecting {false}; ///< Whether the worker is connecting right now.
std::chrono::steady_clock::time_point next_attempt; ///< Earliest time for the next connection attempt.
std::optional<target_t> target; ///< Current console connection.
std::chrono::steady_clock::time_point last_drop_log; ///< When a dropped event was last logged.
std::map<std::uint32_t, std::set<std::uint16_t>> pressed_keys; ///< Pressed QEMU key numbers and the virtual keys holding each.
std::set<mouse_button_e> pressed_buttons; ///< Pressed mouse buttons.
int scroll_remainder {0}; ///< Scroll distance not yet sent, in 1/120 steps.
std::optional<double> pointer_x; ///< Where the guest pointer is believed to be, in console pixels.
std::optional<double> pointer_y; ///< Where the guest pointer is believed to be, in console pixels.
double motion_remainder_x {0}; ///< Relative motion below one pixel not yet sent.
double motion_remainder_y {0}; ///< Relative motion below one pixel not yet sent.
std::uint64_t pointer_sequence {0}; ///< `MouseSet` report last taken as the pointer position.
std::chrono::steady_clock::time_point last_relative_motion; ///< When relative motion was last sent.
std::map<std::uint32_t, touch_contact_t> touch_contacts; ///< Active touch contacts by Moonlight pointer id.
std::optional<std::uint32_t> mouse_contact; ///< Contact that drives the mouse when there is no multi-touch.
std::bitset<256> logged_keys; ///< Virtual keys already logged as unmapped.
bool logged_hscroll {false}; ///< Whether horizontal scrolling was logged as unsupported.
bool logged_unicode {false}; ///< Whether text input was logged as unsupported.
bool logged_no_pointer {false}; ///< Whether a dropped absolute move for a relative guest was logged.
bool logged_touch {false}; ///< Whether the multi-touch device hint was logged.
};
} // namespace qemu
src/platform/linux/qemu/keymap.cpp +227 −0
@@ -1,0 +1,227 @@
/**
* @file src/platform/linux/qemu/keymap.cpp
* @brief Definitions for translating Moonlight key codes to QEMU key numbers.
*/
// class header include
#include "keymap.h"
// standard includes
#include <array>
#include <utility>
#include <vector>
// platform includes
#include <linux/input-event-codes.h>
// Generated by keycodemapdb's keymap-gen, see third-party/qemu-keycodemapdb/README.md.
extern const std::vector<unsigned short> qemu_keycodemapdb_linux_to_qnum; ///< Linux key code -> QEMU key number, 0 for none.
extern const std::vector<unsigned short> qemu_keycodemapdb_qnum_to_linux; ///< QEMU key number -> Linux key code, 0 for none.
namespace qemu::keymap {
namespace {
/**
* @brief Windows virtual-key codes and the Linux keys they stand for.
* @details The first block is the table Sunshine's Linux input backend uses: libvirtualhid's
* `key_code_to_linux` (`third-party/libvirtualhid/src/platform/linux/uhid_backend.cpp` at
* `6fdb8bd4`, MIT), including its ranges for digits, letters, keypad digits and F1-F24. The
* second block adds the keys that table has no entry for, taken from keycodemapdb's `Win32`
* column (`third-party/qemu-keycodemapdb`), so a guest also gets Pause, Menu, media, browser
* and IME keys.
*/
constexpr auto vk_pairs = std::to_array<std::pair<std::uint8_t, int>>({
// libvirtualhid key_code_to_linux: special keys
{0x08, KEY_BACKSPACE},
{0x09, KEY_TAB},
{0x0D, KEY_ENTER},
{0x10, KEY_LEFTSHIFT},
{0xA0, KEY_LEFTSHIFT},
{0x11, KEY_LEFTCTRL},
{0xA2, KEY_LEFTCTRL},
{0x12, KEY_LEFTALT},
{0xA4, KEY_LEFTALT},
{0x14, KEY_CAPSLOCK},
{0x1B, KEY_ESC},
{0x20, KEY_SPACE},
{0x21, KEY_PAGEUP},
{0x22, KEY_PAGEDOWN},
{0x23, KEY_END},
{0x24, KEY_HOME},
{0x25, KEY_LEFT},
{0x26, KEY_UP},
{0x27, KEY_RIGHT},
{0x28, KEY_DOWN},
{0x2C, KEY_SYSRQ},
{0x2D, KEY_INSERT},
{0x2E, KEY_DELETE},
{0x5B, KEY_LEFTMETA},
{0x5C, KEY_RIGHTMETA},
{0x6A, KEY_KPASTERISK},
{0x6B, KEY_KPPLUS},
{0x6D, KEY_KPMINUS},
{0x6E, KEY_KPDOT},
{0x6F, KEY_KPSLASH},
{0x90, KEY_NUMLOCK},
{0x91, KEY_SCROLLLOCK},
{0xA1, KEY_RIGHTSHIFT},
{0xA3, KEY_RIGHTCTRL},
{0xA5, KEY_RIGHTALT},
{0xBA, KEY_SEMICOLON},
{0xBB, KEY_EQUAL},
{0xBC, KEY_COMMA},
{0xBD, KEY_MINUS},
{0xBE, KEY_DOT},
{0xBF, KEY_SLASH},
{0xC0, KEY_GRAVE},
{0xDB, KEY_LEFTBRACE},
{0xDC, KEY_BACKSLASH},
{0xDD, KEY_RIGHTBRACE},
{0xDE, KEY_APOSTROPHE},
{0xE2, KEY_102ND},
// libvirtualhid key_code_to_linux: digits 0x30-0x39
{0x30, KEY_0},
{0x31, KEY_1},
{0x32, KEY_2},
{0x33, KEY_3},
{0x34, KEY_4},
{0x35, KEY_5},
{0x36, KEY_6},
{0x37, KEY_7},
{0x38, KEY_8},
{0x39, KEY_9},
// libvirtualhid key_code_to_linux: letters 0x41-0x5A
{0x41, KEY_A},
{0x42, KEY_B},
{0x43, KEY_C},
{0x44, KEY_D},
{0x45, KEY_E},
{0x46, KEY_F},
{0x47, KEY_G},
{0x48, KEY_H},
{0x49, KEY_I},
{0x4A, KEY_J},
{0x4B, KEY_K},
{0x4C, KEY_L},
{0x4D, KEY_M},
{0x4E, KEY_N},
{0x4F, KEY_O},
{0x50, KEY_P},
{0x51, KEY_Q},
{0x52, KEY_R},
{0x53, KEY_S},
{0x54, KEY_T},
{0x55, KEY_U},
{0x56, KEY_V},
{0x57, KEY_W},
{0x58, KEY_X},
{0x59, KEY_Y},
{0x5A, KEY_Z},
// libvirtualhid key_code_to_linux: keypad digits 0x60-0x69
{0x60, KEY_KP0},
{0x61, KEY_KP1},
{0x62, KEY_KP2},
{0x63, KEY_KP3},
{0x64, KEY_KP4},
{0x65, KEY_KP5},
{0x66, KEY_KP6},
{0x67, KEY_KP7},
{0x68, KEY_KP8},
{0x69, KEY_KP9},
// libvirtualhid key_code_to_linux: F1-F24 0x70-0x87
{0x70, KEY_F1},
{0x71, KEY_F2},
{0x72, KEY_F3},
{0x73, KEY_F4},
{0x74, KEY_F5},
{0x75, KEY_F6},
{0x76, KEY_F7},
{0x77, KEY_F8},
{0x78, KEY_F9},
{0x79, KEY_F10},
{0x7A, KEY_F11},
{0x7B, KEY_F12},
{0x7C, KEY_F13},
{0x7D, KEY_F14},
{0x7E, KEY_F15},
{0x7F, KEY_F16},
{0x80, KEY_F17},
{0x81, KEY_F18},
{0x82, KEY_F19},
{0x83, KEY_F20},
{0x84, KEY_F21},
{0x85, KEY_F22},
{0x86, KEY_F23},
{0x87, KEY_F24},
// keycodemapdb Win32 column: keys libvirtualhid's table doesn't map
{0x13, KEY_PAUSE}, // VK_PAUSE
{0x15, KEY_HANGEUL}, // VK_HANGUL (also VK_KANA)
{0x19, KEY_HANJA}, // VK_HANJA
{0x1C, KEY_HENKAN}, // VK_CONVERT
{0x1D, KEY_MUHENKAN}, // VK_NONCONVERT
{0x29, KEY_SELECT}, // VK_SELECT
{0x2A, KEY_PRINT}, // VK_PRINT
{0x2F, KEY_HELP}, // VK_HELP
{0x5D, KEY_COMPOSE}, // VK_APPS, the Menu key
{0x5F, KEY_SLEEP}, // VK_SLEEP
{0x6C, KEY_KPCOMMA}, // VK_SEPARATOR
{0xA6, KEY_BACK}, // VK_BROWSER_BACK
{0xA7, KEY_FORWARD}, // VK_BROWSER_FORWARD
{0xA8, KEY_REFRESH}, // VK_BROWSER_REFRESH
{0xA9, KEY_STOP}, // VK_BROWSER_STOP
{0xAA, KEY_SEARCH}, // VK_BROWSER_SEARCH
{0xAB, KEY_FAVORITES}, // VK_BROWSER_FAVORITES
{0xAC, KEY_HOMEPAGE}, // VK_BROWSER_HOME
{0xAD, KEY_MUTE}, // VK_VOLUME_MUTE
{0xAE, KEY_VOLUMEDOWN}, // VK_VOLUME_DOWN
{0xAF, KEY_VOLUMEUP}, // VK_VOLUME_UP
{0xB0, KEY_NEXTSONG}, // VK_MEDIA_NEXT_TRACK
{0xB1, KEY_PREVIOUSSONG}, // VK_MEDIA_PREV_TRACK
{0xB2, KEY_STOPCD}, // VK_MEDIA_STOP
{0xB3, KEY_PLAYPAUSE}, // VK_MEDIA_PLAY_PAUSE
{0xB4, KEY_EMAIL}, // VK_LAUNCH_MAIL
{0xF2, KEY_KATAKANAHIRAGANA}, // VK_OEM_COPY
{0xFA, KEY_PLAY}, // VK_PLAY
{0xFB, KEY_ZOOM}, // VK_ZOOM
});
/**
* @brief Build the virtual-key lookup table.
*
* @return Linux key code for each virtual-key code, 0 (`KEY_RESERVED`) for none.
*/
constexpr std::array<std::uint16_t, 256> make_vk_table() {
std::array<std::uint16_t, 256> table {};
for (const auto &[vk, key] : vk_pairs) {
table[vk] = (std::uint16_t) key;
}
return table;
}
constexpr auto vk_table = make_vk_table(); ///< Linux key code by virtual-key code.
} // namespace
std::optional<int> vk_to_linux(std::uint16_t vk) {
if (vk >= vk_table.size() || vk_table[vk] == KEY_RESERVED) {
return std::nullopt;
}
return vk_table[vk];
}
std::optional<std::uint32_t> linux_to_qnum(int key) {
if (key <= KEY_RESERVED || (std::size_t) key >= qemu_keycodemapdb_linux_to_qnum.size() || qemu_keycodemapdb_linux_to_qnum[key] == 0) {
return std::nullopt;
}
return qemu_keycodemapdb_linux_to_qnum[key];
}
std::optional<int> qnum_to_linux(std::uint32_t qnum) {
if (qnum == 0 || qnum >= qemu_keycodemapdb_qnum_to_linux.size() || qemu_keycodemapdb_qnum_to_linux[qnum] == KEY_RESERVED) {
return std::nullopt;
}
return qemu_keycodemapdb_qnum_to_linux[qnum];
}
std::optional<std::uint32_t> vk_to_qnum(std::uint16_t vk) {
const auto key = vk_to_linux(vk);
return key ? linux_to_qnum(*key) : std::nullopt;
}
} // namespace qemu::keymap
src/platform/linux/qemu/keymap.h +51 −0
@@ -1,0 +1,51 @@
/**
* @file src/platform/linux/qemu/keymap.h
* @brief Declarations for translating Moonlight key codes to QEMU key numbers.
* @details Moonlight sends Windows virtual-key codes. QEMU's `org.qemu.Display1.Keyboard` takes a
* QEMU key number ("qnum": the XT scan code, with the `0xe0` prefix folded into the high bit).
* The translation goes through Linux input key codes: virtual-key -> `KEY_*` with the table
* Sunshine uses on Linux hosts, then `KEY_*` -> qnum with QEMU's keycodemapdb data
* (`third-party/qemu-keycodemapdb`).
*/
#pragma once
// standard includes
#include <cstdint>
#include <optional>
namespace qemu::keymap {
/**
* @brief Translate a Windows virtual-key code to a Linux input key code.
* @details Side-less modifiers map to the left key (`VK_SHIFT` -> `KEY_LEFTSHIFT`), like
* Sunshine's Linux input backend. Moonlight doesn't send the extended-key bit, so keys that only
* differ by it share a code: `VK_RETURN` is `KEY_ENTER` for the main and the keypad Enter.
*
* @param vk Virtual-key code (the low byte of Moonlight's key code).
* @return `KEY_*` code, or nothing for codes that aren't keys or have no Linux key.
*/
std::optional<int> vk_to_linux(std::uint16_t vk);
/**
* @brief Translate a Linux input key code to a QEMU key number.
*
* @param key `KEY_*` code.
* @return QEMU key number, or nothing when QEMU has no number for the key.
*/
std::optional<std::uint32_t> linux_to_qnum(int key);
/**
* @brief Translate a QEMU key number back to a Linux input key code, as QEMU decodes it.
*
* @param qnum QEMU key number.
* @return `KEY_*` code, or nothing for numbers QEMU doesn't decode.
*/
std::optional<int> qnum_to_linux(std::uint32_t qnum);
/**
* @brief Translate a Windows virtual-key code to a QEMU key number.
*
* @param vk Virtual-key code.
* @return QEMU key number, or nothing when either step has no mapping.
*/
std::optional<std::uint32_t> vk_to_qnum(std::uint16_t vk);
} // namespace qemu::keymap
src/platform/linux/qemu/session.cpp +362 −2
@@ -162,11 +162,11 @@
done.get();
}
private:
/**
* @brief Queue a function on the loop without waiting.
* @details Functions posted from one thread run in the order they were posted.
*
* @param fn Function to run; must outlive its execution.
* @param fn Function to run.
*/
void post(std::function<void()> fn) {
auto source = g_idle_source_new();
@@ -186,6 +186,7 @@
g_source_unref(source);
}
private:
GMainContext *context; ///< Context owned by the loop thread.
GMainLoop *loop; ///< Loop running on the thread.
std::thread thread; ///< Thread running the loop.
@@ -370,6 +371,125 @@
};
/**
* @brief Calls queued on a console's input interfaces that QEMU hasn't answered yet.
* @details Shared with the reply callbacks, which may run after the console input is gone.
*/
struct pending_calls_t {
std::mutex mutex; ///< Guards `count`.
std::condition_variable idle; ///< Signaled when `count` drops to zero.
int count {0}; ///< Queued or sent calls without a reply.
std::atomic<int> errors {0}; ///< Calls QEMU answered with an error.
};
/**
* @brief Keyboard, mouse and multi-touch proxies of one console, driven from the session's input thread.
*/
class console_input_impl_t: public console_input_t {
public:
/**
* @brief Create unconnected console input.
*
* @param session Session that owns the input thread.
* @param console_id Console to send input to.
* @param timeout_ms Timeout of each call.
*/
console_input_impl_t(std::shared_ptr<session_impl_t> session, std::uint32_t console_id, int timeout_ms);
~console_input_impl_t() override;
console_input_impl_t(const console_input_impl_t &) = delete;
console_input_impl_t &operator=(const console_input_impl_t &) = delete;
/**
* @brief Create the proxies and read their properties.
* @details Must run on the session's input thread, so property changes are delivered there.
*
* @param connection Bus connection.
* @param cancellable Cancels the property reads when the deadline passes.
* @return True when the console exists.
*/
bool start(GDBusConnection *connection, GCancellable *cancellable);
std::uint32_t console_id() const override {
return id;
}
bool alive() const override;
bool is_absolute() const override {
return absolute;
}
bool has_multitouch() const override {
return multitouch != nullptr;
}
int max_slots() const override {
return slots;
}
std::uint32_t width() const override {
return console_width;
}
std::uint32_t height() const override {
return console_height;
}
void key(std::uint32_t qnum, bool pressed) override;
void button(mouse_button_e button, bool pressed) override;
void set_abs_position(std::uint32_t x, std::uint32_t y) override;
void rel_motion(std::int32_t dx, std::int32_t dy) override;
void touch(touch_kind_e kind, std::uint64_t slot, double x, double y) override;
bool flush(std::chrono::milliseconds timeout) override;
private:
/**
* @brief Queue a call to be made on the input thread, after every call queued before it.
*
* @param call Function that starts one asynchronous call, passing `reply_data()` to the reply callback.
*/
void enqueue(std::function<void()> call);
/**
* @brief Run the queued calls in order; runs on the input thread.
*/
void drain();
/**
* @brief Create the user data for one reply callback.
*
* @param method Method name for error logs.
* @return Data that `on_reply` frees.
*/
gpointer reply_data(const char *method);
/**
* @brief Read the cached properties into the atomics; runs on the input thread.
*/
void read_properties();
static void on_reply(GObject *source, GAsyncResult *result, gpointer data);
static void on_property(GObject *object, GParamSpec *pspec, gpointer self);
std::shared_ptr<session_impl_t> session; ///< Keeps the session and its input thread alive.
std::uint32_t id; ///< Console id.
int timeout_ms; ///< Timeout of each call.
std::shared_ptr<pending_calls_t> pending {std::make_shared<pending_calls_t>()}; ///< Calls without a reply.
std::mutex queue_mutex; ///< Guards `queue` and `drain_posted`.
std::vector<std::function<void()>> queue; ///< Calls not yet started, in order.
bool drain_posted {false}; ///< Whether a drain is queued on the input thread.
QemuDBusDisplay1Console *console {nullptr}; ///< Console proxy, for Width and Height.
QemuDBusDisplay1Keyboard *keyboard {nullptr}; ///< Keyboard proxy, when the console has one.
QemuDBusDisplay1Mouse *mouse {nullptr}; ///< Mouse proxy, when the console has one.
QemuDBusDisplay1MultiTouch *multitouch {nullptr}; ///< MultiTouch proxy, when the console has one.
std::atomic<bool> absolute {false}; ///< Cached `Mouse.IsAbsolute`.
std::atomic<int> slots {0}; ///< Cached `MultiTouch.MaxSlots`.
std::atomic<std::uint32_t> console_width {0}; ///< Cached `Console.Width`.
std::atomic<std::uint32_t> console_height {0}; ///< Cached `Console.Height`.
};
/**
* @brief GDBus implementation of the session.
*/
class session_impl_t: public session_t, public std::enable_shared_from_this<session_impl_t> {
@@ -479,6 +599,42 @@
return registration;
}
std::shared_ptr<console_input_t> open_input(std::uint32_t console_id) override {
const bool known = std::ranges::any_of(console_proxies, [&](const auto &entry) {
return entry.first == console_id;
});
if (!known || !is_alive) {
BOOST_LOG(error) << "qemu: console "sv << console_id << " is not available for input"sv;
return nullptr;
}
auto input = std::make_shared<console_input_impl_t>(shared_from_this(), console_id, timeout_ms);
bool ok = false;
deadline_t deadline {timeout};
input_loop().invoke([&]() {
ok = input->start(connection, deadline.cancellable);
});
if (!ok) {
return nullptr;
}
return input;
}
/**
* @brief Thread that sends input to QEMU, created on first use.
* @details It only makes asynchronous calls, so input never waits behind a blocking
* registration on the bus thread or behind display updates on the listener thread.
*
* @return The input thread.
*/
loop_thread_t &input_loop() {
std::lock_guard lock {input_loop_mutex};
if (!input_loop_thread) {
input_loop_thread = std::make_unique<loop_thread_t>();
}
return *input_loop_thread;
}
/**
* @brief Thread that owns the bus connection and makes blocking calls to QEMU.
*/
@@ -706,7 +862,211 @@
QemuDBusDisplay1VM *vm_proxy {nullptr}; ///< VM proxy.
QemuDBusDisplay1Audio *audio_proxy {nullptr}; ///< Audio proxy, created on the first audio registration.
std::vector<std::pair<std::uint32_t, QemuDBusDisplay1Console *>> console_proxies; ///< Console proxies in ConsoleIDs order.
std::mutex input_loop_mutex; ///< Guards `input_loop_thread`.
std::unique_ptr<loop_thread_t> input_loop_thread; ///< Input thread, created by the first `open_input()`.
};
console_input_impl_t::console_input_impl_t(std::shared_ptr<session_impl_t> session, std::uint32_t console_id, int timeout_ms):
session {std::move(session)},
id {console_id},
timeout_ms {timeout_ms} {
}
console_input_impl_t::~console_input_impl_t() {
// let queued calls go out and be answered, so no reply arrives after the proxies are gone
if (!flush(std::chrono::milliseconds {timeout_ms})) {
BOOST_LOG(warning) << "qemu: input calls to console "sv << id << " still pending at teardown"sv;
}
session->input_loop().invoke([this]() {
for (auto object : {(GObject *) console, (GObject *) keyboard, (GObject *) mouse, (GObject *) multitouch}) {
if (object) {
g_signal_handlers_disconnect_by_data(object, this);
g_object_unref(object);
}
}
console = nullptr;
keyboard = nullptr;
mouse = nullptr;
multitouch = nullptr;
});
}
bool console_input_impl_t::start(GDBusConnection *connection, GCancellable *cancellable) {
GError *err = nullptr;
const auto path = "/org/qemu/Display1/Console_" + std::to_string(id);
const auto flags = G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;
console = qemu_dbus_display1_console_proxy_new_sync(connection, flags, bus_name, path.c_str(), cancellable, &err);
if (!console) {
BOOST_LOG(error) << "qemu: couldn't create the console "sv << id << " proxy for input: "sv << err->message;
g_clear_error(&err);
return false;
}
const auto interfaces = to_strings(qemu_dbus_display1_console_get_interfaces(console));
const auto has = [&](std::string_view name) {
return std::ranges::find(interfaces, name) != interfaces.end();
};
if (has("org.qemu.Display1.Keyboard")) {
keyboard = qemu_dbus_display1_keyboard_proxy_new_sync(connection, flags, bus_name, path.c_str(), cancellable, &err);
}
if (!err && has("org.qemu.Display1.Mouse")) {
mouse = qemu_dbus_display1_mouse_proxy_new_sync(connection, flags, bus_name, path.c_str(), cancellable, &err);
}
if (!err && has("org.qemu.Display1.MultiTouch")) {
multitouch = qemu_dbus_display1_multi_touch_proxy_new_sync(connection, flags, bus_name, path.c_str(), cancellable, &err);
}
if (err) {
BOOST_LOG(error) << "qemu: couldn't create the input proxies of console "sv << id << ": "sv << err->message;
g_clear_error(&err);
return false;
}
g_signal_connect(console, "notify::width", G_CALLBACK(&console_input_impl_t::on_property), this);
g_signal_connect(console, "notify::height", G_CALLBACK(&console_input_impl_t::on_property), this);
if (mouse) {
g_signal_connect(mouse, "notify::is-absolute", G_CALLBACK(&console_input_impl_t::on_property), this);
}
if (multitouch) {
g_signal_connect(multitouch, "notify::max-slots", G_CALLBACK(&console_input_impl_t::on_property), this);
}
read_properties();
BOOST_LOG(info) << "qemu: sending input to console "sv << id << " (keyboard: "sv << (keyboard ? "yes"sv : "no"sv) << ", mouse: "sv << (mouse ? (absolute ? "absolute"sv : "relative"sv) : "no"sv) << ", touch slots: "sv << slots << ')';
return true;
}
bool console_input_impl_t::alive() const {
return session->alive();
}
void console_input_impl_t::read_properties() {
console_width = qemu_dbus_display1_console_get_width(console);
console_height = qemu_dbus_display1_console_get_height(console);
const bool was_absolute = absolute;
absolute = mouse && qemu_dbus_display1_mouse_get_is_absolute(mouse);
slots = multitouch ? std::max(0, qemu_dbus_display1_multi_touch_get_max_slots(multitouch)) : 0;
if (was_absolute != absolute) {
BOOST_LOG(debug) << "qemu: console "sv << id << " mouse is now "sv << (absolute ? "absolute"sv : "relative"sv);
}
}
void console_input_impl_t::on_property(GObject *object, GParamSpec *pspec, gpointer self) {
((console_input_impl_t *) self)->read_properties();
}
void console_input_impl_t::enqueue(std::function<void()> call) {
{
std::lock_guard lock {pending->mutex};
pending->count += 1;
}
std::lock_guard lock {queue_mutex};
queue.emplace_back(std::move(call));
if (!drain_posted) {
drain_posted = true;
// the session (and so the input thread) outlives this object: the destructor flushes first
session->input_loop().post([this]() {
drain();
});
}
}
void console_input_impl_t::drain() {
std::vector<std::function<void()>> calls;
{
std::lock_guard lock {queue_mutex};
calls.swap(queue);
drain_posted = false;
}
for (auto &call : calls) {
call();
}
}
gpointer console_input_impl_t::reply_data(const char *method) {
return new std::pair<std::shared_ptr<pending_calls_t>, const char *> {pending, method};
}
void console_input_impl_t::on_reply(GObject *source, GAsyncResult *result, gpointer data) {
std::unique_ptr<std::pair<std::shared_ptr<pending_calls_t>, const char *>> call {(std::pair<std::shared_ptr<pending_calls_t>, const char *> *) data};
auto &state = *call->first;
GError *err = nullptr;
if (auto reply = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &err)) {
g_variant_unref(reply);
} else {
// the first errors are worth a warning; a guest switching IsAbsolute can fail a few moves
const auto count = ++state.errors;
BOOST_LOG(count <= 3 ? warning : debug) << "qemu: "sv << call->second << " failed: "sv << err->message;
g_clear_error(&err);
}
std::lock_guard lock {state.mutex};
state.count -= 1;
if (state.count == 0) {
state.idle.notify_all();
}
}
void console_input_impl_t::key(std::uint32_t qnum, bool pressed) {
if (!keyboard) {
return;
}
enqueue([this, qnum, pressed]() {
if (pressed) {
qemu_dbus_display1_keyboard_call_press(keyboard, qnum, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Keyboard.Press"));
} else {
qemu_dbus_display1_keyboard_call_release(keyboard, qnum, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Keyboard.Release"));
}
});
}
void console_input_impl_t::button(mouse_button_e button, bool pressed) {
if (!mouse) {
return;
}
enqueue([this, button, pressed]() {
if (pressed) {
qemu_dbus_display1_mouse_call_press(mouse, (guint) button, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Mouse.Press"));
} else {
qemu_dbus_display1_mouse_call_release(mouse, (guint) button, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Mouse.Release"));
}
});
}
void console_input_impl_t::set_abs_position(std::uint32_t x, std::uint32_t y) {
if (!mouse) {
return;
}
enqueue([this, x, y]() {
qemu_dbus_display1_mouse_call_set_abs_position(mouse, x, y, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Mouse.SetAbsPosition"));
});
}
void console_input_impl_t::rel_motion(std::int32_t dx, std::int32_t dy) {
if (!mouse) {
return;
}
enqueue([this, dx, dy]() {
qemu_dbus_display1_mouse_call_rel_motion(mouse, dx, dy, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("Mouse.RelMotion"));
});
}
void console_input_impl_t::touch(touch_kind_e kind, std::uint64_t slot, double x, double y) {
if (!multitouch) {
return;
}
enqueue([this, kind, slot, x, y]() {
qemu_dbus_display1_multi_touch_call_send_event(multitouch, (guint) kind, slot, x, y, G_DBUS_CALL_FLAGS_NONE, timeout_ms, nullptr, &console_input_impl_t::on_reply, reply_data("MultiTouch.SendEvent"));
});
}
bool console_input_impl_t::flush(std::chrono::milliseconds timeout) {
std::unique_lock lock {pending->mutex};
return pending->idle.wait_for(lock, timeout, [this]() {
return pending->count == 0;
});
}
listener_impl_t::listener_impl_t(std::shared_ptr<session_impl_t> session, std::shared_ptr<display_listener_t> listener):
session {std::move(session)},
src/platform/linux/qemu/session.h +146 −0
@@ -324,6 +324,142 @@
};
/**
* @brief Button values of `org.qemu.Display1.Mouse.Press` and `Release`.
*/
enum class mouse_button_e : std::uint32_t {
left = 0, ///< Left button.
middle = 1, ///< Middle button.
right = 2, ///< Right button.
wheel_up = 3, ///< One wheel step up.
wheel_down = 4, ///< One wheel step down.
side = 5, ///< Side (back) button.
extra = 6, ///< Extra (forward) button.
};
/**
* @brief Event kinds of `org.qemu.Display1.MultiTouch.SendEvent`.
*/
enum class touch_kind_e : std::uint32_t {
begin = 0, ///< A contact starts.
update = 1, ///< A contact moves.
end = 2, ///< A contact lifts.
cancel = 3, ///< A contact is cancelled.
};
/**
* @brief The keyboard, mouse and touch interfaces QEMU exports on one console.
* @details The input methods never block: calls are queued, sent asynchronously from a GLib
* thread that makes no blocking calls, and reach QEMU in the order they were made. Errors QEMU
* returns are logged. The property getters read values cached from `PropertiesChanged`, so they
* follow the guest (for example `IsAbsolute` when the guest driver switches devices). All
* methods are thread-safe.
*/
class console_input_t {
public:
virtual ~console_input_t() = default;
/**
* @brief Id of the console the input goes to.
*
* @return Console id.
*/
[[nodiscard]] virtual std::uint32_t console_id() const = 0;
/**
* @brief Report whether QEMU is still reachable.
*
* @return False once the session's bus connection closed or QEMU left the bus.
*/
[[nodiscard]] virtual bool alive() const = 0;
/**
* @brief Current `Mouse.IsAbsolute` property.
*
* @return True when the guest uses an absolute pointing device, so `set_abs_position()` works
* and `rel_motion()` fails; false for relative devices such as a PS/2 mouse.
*/
[[nodiscard]] virtual bool is_absolute() const = 0;
/**
* @brief Report whether the console exports `org.qemu.Display1.MultiTouch`.
*
* @return True when touch events can be sent.
*/
[[nodiscard]] virtual bool has_multitouch() const = 0;
/**
* @brief `MultiTouch.MaxSlots` property.
*
* @return Number of touch slots, 0 without multi-touch.
*/
[[nodiscard]] virtual int max_slots() const = 0;
/**
* @brief Current console width, from the `Console.Width` property.
*
* @return Width in pixels.
*/
[[nodiscard]] virtual std::uint32_t width() const = 0;
/**
* @brief Current console height, from the `Console.Height` property.
*
* @return Height in pixels.
*/
[[nodiscard]] virtual std::uint32_t height() const = 0;
/**
* @brief Queue `Keyboard.Press` or `Keyboard.Release`.
*
* @param qnum QEMU key number.
* @param pressed True for a press, false for a release.
*/
virtual void key(std::uint32_t qnum, bool pressed) = 0;
/**
* @brief Queue `Mouse.Press` or `Mouse.Release`.
*
* @param button Button.
* @param pressed True for a press, false for a release.
*/
virtual void button(mouse_button_e button, bool pressed) = 0;
/**
* @brief Queue `Mouse.SetAbsPosition`.
*
* @param x X position in console pixels, less than the console width.
* @param y Y position in console pixels, less than the console height.
*/
virtual void set_abs_position(std::uint32_t x, std::uint32_t y) = 0;
/**
* @brief Queue `Mouse.RelMotion`.
*
* @param dx Horizontal motion in pixels.
* @param dy Vertical motion in pixels.
*/
virtual void rel_motion(std::int32_t dx, std::int32_t dy) = 0;
/**
* @brief Queue `MultiTouch.SendEvent`.
*
* @param kind Event kind.
* @param slot Touch slot, less than `max_slots()`.
* @param x X position in console pixels.
* @param y Y position in console pixels.
*/
virtual void touch(touch_kind_e kind, std::uint64_t slot, double x, double y) = 0;
/**
* @brief Wait until QEMU answered every queued call.
*
* @param timeout Maximum time to wait.
* @return True when no call is pending.
*/
virtual bool flush(std::chrono::milliseconds timeout) = 0;
};
/**
* @brief Registration of a display or audio listener; destroying it unregisters the listener.
* @details After the destructor returns, no method of the listener is invoked again.
*/
@@ -390,6 +526,16 @@
* @return Registration handle, or nullptr when QEMU has no D-Bus audio or refused the listener.
*/
virtual std::unique_ptr<listener_registration_t> register_audio_out_listener(std::shared_ptr<audio_out_listener_t> listener) = 0;
/**
* @brief Open the keyboard, mouse and touch interfaces of a console.
* @details Blocks while the proxies read their properties; the returned object's input
* methods never block.
*
* @param console_id Id of the console to send input to.
* @return Console input, or nullptr when the console doesn't exist or QEMU is gone.
*/
virtual std::shared_ptr<console_input_t> open_input(std::uint32_t console_id) = 0;
};
/**
src/platform/virtualhid_input.cpp +91 −2
@@ -23,6 +23,10 @@
#include "src/logging.h"
#include "virtualhid_input.h"
#ifdef SUNSHINE_BUILD_QEMU
#include "src/platform/linux/qemu/input.h"
#endif
using namespace std::literals;
namespace platf::virtualhid {
@@ -1002,6 +1006,9 @@
*/
struct input_raw_t {
virtualhid::input_context_t virtualhid; ///< libvirtualhid input context.
#ifdef SUNSHINE_BUILD_QEMU
std::unique_ptr<qemu::input_t> qemu; ///< Keyboard, mouse and touch for a VM with `capture = qemu`; gamepads stay on libvirtualhid.
#endif
};
namespace {
@@ -1016,15 +1023,28 @@
* @param input Platform input backend that receives the event.
*/
explicit client_input_raw_t(input_t &input):
#ifdef SUNSHINE_BUILD_QEMU
virtualhid {input->virtualhid} {}
qemu {input->qemu.get()},
#endif
virtualhid {input->virtualhid} {
}
#ifdef SUNSHINE_BUILD_QEMU
qemu::input_t *qemu; ///< QEMU input of the global context, or nullptr.
#endif
virtualhid::client_context_t virtualhid; ///< libvirtualhid client context.
};
} // namespace
input_t input() {
auto raw = new input_raw_t {};
return {new input_raw_t {}};
#ifdef SUNSHINE_BUILD_QEMU
if (config::video.capture == "qemu") {
raw->qemu = std::make_unique<qemu::input_t>(config::video.qemu_dbus_address);
}
#endif
return {raw};
}
std::unique_ptr<client_input_t> allocate_client_input_context(input_t &input) {
@@ -1044,38 +1064,107 @@
}
#endif
#ifdef SUNSHINE_BUILD_QEMU
namespace {
/**
* @brief Get the QEMU input that keyboard, mouse and touch events go to instead of libvirtualhid.
*
* @param input Platform input backend.
* @return QEMU input, or nullptr unless `capture = qemu`.
*/
qemu::input_t *qemu_input(input_t &input) {
return input->qemu.get();
}
/**
* @brief Get the QEMU input that touch and pen events go to instead of libvirtualhid.
*
* @param input Per-client platform input backend.
* @return QEMU input, or nullptr unless `capture = qemu`.
*/
qemu::input_t *qemu_input(client_input_t *input) {
return static_cast<client_input_raw_t *>(input)->qemu;
}
} // namespace
#endif
void move_mouse(input_t &input, int deltaX, int deltaY) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->move_mouse(deltaX, deltaY);
}
#endif
virtualhid::move_mouse(virtualhid::get_input_context(input), deltaX, deltaY);
}
void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->abs_mouse(touch_port, x, y);
}
#endif
virtualhid::abs_mouse(virtualhid::get_input_context(input), touch_port, x, y);
}
void button_mouse(input_t &input, int button, bool release) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->button_mouse(button, release);
}
#endif
virtualhid::button_mouse(virtualhid::get_input_context(input), button, release);
}
void scroll(input_t &input, int high_res_distance) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->scroll(high_res_distance);
}
#endif
virtualhid::scroll(virtualhid::get_input_context(input), high_res_distance);
}
void hscroll(input_t &input, int high_res_distance) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->hscroll(high_res_distance);
}
#endif
virtualhid::hscroll(virtualhid::get_input_context(input), high_res_distance);
}
void keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->keyboard_update(modcode, release, flags);
}
#endif
virtualhid::keyboard_update(virtualhid::get_input_context(input), modcode, release, flags);
}
void unicode(input_t &input, const char *utf8, int size) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->unicode({utf8, utf8 && size > 0 ? static_cast<std::size_t>(size) : 0});
}
#endif
virtualhid::unicode(virtualhid::get_input_context(input), utf8, size);
}
void touch_update(client_input_t *input, const touch_port_t &touch_port, const touch_input_t &touch) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->touch_update(touch_port, touch);
}
#endif
virtualhid::touch_update(virtualhid::get_client_context(input), touch_port, touch);
}
void pen_update(client_input_t *input, const touch_port_t &touch_port, const pen_input_t &pen) {
#ifdef SUNSHINE_BUILD_QEMU
if (auto qemu = qemu_input(input)) {
return qemu->pen_update(touch_port, pen);
}
#endif
virtualhid::pen_update(virtualhid::get_client_context(input), touch_port, pen);
}
tests/unit/platform/linux/qemu/fake_qemu.h +252 −3
@@ -15,9 +15,11 @@
#include <condition_variable>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <future>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
@@ -174,11 +176,28 @@
qemu_dbus_display1_console_set_type_(state.skeleton, c.type.c_str());
qemu_dbus_display1_console_set_width(state.skeleton, c.width);
qemu_dbus_display1_console_set_height(state.skeleton, c.height);
const gchar *interfaces[] = {"org.qemu.Display1.Keyboard", "org.qemu.Display1.Mouse", nullptr};
const gchar *interfaces[] = {"org.qemu.Display1.Keyboard", "org.qemu.Display1.Mouse", "org.qemu.Display1.MultiTouch", nullptr};
qemu_dbus_display1_console_set_interfaces(state.skeleton, interfaces);
g_signal_connect(state.skeleton, "handle-register-listener", G_CALLBACK(&fake_qemu_t::on_register_listener), &state);
auto path = "/org/qemu/Display1/Console_" + std::to_string(c.id);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(state.skeleton), connection, path.c_str(), nullptr);
// input interfaces on the same object, like QEMU's dbus_display_console_new()
state.keyboard = qemu_dbus_display1_keyboard_skeleton_new();
g_signal_connect(state.keyboard, "handle-press", G_CALLBACK(&fake_qemu_t::on_key_press), &state);
g_signal_connect(state.keyboard, "handle-release", G_CALLBACK(&fake_qemu_t::on_key_release), &state);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(state.keyboard), connection, path.c_str(), nullptr);
state.mouse = qemu_dbus_display1_mouse_skeleton_new();
qemu_dbus_display1_mouse_set_is_absolute(state.mouse, TRUE);
g_signal_connect(state.mouse, "handle-press", G_CALLBACK(&fake_qemu_t::on_mouse_press), &state);
g_signal_connect(state.mouse, "handle-release", G_CALLBACK(&fake_qemu_t::on_mouse_release), &state);
g_signal_connect(state.mouse, "handle-set-abs-position", G_CALLBACK(&fake_qemu_t::on_set_abs_position), &state);
g_signal_connect(state.mouse, "handle-rel-motion", G_CALLBACK(&fake_qemu_t::on_rel_motion), &state);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(state.mouse), connection, path.c_str(), nullptr);
state.multitouch = qemu_dbus_display1_multi_touch_skeleton_new();
qemu_dbus_display1_multi_touch_set_max_slots(state.multitouch, 10);
g_signal_connect(state.multitouch, "handle-send-event", G_CALLBACK(&fake_qemu_t::on_touch_event), &state);
g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(state.multitouch), connection, path.c_str(), nullptr);
}
if (with_audio) {
@@ -203,8 +222,15 @@
invoke([this]() {
for (auto &[id, state] : console_states) {
drop_listener_locked(state);
g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(state.skeleton));
g_object_unref(state.skeleton);
for (auto skeleton : {(GDBusInterfaceSkeleton *) state.keyboard, (GDBusInterfaceSkeleton *) state.mouse, (GDBusInterfaceSkeleton *) state.multitouch, (GDBusInterfaceSkeleton *) state.skeleton}) {
if (skeleton) {
g_signal_handlers_disconnect_by_data(skeleton, &state);
if (g_dbus_interface_skeleton_get_connection(skeleton)) {
g_dbus_interface_skeleton_unexport(skeleton);
}
g_object_unref(skeleton);
}
}
}
if (audio.skeleton) {
std::lock_guard lock {mutex};
@@ -771,6 +797,121 @@
}
/**
* @brief Snapshot the input calls a console received, in the order QEMU would process them.
* @details Each call is one string: `key press <qnum>`, `key release <qnum>`,
* `button press <button>`, `button release <button>`, `abs <x> <y>`, `rel <dx> <dy>` or
* `touch <kind> <slot> <x> <y>` (coordinates with one decimal). Calls the fake refused, like
* QEMU does, are recorded with an `error ` prefix.
*
* @param console_id Console to read.
* @return Recorded calls.
*/
std::vector<std::string> input_calls(std::uint32_t console_id) {
std::lock_guard lock {mutex};
return console_states[console_id].input_calls;
}
/**
* @brief Wait until a console received a number of input calls.
*
* @param console_id Console to check.
* @param count Calls to wait for.
* @param timeout Maximum time to wait.
* @return True when at least `count` calls arrived in time.
*/
bool wait_for_input_calls(std::uint32_t console_id, std::size_t count, std::chrono::milliseconds timeout = 5s) {
return wait_until(
[&]() {
std::lock_guard lock {mutex};
return console_states[console_id].input_calls.size() >= count;
},
timeout
);
}
/**
* @brief Forget the input calls recorded so far.
*
* @param console_id Console to clear.
*/
void clear_input_calls(std::uint32_t console_id) {
std::lock_guard lock {mutex};
console_states[console_id].input_calls.clear();
}
/**
* @brief Change `Mouse.IsAbsolute`, as when the guest switches between a tablet and a PS/2 mouse.
*
* @param console_id Console to change.
* @param absolute New value.
*/
void set_absolute(std::uint32_t console_id, bool absolute) {
invoke([&]() {
qemu_dbus_display1_mouse_set_is_absolute(console_states[console_id].mouse, absolute);
g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(console_states[console_id].mouse));
});
}
/**
* @brief Change the console size properties, as QEMU does on a guest mode change.
*
* @param console_id Console to change.
* @param width New width.
* @param height New height.
*/
void set_console_size(std::uint32_t console_id, std::uint32_t width, std::uint32_t height) {
invoke([&]() {
auto skeleton = console_states[console_id].skeleton;
qemu_dbus_display1_console_set_width(skeleton, width);
qemu_dbus_display1_console_set_height(skeleton, height);
g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(skeleton));
});
}
/**
* @brief Remove `org.qemu.Display1.MultiTouch` from a console, like a QEMU without touch support.
*
* @param console_id Console to change; call before a client opens its input.
*/
void remove_multitouch(std::uint32_t console_id) {
invoke([&]() {
auto &state = console_states[console_id];
g_dbus_interface_skeleton_unexport(G_DBUS_INTERFACE_SKELETON(state.multitouch));
const gchar *interfaces[] = {"org.qemu.Display1.Keyboard", "org.qemu.Display1.Mouse", nullptr};
qemu_dbus_display1_console_set_interfaces(state.skeleton, interfaces);
g_dbus_interface_skeleton_flush(G_DBUS_INTERFACE_SKELETON(state.skeleton));
});
}
/**
* @brief Block the fake's thread, as a busy QEMU main loop does, so no call is answered meanwhile.
*
* @param duration How long to block.
* @return Future that completes when the fake is responsive again.
*/
std::future<void> stall(std::chrono::milliseconds duration) {
auto done = std::make_shared<std::promise<void>>();
auto future = done->get_future();
auto source = g_idle_source_new();
g_source_set_callback(
source,
[](gpointer data) -> gboolean {
auto args = (std::pair<std::chrono::milliseconds, std::shared_ptr<std::promise<void>>> *) data;
std::this_thread::sleep_for(args->first);
args->second->set_value();
return G_SOURCE_REMOVE;
},
new std::pair<std::chrono::milliseconds, std::shared_ptr<std::promise<void>>> {duration, done},
[](gpointer data) {
delete (std::pair<std::chrono::milliseconds, std::shared_ptr<std::promise<void>>> *) data;
}
);
g_source_attach(source, context);
g_source_unref(source);
return future;
}
/**
* @brief Give up ownership of `org.qemu`.
*/
void release_name() {
@@ -976,7 +1117,115 @@
std::vector<std::string> listener_interfaces; ///< Interfaces the listener advertised.
int registrations {0}; ///< Number of successful registrations.
bool closed_by_peer {false}; ///< Whether the client closed the connection.
QemuDBusDisplay1Keyboard *keyboard {nullptr}; ///< Exported Keyboard interface.
QemuDBusDisplay1Mouse *mouse {nullptr}; ///< Exported Mouse interface.
QemuDBusDisplay1MultiTouch *multitouch {nullptr}; ///< Exported MultiTouch interface.
std::vector<std::string> input_calls; ///< Input calls received, see `input_calls()`.
};
/**
* @brief Record one input call.
*
* @param state Console that received it.
* @param call Call description.
*/
static void record_input(console_state_t *state, std::string call) {
std::lock_guard lock {state->owner->mutex};
state->input_calls.emplace_back(std::move(call));
}
/**
* @brief Fail an input call and record it, like QEMU's `DBUS_DISPLAY_ERROR_INVALID` replies.
*
* @param state Console that received it.
* @param invocation Method invocation.
* @param call Call description.
* @param message Error message.
* @return Always TRUE.
*/
static gboolean refuse_input(console_state_t *state, GDBusMethodInvocation *invocation, const std::string &call, const char *message) {
record_input(state, "error " + call);
g_dbus_method_invocation_return_error_literal(invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, message);
return TRUE;
}
/**
* @brief Format a touch coordinate with one decimal.
*
* @param value Coordinate.
* @return Text.
*/
static std::string one_decimal(double value) {
char text[32];
std::snprintf(text, sizeof(text), "%.1f", value);
return text;
}
static gboolean on_key_press(QemuDBusDisplay1Keyboard *object, GDBusMethodInvocation *invocation, guint keycode, gpointer data) {
record_input((console_state_t *) data, "key press " + std::to_string(keycode));
qemu_dbus_display1_keyboard_complete_press(object, invocation);
return TRUE;
}
static gboolean on_key_release(QemuDBusDisplay1Keyboard *object, GDBusMethodInvocation *invocation, guint keycode, gpointer data) {
record_input((console_state_t *) data, "key release " + std::to_string(keycode));
qemu_dbus_display1_keyboard_complete_release(object, invocation);
return TRUE;
}
static gboolean on_mouse_press(QemuDBusDisplay1Mouse *object, GDBusMethodInvocation *invocation, guint button, gpointer data) {
record_input((console_state_t *) data, "button press " + std::to_string(button));
qemu_dbus_display1_mouse_complete_press(object, invocation);
return TRUE;
}
static gboolean on_mouse_release(QemuDBusDisplay1Mouse *object, GDBusMethodInvocation *invocation, guint button, gpointer data) {
record_input((console_state_t *) data, "button release " + std::to_string(button));
qemu_dbus_display1_mouse_complete_release(object, invocation);
return TRUE;
}
static gboolean on_set_abs_position(QemuDBusDisplay1Mouse *object, GDBusMethodInvocation *invocation, guint x, guint y, gpointer data) {
auto state = (console_state_t *) data;
const auto call = "abs " + std::to_string(x) + " " + std::to_string(y);
// like QEMU's dbus_mouse_set_pos()
if (!qemu_dbus_display1_mouse_get_is_absolute(object)) {
return refuse_input(state, invocation, call, "Mouse is not absolute");
}
if (x >= qemu_dbus_display1_console_get_width(state->skeleton) || y >= qemu_dbus_display1_console_get_height(state->skeleton)) {
return refuse_input(state, invocation, call, "Invalid mouse position");
}
record_input(state, call);
qemu_dbus_display1_mouse_complete_set_abs_position(object, invocation);
return TRUE;
}
static gboolean on_rel_motion(QemuDBusDisplay1Mouse *object, GDBusMethodInvocation *invocation, gint dx, gint dy, gpointer data) {
auto state = (console_state_t *) data;
const auto call = "rel " + std::to_string(dx) + " " + std::to_string(dy);
// like QEMU's dbus_mouse_rel_motion()
if (qemu_dbus_display1_mouse_get_is_absolute(object)) {
return refuse_input(state, invocation, call, "Mouse is not relative");
}
record_input(state, call);
qemu_dbus_display1_mouse_complete_rel_motion(object, invocation);
return TRUE;
}
static gboolean on_touch_event(QemuDBusDisplay1MultiTouch *object, GDBusMethodInvocation *invocation, guint kind, guint64 slot, gdouble x, gdouble y, gpointer data) {
auto state = (console_state_t *) data;
const auto call = "touch " + std::to_string(kind) + " " + std::to_string(slot) + " " + one_decimal(x) + " " + one_decimal(y);
// like QEMU's dbus_touch_send_event() and qemu_input_touch_event()
if (kind > 3) {
return refuse_input(state, invocation, call, "Invalid touch event kind");
}
if (slot >= (guint64) qemu_dbus_display1_multi_touch_get_max_slots(object)) {
return refuse_input(state, invocation, call, "Unexpected touch slot number");
}
record_input(state, call);
qemu_dbus_display1_multi_touch_complete_send_event(object, invocation);
return TRUE;
}
/**
* @brief Run a function on the fake's thread and wait for it.
tests/unit/platform/linux/qemu/test_input.cpp +565 −0
@@ -1,0 +1,565 @@
/**
* @file tests/unit/platform/linux/qemu/test_input.cpp
* @brief Test sending Moonlight keyboard, mouse and touch input to a fake QEMU's console.
*/
#ifdef SUNSHINE_BUILD_QEMU
// test includes
#include "../../../../tests_common.h"
#include "fake_qemu.h"
// standard includes
#include <algorithm>
#include <chrono>
#include <memory>
#include <string>
#include <thread>
#include <vector>
// local includes
#include <src/config.h>
#include <src/platform/common.h>
#include <src/platform/linux/qemu/frame_store.h>
#include <src/platform/linux/qemu/input.h>
#include <src/platform/linux/qemu/session.h>
using namespace std::literals;
namespace {
constexpr std::uint16_t vk_a = 0x41; ///< VK_A.
constexpr std::uint16_t vk_shift = 0x10; ///< VK_SHIFT.
constexpr std::uint16_t vk_lshift = 0xA0; ///< VK_LSHIFT.
constexpr std::uint16_t vk_clear = 0x0C; ///< VK_CLEAR, which has no Linux key.
/**
* @brief A Moonlight touch event.
*
* @param type `LI_TOUCH_EVENT_*`.
* @param id Pointer id.
* @param x Normalized X.
* @param y Normalized Y.
* @return Touch event.
*/
platf::touch_input_t touch_event(std::uint8_t type, std::uint32_t id, float x, float y) {
return {type, LI_ROT_UNKNOWN, id, x, y, 1.0f, 0.0f, 0.0f};
}
/**
* @brief Fixture with a fake QEMU (640x400 console 0, 320x200 console 1) and `capture = qemu` pointing at it.
*/
class QemuInputTest: public BaseTest {
protected:
void SetUp() override {
BaseTest::SetUp();
saved_capture = config::video.capture;
saved_address = config::video.qemu_dbus_address;
saved_output = config::video.output_name;
saved_pen_touch = config::input.native_pen_touch;
bus = std::make_unique<qemu_test::private_bus_t>();
if (!bus->ok()) {
GTEST_SKIP() << "dbus-daemon is not available; REQ-INP tests need it";
}
start_fake();
config::video.capture = "qemu";
config::video.qemu_dbus_address = bus->address();
config::video.output_name = "";
}
void TearDown() override {
input.reset();
config::video.capture = saved_capture;
config::video.qemu_dbus_address = saved_address;
config::video.output_name = saved_output;
config::input.native_pen_touch = saved_pen_touch;
fake.reset();
bus.reset();
BaseTest::TearDown();
}
/**
* @brief (Re)start the fake QEMU.
*/
void start_fake() {
fake.reset();
fake = std::make_unique<qemu_test::fake_qemu_t>(
bus->address(),
"input-vm",
"00000000-0000-0000-0000-000000000005",
std::vector<qemu_test::fake_console_t> {{0, "VGA", "Graphic", 640, 400}, {1, "virtio-gpu", "Graphic", 320, 200}}
);
ASSERT_TRUE(fake->ok());
}
/**
* @brief Create the input and wait until it reaches a console.
*/
void connect_input() {
input = std::make_unique<qemu::input_t>(bus->address());
ASSERT_TRUE(input->wait_for_console(5s));
}
/**
* @brief Wait for the input's calls to be answered and read what the fake received.
*
* @param console_id Console to read.
* @return Calls in order.
*/
std::vector<std::string> calls(std::uint32_t console_id = 0) {
EXPECT_TRUE(input->flush(5s));
return fake->input_calls(console_id);
}
/**
* @brief Switch the fake's mouse mode and wait until the input sees it.
*
* @param absolute New mode.
*/
void set_absolute(bool absolute) {
fake->set_absolute(0, absolute);
ASSERT_TRUE(qemu_test::wait_until([&]() {
return input->guest_absolute() == absolute;
}));
}
std::unique_ptr<qemu_test::private_bus_t> bus; ///< Private bus.
std::unique_ptr<qemu_test::fake_qemu_t> fake; ///< Fake QEMU.
std::unique_ptr<qemu::input_t> input; ///< Input under test.
std::string saved_capture; ///< Saved `capture`.
std::string saved_address; ///< Saved `qemu_dbus_address`.
std::string saved_output; ///< Saved `output_name`.
bool saved_pen_touch {true}; ///< Saved `native_pen_touch`.
};
const platf::touch_port_t port_1280x800 {0, 0, 1280, 800, 1280, 800}; ///< Stream twice the console size.
const platf::touch_port_t port_640x400 {0, 0, 640, 400, 640, 400}; ///< Stream at the console size.
} // namespace
// @tag requirements: [REQ-INP-001, REQ-INP-002]
TEST_F(QemuInputTest, SessionSendsConsoleInputCallsInOrder) {
auto session = qemu::session_t::connect(bus->address());
ASSERT_TRUE(session);
EXPECT_EQ(session->open_input(7), nullptr) << "no such console";
auto device = session->open_input(0);
ASSERT_TRUE(device);
EXPECT_EQ(device->console_id(), 0u);
EXPECT_TRUE(device->alive());
EXPECT_TRUE(device->is_absolute());
EXPECT_TRUE(device->has_multitouch());
EXPECT_EQ(device->max_slots(), 10);
EXPECT_EQ(device->width(), 640u);
EXPECT_EQ(device->height(), 400u);
std::vector<std::string> expected;
for (int i = 0; i < 50; ++i) {
device->key(0x1e, true);
device->set_abs_position(i, 2 * i);
device->button(qemu::mouse_button_e::right, i % 2 == 0);
device->touch(qemu::touch_kind_e::update, 3, i + 0.5, 1.25);
expected.push_back("key press 30");
expected.push_back("abs " + std::to_string(i) + " " + std::to_string(2 * i));
expected.push_back(std::string {"button "} + (i % 2 == 0 ? "press" : "release") + " 2");
expected.push_back("touch 1 3 " + std::to_string(i) + ".5 1.2");
}
device->key(0x1e, false);
device->rel_motion(1, -1);
expected.push_back("key release 30");
expected.push_back("error rel 1 -1"); // QEMU refuses relative motion for an absolute mouse
ASSERT_TRUE(device->flush(5s));
EXPECT_EQ(fake->input_calls(0), expected);
}
// @tag requirements: [REQ-INP-001, REQ-INP-002]
TEST_F(QemuInputTest, SessionTracksIsAbsoluteAndConsoleSize) {
auto session = qemu::session_t::connect(bus->address());
ASSERT_TRUE(session);
auto device = session->open_input(0);
ASSERT_TRUE(device);
fake->set_absolute(0, false);
EXPECT_TRUE(qemu_test::wait_until([&]() {
return !device->is_absolute();
}));
fake->set_console_size(0, 1024, 768);
EXPECT_TRUE(qemu_test::wait_until([&]() {
return device->width() == 1024 && device->height() == 768;
}));
fake->remove_multitouch(1);
auto no_touch = session->open_input(1);
ASSERT_TRUE(no_touch);
EXPECT_FALSE(no_touch->has_multitouch());
EXPECT_EQ(no_touch->max_slots(), 0);
no_touch->touch(qemu::touch_kind_e::begin, 0, 1, 1);
EXPECT_TRUE(no_touch->flush(5s));
EXPECT_TRUE(fake->input_calls(1).empty());
}
// @tag requirements: [REQ-INP-001, REQ-INP-002]
TEST_F(QemuInputTest, InputNeverWaitsForABusyQemuAndKeepsItsOrder) {
connect_input();
input->move_mouse(0, 0); // nothing to send, but the input is warm
auto stalled = fake->stall(1s);
const auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 100; ++i) {
input->abs_mouse(port_640x400, (float) i, (float) i);
}
const auto elapsed = std::chrono::steady_clock::now() - start;
EXPECT_LT(elapsed, 200ms) << "input calls must not wait for QEMU's replies";
EXPECT_TRUE(fake->input_calls(0).empty());
stalled.wait();
std::vector<std::string> expected;
for (int i = 0; i < 100; ++i) {
expected.push_back("abs " + std::to_string(i) + " " + std::to_string(i));
}
EXPECT_EQ(calls(), expected);
}
// @tag requirements: [REQ-INP-001]
TEST_F(QemuInputTest, TypesALetterAndAShiftedLetter) {
connect_input();
input->keyboard_update(vk_a, false, 0);
input->keyboard_update(vk_a, true, 0);
input->keyboard_update(vk_lshift, false, 0);
input->keyboard_update(vk_a, false, SS_KBE_FLAG_NON_NORMALIZED);
input->keyboard_update(vk_a, true, SS_KBE_FLAG_NON_NORMALIZED);
input->keyboard_update(vk_lshift, true, 0);
EXPECT_EQ(calls(), (std::vector<std::string> {"key press 30", "key release 30", "key press 42", "key press 30", "key release 30", "key release 42"}));
}
// @tag requirements: [REQ-INP-001]
TEST_F(QemuInputTest, KeysHeldByTwoVirtualKeysAreReleasedWithTheLastOne) {
connect_input();
// Sunshine presses VK_SHIFT for a synthetic modifier while the client holds VK_LSHIFT; both are KEY_LEFTSHIFT
input->keyboard_update(vk_lshift, false, 0);
input->keyboard_update(vk_shift, false, 0);
input->keyboard_update(vk_shift, true, 0);
input->keyboard_update(vk_lshift, false, 0); // key repeat presses again
input->keyboard_update(vk_lshift, true, 0);
input->keyboard_update(vk_lshift, true, 0); // a second release is not sent again
input->keyboard_update(0xFF00 | vk_a, false, 0); // only the low byte is the virtual key
input->keyboard_update(vk_a, true, 0);
EXPECT_EQ(calls(), (std::vector<std::string> {"key press 42", "key press 42", "key press 42", "key release 42", "key press 30", "key release 30"}));
}
// @tag requirements: [REQ-INP-001]
TEST_F(QemuInputTest, KeysWithoutAQemuKeyAndTextAreIgnored) {
connect_input();
input->keyboard_update(vk_clear, false, 0);
input->keyboard_update(vk_clear, true, 0);
input->keyboard_update(0xAB, false, 0); // VK_BROWSER_FAVORITES: a Linux key without a QEMU number
input->keyboard_update(vk_clear, false, 0); // logged only once
input->unicode("héllo");
input->unicode("again");
input->keyboard_update(vk_a, false, 0);
EXPECT_EQ(calls(), (std::vector<std::string> {"key press 30"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, AbsoluteMouseIsScaledToConsolePixels) {
connect_input();
input->abs_mouse(port_1280x800, 640.0f, 400.0f);
input->abs_mouse(port_1280x800, 1.9f, 3.0f);
input->abs_mouse(port_1280x800, 1280.0f, 800.0f); // the far edge is the last pixel
input->abs_mouse(port_1280x800, -5.0f, -5.0f);
input->abs_mouse({100, 50, 640, 400, 640, 400}, 420.0f, 250.0f); // the touch port offset is subtracted
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 320 200", "abs 0 1", "abs 639 399", "abs 0 0", "abs 320 200"}));
// the guest changed its resolution; Moonlight's coordinates still cover the whole console
fake->set_console_size(0, 1024, 768);
ASSERT_TRUE(qemu_test::wait_until([&]() {
return input->console_size() == std::pair<std::uint32_t, std::uint32_t> {1024, 768};
}));
fake->clear_input_calls(0);
input->abs_mouse(port_1280x800, 640.0f, 400.0f);
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 512 384"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, RelativeMouseGoesToARelativeGuest) {
connect_input();
set_absolute(false);
input->move_mouse(5, -3);
input->move_mouse(0, 0);
input->move_mouse(-200, 1000);
EXPECT_EQ(calls(), (std::vector<std::string> {"rel 5 -3", "rel -200 1000"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, RelativeMouseMovesATrackedPointerOnAnAbsoluteGuest) {
connect_input();
input->move_mouse(10, 5); // no known position yet: starts at the console center
input->abs_mouse(port_640x400, 100.0f, 100.0f);
input->move_mouse(10, 5);
input->move_mouse(-1000, -1000); // clamped at the console edge
input->move_mouse(2000, 2000);
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 330 205", "abs 100 100", "abs 110 105", "abs 0 0", "abs 639 399"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, AbsoluteMouseBecomesRelativeMotionFromTheGuestPointer) {
connect_input();
set_absolute(false);
// no MouseSet from QEMU yet: the guest pointer position is unknown, so the move is dropped
input->abs_mouse(port_640x400, 100.0f, 100.0f);
EXPECT_TRUE(calls().empty());
// a capture on the console provides QEMU's MouseSet reports
auto session = qemu::shared_session(bus->address());
ASSERT_TRUE(session);
auto store = std::make_shared<qemu::frame_store_t>();
auto registration = session->register_listener(0, store);
ASSERT_TRUE(registration);
ASSERT_TRUE(fake->wait_for_listener(0));
qemu::set_capture_console(session, 0, store);
ASSERT_TRUE(input->wait_for_console(5s));
// a hidden cursor's position isn't the pointer (QEMU reports 0,0 for virtio-gpu at registration)
ASSERT_TRUE(fake->mouse_set(0, 0, 0, false));
input->abs_mouse(port_640x400, 100.0f, 100.0f);
EXPECT_TRUE(calls().empty());
ASSERT_TRUE(fake->mouse_set(0, 50, 60, true));
input->abs_mouse(port_640x400, 100.0f, 100.0f);
input->abs_mouse(port_640x400, 90.0f, 100.0f);
// the stream is twice the console size: half-pixel steps add up
input->abs_mouse(port_1280x800, 181.0f, 200.0f);
input->abs_mouse(port_1280x800, 182.0f, 200.0f);
EXPECT_EQ(calls(), (std::vector<std::string> {"rel 50 40", "rel -10 0", "rel 1 0"}));
// QEMU's next report is taken once our own motion had time to reach the guest
ASSERT_TRUE(fake->mouse_set(0, 200, 150, true));
std::this_thread::sleep_for(150ms);
input->abs_mouse(port_640x400, 210.0f, 150.0f);
EXPECT_EQ(calls(), (std::vector<std::string> {"rel 50 40", "rel -10 0", "rel 1 0", "rel 10 0"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, FollowsTheGuestSwitchingBetweenAbsoluteAndRelative) {
connect_input();
// each call is answered before the guest switches, like a guest switching between gestures
input->abs_mouse(port_640x400, 10.0f, 20.0f);
ASSERT_TRUE(input->flush(5s));
set_absolute(false);
input->move_mouse(3, 4);
ASSERT_TRUE(input->flush(5s));
set_absolute(true);
input->move_mouse(3, 4);
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 10 20", "rel 3 4", "abs 16 28"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, ScrollSendsOneWheelStepPer120UnitsAndKeepsTheRest) {
connect_input();
input->scroll(60);
EXPECT_TRUE(calls().empty());
input->scroll(60);
input->scroll(-250); // -10 left over
input->scroll(130); // up one, 0 left over
input->hscroll(120); // no horizontal wheel on QEMU's D-Bus mouse
input->hscroll(-120);
EXPECT_EQ(calls(), (std::vector<std::string> {"button press 3", "button release 3", "button press 4", "button release 4", "button press 4", "button release 4", "button press 3", "button release 3"}));
}
// @tag requirements: [REQ-INP-002]
TEST_F(QemuInputTest, MoonlightButtonsMapToQemuButtons) {
connect_input();
for (int button = 1; button <= 5; ++button) {
input->button_mouse(button, false);
input->button_mouse(button, true);
}
input->button_mouse(6, false); // no such button
input->button_mouse(0, false);
input->button_mouse(BUTTON_LEFT, true); // not pressed, nothing to release
EXPECT_EQ(calls(), (std::vector<std::string> {"button press 0", "button release 0", "button press 1", "button release 1", "button press 2", "button release 2", "button press 5", "button release 5", "button press 6", "button release 6"}));
}
// @tag requirements: [REQ-INP-003]
TEST_F(QemuInputTest, TouchContactsGetTheLowestFreeSlot) {
connect_input();
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_DOWN, 7, 0.5f, 0.25f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_DOWN, 9, 0.0f, 1.0f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_MOVE, 7, 0.75f, 0.5f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_HOVER, 12, 0.1f, 0.1f)); // hovering isn't a contact
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_UP, 7, 0.75f, 0.5f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_UP, 7, 0.75f, 0.5f)); // already up
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_DOWN, 11, 0.1f, 0.1f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_CANCEL, 9, 0.0f, 0.0f));
input->touch_update(port_1280x800, touch_event(LI_TOUCH_EVENT_CANCEL_ALL, 0, 0.0f, 0.0f));
EXPECT_EQ(calls(), (std::vector<std::string> {"touch 0 0 320.0 100.0", "touch 0 1 0.0 400.0", "touch 1 0 480.0 200.0", "touch 2 0 480.0 200.0", "touch 0 0 64.0 40.0", "touch 3 1 0.0 400.0", "touch 3 0 64.0 40.0"}));
}
// @tag requirements: [REQ-INP-003]
TEST_F(QemuInputTest, TouchContactsBeyondMaxSlotsAreDropped) {
connect_input();
for (std::uint32_t id = 0; id < 11; ++id) {
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 100 + id, 0.5f, 0.5f));
}
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_MOVE, 110, 0.5f, 0.5f)); // never got a slot
const auto received = calls();
ASSERT_EQ(received.size(), 10u);
EXPECT_EQ(received.back(), "touch 0 9 320.0 200.0");
}
// @tag requirements: [REQ-INP-003]
TEST_F(QemuInputTest, TouchWithoutMultiTouchDrivesTheMouse) {
fake->remove_multitouch(0);
connect_input();
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 1, 0.5f, 0.5f));
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 2, 0.1f, 0.1f)); // a second finger is ignored
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_MOVE, 1, 0.25f, 0.5f));
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_UP, 1, 0.25f, 0.75f));
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 2, 0.0f, 0.0f));
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_CANCEL_ALL, 0, 0.0f, 0.0f));
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 320 200", "button press 0", "abs 160 200", "abs 160 300", "button release 0", "abs 0 0", "button press 0", "button release 0"}));
}
// @tag requirements: [REQ-INP-003]
TEST_F(QemuInputTest, PenDrivesTheMouse) {
connect_input();
platf::pen_input_t pen {LI_TOUCH_EVENT_HOVER, LI_TOOL_TYPE_PEN, 0, LI_TILT_UNKNOWN, LI_ROT_UNKNOWN, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f};
input->pen_update(port_640x400, pen);
pen.eventType = LI_TOUCH_EVENT_DOWN;
input->pen_update(port_640x400, pen);
pen.eventType = LI_TOUCH_EVENT_MOVE;
pen.x = 0.75f;
input->pen_update(port_640x400, pen);
pen.eventType = LI_TOUCH_EVENT_UP;
input->pen_update(port_640x400, pen);
EXPECT_EQ(calls(), (std::vector<std::string> {"abs 320 200", "abs 320 200", "button press 0", "abs 480 200", "abs 480 200", "button release 0"}));
}
// @tag requirements: [REQ-INP-001, REQ-INP-002, REQ-INP-003]
TEST_F(QemuInputTest, StreamingStopReleasesEverythingHeldInTheGuest) {
connect_input();
input->keyboard_update(vk_lshift, false, 0);
input->keyboard_update(vk_a, false, 0);
input->button_mouse(BUTTON_RIGHT, false);
input->touch_update(port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 4, 0.5f, 0.5f));
ASSERT_EQ(calls().size(), 4u);
fake->clear_input_calls(0);
// the last client disconnected
platf::streaming_will_stop();
auto released = calls();
std::ranges::sort(released);
EXPECT_EQ(released, (std::vector<std::string> {"button release 2", "key release 30", "key release 42", "touch 3 0 320.0 200.0"}));
// nothing is held any more
fake->clear_input_calls(0);
input->release_all();
input->keyboard_update(vk_a, true, 0);
EXPECT_TRUE(calls().empty());
}
// @tag requirements: [REQ-INP-001, REQ-INP-002]
TEST_F(QemuInputTest, ReconnectsWhenTheVmComesBack) {
connect_input();
input->keyboard_update(vk_a, false, 0);
ASSERT_EQ(calls().size(), 1u);
start_fake();
// events for the old VM are dropped while the input reconnects
ASSERT_TRUE(qemu_test::wait_until([&]() {
input->move_mouse(0, 0);
return !input->guest_absolute().has_value() || input->wait_for_console(10ms);
}));
ASSERT_TRUE(input->wait_for_console(5s));
input->keyboard_update(vk_a, true, 0); // the new VM never saw the press
input->keyboard_update(vk_a, false, 0);
EXPECT_EQ(calls(), (std::vector<std::string> {"key press 30"}));
}
// @tag requirements: [REQ-INP-001, REQ-INP-002]
TEST_F(QemuInputTest, SendsToTheCapturedConsole) {
config::video.output_name = "1";
connect_input();
input->keyboard_update(vk_a, false, 0);
EXPECT_EQ(calls(1), (std::vector<std::string> {"key press 30"})) << "without a capture, output_name selects the console";
// the capture registers on console 0: input follows it and releases what it held on console 1
auto session = qemu::shared_session(bus->address());
ASSERT_TRUE(session);
auto store = std::make_shared<qemu::frame_store_t>();
auto registration = session->register_listener(0, store);
ASSERT_TRUE(registration);
qemu::set_capture_console(session, 0, store);
ASSERT_TRUE(qemu_test::wait_until([&]() {
input->move_mouse(0, 0);
return input->console_size() == std::pair<std::uint32_t, std::uint32_t> {640, 400};
}));
input->keyboard_update(vk_a, false, 0);
EXPECT_EQ(calls(0), (std::vector<std::string> {"key press 30"}));
EXPECT_EQ(fake->input_calls(1), (std::vector<std::string> {"key press 30", "key release 30"}));
}
// @tag requirements: [REQ-INP-001, REQ-INP-003, REQ-CMP-001]
TEST_F(QemuInputTest, PlatformInputDispatchesToQemuOnlyWithQemuCapture) {
{
auto platform_input = platf::input();
ASSERT_TRUE(platform_input);
EXPECT_EQ(qemu::input_count(), 1u);
auto client = platf::allocate_client_input_context(platform_input);
ASSERT_TRUE(client);
ASSERT_TRUE(qemu_test::wait_until([&]() {
platf::keyboard_update(platform_input, vk_a, false, 0);
return !fake->input_calls(0).empty();
}));
std::this_thread::sleep_for(300ms); // presses sent while waiting may still be on their way
fake->clear_input_calls(0);
platf::keyboard_update(platform_input, vk_a, true, 0);
platf::button_mouse(platform_input, BUTTON_LEFT, false);
platf::button_mouse(platform_input, BUTTON_LEFT, true);
platf::abs_mouse(platform_input, port_640x400, 5.0f, 6.0f);
platf::move_mouse(platform_input, 1, 1);
platf::scroll(platform_input, -120);
platf::hscroll(platform_input, 120);
platf::unicode(platform_input, "x", 1);
platf::touch_update(client.get(), port_640x400, touch_event(LI_TOUCH_EVENT_DOWN, 1, 0.5f, 0.5f));
platf::pen_update(client.get(), port_640x400, {LI_TOUCH_EVENT_HOVER, LI_TOOL_TYPE_PEN, 0, LI_TILT_UNKNOWN, LI_ROT_UNKNOWN, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f});
EXPECT_TRUE(qemu_test::wait_until([&]() {
return fake->input_calls(0).size() >= 9;
}));
EXPECT_EQ(fake->input_calls(0), (std::vector<std::string> {"key release 30", "button press 0", "button release 0", "abs 5 6", "abs 6 7", "button press 4", "button release 4", "touch 0 0 320.0 200.0", "abs 0 0"}));
}
EXPECT_EQ(qemu::input_count(), 0u);
config::video.capture = "x11";
auto other = platf::input();
ASSERT_TRUE(other);
EXPECT_EQ(qemu::input_count(), 0u) << "no QEMU input unless capture = qemu";
}
// @tag requirements: [REQ-INP-003]
TEST_F(QemuInputTest, AdvertisesPenAndTouchWithQemuCapture) {
config::input.native_pen_touch = true;
EXPECT_TRUE(platf::get_capabilities() & platf::platform_caps::pen_touch);
config::input.native_pen_touch = false;
EXPECT_FALSE(platf::get_capabilities() & platf::platform_caps::pen_touch);
}
#endif
tests/unit/platform/linux/qemu/test_keymap.cpp +153 −0
@@ -1,0 +1,153 @@
/**
* @file tests/unit/platform/linux/qemu/test_keymap.cpp
* @brief Test the Windows virtual-key -> Linux key -> QEMU key number translation.
*/
#ifdef SUNSHINE_BUILD_QEMU
// test includes
#include "../../../../tests_common.h"
// standard includes
#include <cstdint>
#include <vector>
// platform includes
#include <linux/input-event-codes.h>
// local includes
#include <src/platform/linux/qemu/keymap.h>
namespace {
/**
* @brief One key: its virtual-key code, the Linux key and the QEMU key number QEMU expects.
*/
struct key_case_t {
std::uint16_t vk; ///< Windows virtual-key code Moonlight sends.
int linux_key; ///< Expected `KEY_*` code.
std::uint32_t qnum; ///< Expected QEMU key number (XT scan code, 0xe0 prefix as high bit).
};
/**
* @brief Check a list of keys against the keymap.
*
* @param cases Keys to check.
*/
void expect_keys(const std::vector<key_case_t> &cases) {
for (const auto &c : cases) {
EXPECT_EQ(qemu::keymap::vk_to_linux(c.vk), c.linux_key) << "vk 0x" << std::hex << c.vk;
EXPECT_EQ(qemu::keymap::linux_to_qnum(c.linux_key), c.qnum) << "linux key " << std::dec << c.linux_key;
EXPECT_EQ(qemu::keymap::vk_to_qnum(c.vk), c.qnum) << "vk 0x" << std::hex << c.vk;
}
}
} // namespace
// @tag requirements: [REQ-INP-001]
TEST(QemuKeymapTest, MapsThe104KeyUsLayout) {
// clang-format off
const std::vector<key_case_t> keys {
// function row (13)
{0x1B, KEY_ESC, 0x01}, {0x70, KEY_F1, 0x3b}, {0x71, KEY_F2, 0x3c}, {0x72, KEY_F3, 0x3d}, {0x73, KEY_F4, 0x3e},
{0x74, KEY_F5, 0x3f}, {0x75, KEY_F6, 0x40}, {0x76, KEY_F7, 0x41}, {0x77, KEY_F8, 0x42}, {0x78, KEY_F9, 0x43},
{0x79, KEY_F10, 0x44}, {0x7A, KEY_F11, 0x57}, {0x7B, KEY_F12, 0x58},
// number row (14)
{0xC0, KEY_GRAVE, 0x29}, {0x31, KEY_1, 0x02}, {0x32, KEY_2, 0x03}, {0x33, KEY_3, 0x04}, {0x34, KEY_4, 0x05},
{0x35, KEY_5, 0x06}, {0x36, KEY_6, 0x07}, {0x37, KEY_7, 0x08}, {0x38, KEY_8, 0x09}, {0x39, KEY_9, 0x0a},
{0x30, KEY_0, 0x0b}, {0xBD, KEY_MINUS, 0x0c}, {0xBB, KEY_EQUAL, 0x0d}, {0x08, KEY_BACKSPACE, 0x0e},
// top letter row (14)
{0x09, KEY_TAB, 0x0f}, {0x51, KEY_Q, 0x10}, {0x57, KEY_W, 0x11}, {0x45, KEY_E, 0x12}, {0x52, KEY_R, 0x13},
{0x54, KEY_T, 0x14}, {0x59, KEY_Y, 0x15}, {0x55, KEY_U, 0x16}, {0x49, KEY_I, 0x17}, {0x4F, KEY_O, 0x18},
{0x50, KEY_P, 0x19}, {0xDB, KEY_LEFTBRACE, 0x1a}, {0xDD, KEY_RIGHTBRACE, 0x1b}, {0xDC, KEY_BACKSLASH, 0x2b},
// home row (13)
{0x14, KEY_CAPSLOCK, 0x3a}, {0x41, KEY_A, 0x1e}, {0x53, KEY_S, 0x1f}, {0x44, KEY_D, 0x20}, {0x46, KEY_F, 0x21},
{0x47, KEY_G, 0x22}, {0x48, KEY_H, 0x23}, {0x4A, KEY_J, 0x24}, {0x4B, KEY_K, 0x25}, {0x4C, KEY_L, 0x26},
{0xBA, KEY_SEMICOLON, 0x27}, {0xDE, KEY_APOSTROPHE, 0x28}, {0x0D, KEY_ENTER, 0x1c},
// bottom letter row (12)
{0xA0, KEY_LEFTSHIFT, 0x2a}, {0x5A, KEY_Z, 0x2c}, {0x58, KEY_X, 0x2d}, {0x43, KEY_C, 0x2e}, {0x56, KEY_V, 0x2f},
{0x42, KEY_B, 0x30}, {0x4E, KEY_N, 0x31}, {0x4D, KEY_M, 0x32}, {0xBC, KEY_COMMA, 0x33}, {0xBE, KEY_DOT, 0x34},
{0xBF, KEY_SLASH, 0x35}, {0xA1, KEY_RIGHTSHIFT, 0x36},
// space row (8): the right-hand keys are extended scan codes (high bit set)
{0xA2, KEY_LEFTCTRL, 0x1d}, {0x5B, KEY_LEFTMETA, 0xdb}, {0xA4, KEY_LEFTALT, 0x38}, {0x20, KEY_SPACE, 0x39},
{0xA5, KEY_RIGHTALT, 0xb8}, {0x5C, KEY_RIGHTMETA, 0xdc}, {0x5D, KEY_COMPOSE, 0xdd}, {0xA3, KEY_RIGHTCTRL, 0x9d},
// print screen, scroll lock, pause (3); QEMU encodes SysRq as 0x54 and Pause as 0xc6
{0x2C, KEY_SYSRQ, 0x54}, {0x91, KEY_SCROLLLOCK, 0x46}, {0x13, KEY_PAUSE, 0xc6},
// navigation block (6)
{0x2D, KEY_INSERT, 0xd2}, {0x24, KEY_HOME, 0xc7}, {0x21, KEY_PAGEUP, 0xc9},
{0x2E, KEY_DELETE, 0xd3}, {0x23, KEY_END, 0xcf}, {0x22, KEY_PAGEDOWN, 0xd1},
// arrows (4)
{0x26, KEY_UP, 0xc8}, {0x25, KEY_LEFT, 0xcb}, {0x28, KEY_DOWN, 0xd0}, {0x27, KEY_RIGHT, 0xcd},
// keypad (17); Moonlight sends VK_RETURN for the keypad Enter too, so it is the main Enter
{0x90, KEY_NUMLOCK, 0x45}, {0x6F, KEY_KPSLASH, 0xb5}, {0x6A, KEY_KPASTERISK, 0x37}, {0x6D, KEY_KPMINUS, 0x4a},
{0x67, KEY_KP7, 0x47}, {0x68, KEY_KP8, 0x48}, {0x69, KEY_KP9, 0x49}, {0x6B, KEY_KPPLUS, 0x4e},
{0x64, KEY_KP4, 0x4b}, {0x65, KEY_KP5, 0x4c}, {0x66, KEY_KP6, 0x4d},
{0x61, KEY_KP1, 0x4f}, {0x62, KEY_KP2, 0x50}, {0x63, KEY_KP3, 0x51}, {0x0D, KEY_ENTER, 0x1c},
{0x60, KEY_KP0, 0x52}, {0x6E, KEY_KPDOT, 0x53},
};
// clang-format on
ASSERT_EQ(keys.size(), 104u);
expect_keys(keys);
}
// @tag requirements: [REQ-INP-001]
TEST(QemuKeymapTest, MapsMediaBrowserAndExtraKeys) {
// clang-format off
expect_keys({
{0xAD, KEY_MUTE, 0xa0}, {0xAE, KEY_VOLUMEDOWN, 0xae}, {0xAF, KEY_VOLUMEUP, 0xb0},
{0xB0, KEY_NEXTSONG, 0x99}, {0xB1, KEY_PREVIOUSSONG, 0x90}, {0xB2, KEY_STOPCD, 0xa4}, {0xB3, KEY_PLAYPAUSE, 0xa2},
{0xA6, KEY_BACK, 0xea}, {0xA7, KEY_FORWARD, 0xe9}, {0xA8, KEY_REFRESH, 0xe7}, {0xA9, KEY_STOP, 0xe8},
{0xAA, KEY_SEARCH, 0xe5}, {0xAC, KEY_HOMEPAGE, 0xb2}, {0xB4, KEY_EMAIL, 0xbf},
{0x5F, KEY_SLEEP, 0xdf}, {0x2A, KEY_PRINT, 0xb9}, {0x2F, KEY_HELP, 0xf5}, {0xFA, KEY_PLAY, 0xb3},
{0x7C, KEY_F13, 0x5d}, {0x87, KEY_F24, 0x6f},
{0xE2, KEY_102ND, 0x56}, {0x6C, KEY_KPCOMMA, 0x7e},
{0x15, KEY_HANGEUL, 0x72}, {0x19, KEY_HANJA, 0x71}, {0x1C, KEY_HENKAN, 0x79}, {0x1D, KEY_MUHENKAN, 0x7b},
{0xF2, KEY_KATAKANAHIRAGANA, 0x70},
});
// clang-format on
}
// @tag requirements: [REQ-INP-001]
TEST(QemuKeymapTest, SideLessModifiersUseTheLeftKey) {
expect_keys({
{0x10, KEY_LEFTSHIFT, 0x2a}, // VK_SHIFT
{0x11, KEY_LEFTCTRL, 0x1d}, // VK_CONTROL
{0x12, KEY_LEFTALT, 0x38}, // VK_MENU
});
EXPECT_NE(qemu::keymap::vk_to_qnum(0xA0), qemu::keymap::vk_to_qnum(0xA1)) << "left and right shift differ";
EXPECT_NE(qemu::keymap::vk_to_qnum(0xA2), qemu::keymap::vk_to_qnum(0xA3)) << "left and right control differ";
EXPECT_NE(qemu::keymap::vk_to_qnum(0xA4), qemu::keymap::vk_to_qnum(0xA5)) << "left and right alt differ";
}
// @tag requirements: [REQ-INP-001]
TEST(QemuKeymapTest, EveryMappedKeyDecodesToTheSameLinuxKeyInQemu) {
int mapped = 0;
for (std::uint16_t vk = 0; vk < 256; ++vk) {
const auto linux_key = qemu::keymap::vk_to_linux(vk);
if (!linux_key) {
EXPECT_FALSE(qemu::keymap::vk_to_qnum(vk)) << "vk 0x" << std::hex << vk;
continue;
}
const auto qnum = qemu::keymap::vk_to_qnum(vk);
if (!qnum) {
continue;
}
mapped += 1;
EXPECT_GT(*qnum, 0u);
// QEMU runs qemu_input_key_number_to_linux() on the number we send
EXPECT_EQ(qemu::keymap::qnum_to_linux(*qnum), linux_key) << "vk 0x" << std::hex << vk << " qnum 0x" << *qnum;
}
EXPECT_GE(mapped, 140) << "the 104 keys, media keys and F13-F24";
}
// @tag requirements: [REQ-INP-001]
TEST(QemuKeymapTest, CodesWithoutAKeyHaveNoMapping) {
EXPECT_FALSE(qemu::keymap::vk_to_linux(0x00));
EXPECT_FALSE(qemu::keymap::vk_to_linux(0x01)) << "VK_LBUTTON is a mouse button";
EXPECT_FALSE(qemu::keymap::vk_to_linux(0x0C)) << "VK_CLEAR has no Linux key";
EXPECT_FALSE(qemu::keymap::vk_to_linux(0xFF));
EXPECT_FALSE(qemu::keymap::vk_to_qnum(0x0C));
EXPECT_FALSE(qemu::keymap::linux_to_qnum(KEY_RESERVED));
EXPECT_FALSE(qemu::keymap::linux_to_qnum(-1));
EXPECT_FALSE(qemu::keymap::linux_to_qnum(100000));
EXPECT_FALSE(qemu::keymap::linux_to_qnum(KEY_FAVORITES)) << "keycodemapdb has no XT code for it";
EXPECT_FALSE(qemu::keymap::qnum_to_linux(0));
EXPECT_FALSE(qemu::keymap::qnum_to_linux(100000));
}
#endif
third-party/qemu-keycodemapdb/LICENSE.BSD +27 −0
@@ -1,0 +1,27 @@
Copyright (c) Individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of PyCA Cryptography nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
third-party/qemu-keycodemapdb/linux_to_qnum.cpp +535 −0
@@ -1,0 +1,535 @@
/*
* This file is auto-generated from keymaps.csv
* Database checksum sha256(26a8da612aafebc67b69d7c47a35a86a912f5576d15f6e1447928d49b4de6117)
* To re-generate, run:
* keymap-gen code-map --lang=stdc++ --varname=qemu_keycodemapdb_linux_to_qnum keymaps.csv linux qnum
*/
#include <vector>
extern const std::vector<unsigned short> qemu_keycodemapdb_linux_to_qnum;
const std::vector<unsigned short> qemu_keycodemapdb_linux_to_qnum = {
0, /* linux:0 (KEY_RESERVED) -> linux:0 (KEY_RESERVED) -> qnum:None */
0x1, /* linux:1 (KEY_ESC) -> linux:1 (KEY_ESC) -> qnum:1 */
0x2, /* linux:2 (KEY_1) -> linux:2 (KEY_1) -> qnum:2 */
0x3, /* linux:3 (KEY_2) -> linux:3 (KEY_2) -> qnum:3 */
0x4, /* linux:4 (KEY_3) -> linux:4 (KEY_3) -> qnum:4 */
0x5, /* linux:5 (KEY_4) -> linux:5 (KEY_4) -> qnum:5 */
0x6, /* linux:6 (KEY_5) -> linux:6 (KEY_5) -> qnum:6 */
0x7, /* linux:7 (KEY_6) -> linux:7 (KEY_6) -> qnum:7 */
0x8, /* linux:8 (KEY_7) -> linux:8 (KEY_7) -> qnum:8 */
0x9, /* linux:9 (KEY_8) -> linux:9 (KEY_8) -> qnum:9 */
0xa, /* linux:10 (KEY_9) -> linux:10 (KEY_9) -> qnum:10 */
0xb, /* linux:11 (KEY_0) -> linux:11 (KEY_0) -> qnum:11 */
0xc, /* linux:12 (KEY_MINUS) -> linux:12 (KEY_MINUS) -> qnum:12 */
0xd, /* linux:13 (KEY_EQUAL) -> linux:13 (KEY_EQUAL) -> qnum:13 */
0xe, /* linux:14 (KEY_BACKSPACE) -> linux:14 (KEY_BACKSPACE) -> qnum:14 */
0xf, /* linux:15 (KEY_TAB) -> linux:15 (KEY_TAB) -> qnum:15 */
0x10, /* linux:16 (KEY_Q) -> linux:16 (KEY_Q) -> qnum:16 */
0x11, /* linux:17 (KEY_W) -> linux:17 (KEY_W) -> qnum:17 */
0x12, /* linux:18 (KEY_E) -> linux:18 (KEY_E) -> qnum:18 */
0x13, /* linux:19 (KEY_R) -> linux:19 (KEY_R) -> qnum:19 */
0x14, /* linux:20 (KEY_T) -> linux:20 (KEY_T) -> qnum:20 */
0x15, /* linux:21 (KEY_Y) -> linux:21 (KEY_Y) -> qnum:21 */
0x16, /* linux:22 (KEY_U) -> linux:22 (KEY_U) -> qnum:22 */
0x17, /* linux:23 (KEY_I) -> linux:23 (KEY_I) -> qnum:23 */
0x18, /* linux:24 (KEY_O) -> linux:24 (KEY_O) -> qnum:24 */
0x19, /* linux:25 (KEY_P) -> linux:25 (KEY_P) -> qnum:25 */
0x1a, /* linux:26 (KEY_LEFTBRACE) -> linux:26 (KEY_LEFTBRACE) -> qnum:26 */
0x1b, /* linux:27 (KEY_RIGHTBRACE) -> linux:27 (KEY_RIGHTBRACE) -> qnum:27 */
0x1c, /* linux:28 (KEY_ENTER) -> linux:28 (KEY_ENTER) -> qnum:28 */
0x1d, /* linux:29 (KEY_LEFTCTRL) -> linux:29 (KEY_LEFTCTRL) -> qnum:29 */
0x1e, /* linux:30 (KEY_A) -> linux:30 (KEY_A) -> qnum:30 */
0x1f, /* linux:31 (KEY_S) -> linux:31 (KEY_S) -> qnum:31 */
0x20, /* linux:32 (KEY_D) -> linux:32 (KEY_D) -> qnum:32 */
0x21, /* linux:33 (KEY_F) -> linux:33 (KEY_F) -> qnum:33 */
0x22, /* linux:34 (KEY_G) -> linux:34 (KEY_G) -> qnum:34 */
0x23, /* linux:35 (KEY_H) -> linux:35 (KEY_H) -> qnum:35 */
0x24, /* linux:36 (KEY_J) -> linux:36 (KEY_J) -> qnum:36 */
0x25, /* linux:37 (KEY_K) -> linux:37 (KEY_K) -> qnum:37 */
0x26, /* linux:38 (KEY_L) -> linux:38 (KEY_L) -> qnum:38 */
0x27, /* linux:39 (KEY_SEMICOLON) -> linux:39 (KEY_SEMICOLON) -> qnum:39 */
0x28, /* linux:40 (KEY_APOSTROPHE) -> linux:40 (KEY_APOSTROPHE) -> qnum:40 */
0x29, /* linux:41 (KEY_GRAVE) -> linux:41 (KEY_GRAVE) -> qnum:41 */
0x2a, /* linux:42 (KEY_LEFTSHIFT) -> linux:42 (KEY_LEFTSHIFT) -> qnum:42 */
0x2b, /* linux:43 (KEY_BACKSLASH) -> linux:43 (KEY_BACKSLASH) -> qnum:43 */
0x2c, /* linux:44 (KEY_Z) -> linux:44 (KEY_Z) -> qnum:44 */
0x2d, /* linux:45 (KEY_X) -> linux:45 (KEY_X) -> qnum:45 */
0x2e, /* linux:46 (KEY_C) -> linux:46 (KEY_C) -> qnum:46 */
0x2f, /* linux:47 (KEY_V) -> linux:47 (KEY_V) -> qnum:47 */
0x30, /* linux:48 (KEY_B) -> linux:48 (KEY_B) -> qnum:48 */
0x31, /* linux:49 (KEY_N) -> linux:49 (KEY_N) -> qnum:49 */
0x32, /* linux:50 (KEY_M) -> linux:50 (KEY_M) -> qnum:50 */
0x33, /* linux:51 (KEY_COMMA) -> linux:51 (KEY_COMMA) -> qnum:51 */
0x34, /* linux:52 (KEY_DOT) -> linux:52 (KEY_DOT) -> qnum:52 */
0x35, /* linux:53 (KEY_SLASH) -> linux:53 (KEY_SLASH) -> qnum:53 */
0x36, /* linux:54 (KEY_RIGHTSHIFT) -> linux:54 (KEY_RIGHTSHIFT) -> qnum:54 */
0x37, /* linux:55 (KEY_KPASTERISK) -> linux:55 (KEY_KPASTERISK) -> qnum:55 */
0x38, /* linux:56 (KEY_LEFTALT) -> linux:56 (KEY_LEFTALT) -> qnum:56 */
0x39, /* linux:57 (KEY_SPACE) -> linux:57 (KEY_SPACE) -> qnum:57 */
0x3a, /* linux:58 (KEY_CAPSLOCK) -> linux:58 (KEY_CAPSLOCK) -> qnum:58 */
0x3b, /* linux:59 (KEY_F1) -> linux:59 (KEY_F1) -> qnum:59 */
0x3c, /* linux:60 (KEY_F2) -> linux:60 (KEY_F2) -> qnum:60 */
0x3d, /* linux:61 (KEY_F3) -> linux:61 (KEY_F3) -> qnum:61 */
0x3e, /* linux:62 (KEY_F4) -> linux:62 (KEY_F4) -> qnum:62 */
0x3f, /* linux:63 (KEY_F5) -> linux:63 (KEY_F5) -> qnum:63 */
0x40, /* linux:64 (KEY_F6) -> linux:64 (KEY_F6) -> qnum:64 */
0x41, /* linux:65 (KEY_F7) -> linux:65 (KEY_F7) -> qnum:65 */
0x42, /* linux:66 (KEY_F8) -> linux:66 (KEY_F8) -> qnum:66 */
0x43, /* linux:67 (KEY_F9) -> linux:67 (KEY_F9) -> qnum:67 */
0x44, /* linux:68 (KEY_F10) -> linux:68 (KEY_F10) -> qnum:68 */
0x45, /* linux:69 (KEY_NUMLOCK) -> linux:69 (KEY_NUMLOCK) -> qnum:69 */
0x46, /* linux:70 (KEY_SCROLLLOCK) -> linux:70 (KEY_SCROLLLOCK) -> qnum:70 */
0x47, /* linux:71 (KEY_KP7) -> linux:71 (KEY_KP7) -> qnum:71 */
0x48, /* linux:72 (KEY_KP8) -> linux:72 (KEY_KP8) -> qnum:72 */
0x49, /* linux:73 (KEY_KP9) -> linux:73 (KEY_KP9) -> qnum:73 */
0x4a, /* linux:74 (KEY_KPMINUS) -> linux:74 (KEY_KPMINUS) -> qnum:74 */
0x4b, /* linux:75 (KEY_KP4) -> linux:75 (KEY_KP4) -> qnum:75 */
0x4c, /* linux:76 (KEY_KP5) -> linux:76 (KEY_KP5) -> qnum:76 */
0x4d, /* linux:77 (KEY_KP6) -> linux:77 (KEY_KP6) -> qnum:77 */
0x4e, /* linux:78 (KEY_KPPLUS) -> linux:78 (KEY_KPPLUS) -> qnum:78 */
0x4f, /* linux:79 (KEY_KP1) -> linux:79 (KEY_KP1) -> qnum:79 */
0x50, /* linux:80 (KEY_KP2) -> linux:80 (KEY_KP2) -> qnum:80 */
0x51, /* linux:81 (KEY_KP3) -> linux:81 (KEY_KP3) -> qnum:81 */
0x52, /* linux:82 (KEY_KP0) -> linux:82 (KEY_KP0) -> qnum:82 */
0x53, /* linux:83 (KEY_KPDOT) -> linux:83 (KEY_KPDOT) -> qnum:83 */
0x54, /* linux:84 (unnamed) -> linux:84 (unnamed) -> qnum:84 */
0x76, /* linux:85 (KEY_ZENKAKUHANKAKU) -> linux:85 (KEY_ZENKAKUHANKAKU) -> qnum:118 */
0x56, /* linux:86 (KEY_102ND) -> linux:86 (KEY_102ND) -> qnum:86 */
0x57, /* linux:87 (KEY_F11) -> linux:87 (KEY_F11) -> qnum:87 */
0x58, /* linux:88 (KEY_F12) -> linux:88 (KEY_F12) -> qnum:88 */
0x73, /* linux:89 (KEY_RO) -> linux:89 (KEY_RO) -> qnum:115 */
0x78, /* linux:90 (KEY_KATAKANA) -> linux:90 (KEY_KATAKANA) -> qnum:120 */
0x77, /* linux:91 (KEY_HIRAGANA) -> linux:91 (KEY_HIRAGANA) -> qnum:119 */
0x79, /* linux:92 (KEY_HENKAN) -> linux:92 (KEY_HENKAN) -> qnum:121 */
0x70, /* linux:93 (KEY_KATAKANAHIRAGANA) -> linux:93 (KEY_KATAKANAHIRAGANA) -> qnum:112 */
0x7b, /* linux:94 (KEY_MUHENKAN) -> linux:94 (KEY_MUHENKAN) -> qnum:123 */
0x5c, /* linux:95 (KEY_KPJPCOMMA) -> linux:95 (KEY_KPJPCOMMA) -> qnum:92 */
0x9c, /* linux:96 (KEY_KPENTER) -> linux:96 (KEY_KPENTER) -> qnum:156 */
0x9d, /* linux:97 (KEY_RIGHTCTRL) -> linux:97 (KEY_RIGHTCTRL) -> qnum:157 */
0xb5, /* linux:98 (KEY_KPSLASH) -> linux:98 (KEY_KPSLASH) -> qnum:181 */
0x54, /* linux:99 (KEY_SYSRQ) -> linux:99 (KEY_SYSRQ) -> qnum:84 */
0xb8, /* linux:100 (KEY_RIGHTALT) -> linux:100 (KEY_RIGHTALT) -> qnum:184 */
0x5b, /* linux:101 (KEY_LINEFEED) -> linux:101 (KEY_LINEFEED) -> qnum:91 */
0xc7, /* linux:102 (KEY_HOME) -> linux:102 (KEY_HOME) -> qnum:199 */
0xc8, /* linux:103 (KEY_UP) -> linux:103 (KEY_UP) -> qnum:200 */
0xc9, /* linux:104 (KEY_PAGEUP) -> linux:104 (KEY_PAGEUP) -> qnum:201 */
0xcb, /* linux:105 (KEY_LEFT) -> linux:105 (KEY_LEFT) -> qnum:203 */
0xcd, /* linux:106 (KEY_RIGHT) -> linux:106 (KEY_RIGHT) -> qnum:205 */
0xcf, /* linux:107 (KEY_END) -> linux:107 (KEY_END) -> qnum:207 */
0xd0, /* linux:108 (KEY_DOWN) -> linux:108 (KEY_DOWN) -> qnum:208 */
0xd1, /* linux:109 (KEY_PAGEDOWN) -> linux:109 (KEY_PAGEDOWN) -> qnum:209 */
0xd2, /* linux:110 (KEY_INSERT) -> linux:110 (KEY_INSERT) -> qnum:210 */
0xd3, /* linux:111 (KEY_DELETE) -> linux:111 (KEY_DELETE) -> qnum:211 */
0xef, /* linux:112 (KEY_MACRO) -> linux:112 (KEY_MACRO) -> qnum:239 */
0xa0, /* linux:113 (KEY_MUTE) -> linux:113 (KEY_MUTE) -> qnum:160 */
0xae, /* linux:114 (KEY_VOLUMEDOWN) -> linux:114 (KEY_VOLUMEDOWN) -> qnum:174 */
0xb0, /* linux:115 (KEY_VOLUMEUP) -> linux:115 (KEY_VOLUMEUP) -> qnum:176 */
0xde, /* linux:116 (KEY_POWER) -> linux:116 (KEY_POWER) -> qnum:222 */
0x59, /* linux:117 (KEY_KPEQUAL) -> linux:117 (KEY_KPEQUAL) -> qnum:89 */
0xce, /* linux:118 (KEY_KPPLUSMINUS) -> linux:118 (KEY_KPPLUSMINUS) -> qnum:206 */
0xc6, /* linux:119 (KEY_PAUSE) -> linux:119 (KEY_PAUSE) -> qnum:198 */
0x8b, /* linux:120 (KEY_SCALE) -> linux:120 (KEY_SCALE) -> qnum:139 */
0x7e, /* linux:121 (KEY_KPCOMMA) -> linux:121 (KEY_KPCOMMA) -> qnum:126 */
0x72, /* linux:122 (KEY_HANGEUL) -> linux:122 (KEY_HANGEUL) -> qnum:114 */
0x71, /* linux:123 (KEY_HANJA) -> linux:123 (KEY_HANJA) -> qnum:113 */
0x7d, /* linux:124 (KEY_YEN) -> linux:124 (KEY_YEN) -> qnum:125 */
0xdb, /* linux:125 (KEY_LEFTMETA) -> linux:125 (KEY_LEFTMETA) -> qnum:219 */
0xdc, /* linux:126 (KEY_RIGHTMETA) -> linux:126 (KEY_RIGHTMETA) -> qnum:220 */
0xdd, /* linux:127 (KEY_COMPOSE) -> linux:127 (KEY_COMPOSE) -> qnum:221 */
0xe8, /* linux:128 (KEY_STOP) -> linux:128 (KEY_STOP) -> qnum:232 */
0x85, /* linux:129 (KEY_AGAIN) -> linux:129 (KEY_AGAIN) -> qnum:133 */
0x86, /* linux:130 (KEY_PROPS) -> linux:130 (KEY_PROPS) -> qnum:134 */
0x87, /* linux:131 (KEY_UNDO) -> linux:131 (KEY_UNDO) -> qnum:135 */
0x8c, /* linux:132 (KEY_FRONT) -> linux:132 (KEY_FRONT) -> qnum:140 */
0xf8, /* linux:133 (KEY_COPY) -> linux:133 (KEY_COPY) -> qnum:248 */
0x64, /* linux:134 (KEY_OPEN) -> linux:134 (KEY_OPEN) -> qnum:100 */
0x65, /* linux:135 (KEY_PASTE) -> linux:135 (KEY_PASTE) -> qnum:101 */
0xc1, /* linux:136 (KEY_FIND) -> linux:136 (KEY_FIND) -> qnum:193 */
0xbc, /* linux:137 (KEY_CUT) -> linux:137 (KEY_CUT) -> qnum:188 */
0xf5, /* linux:138 (KEY_HELP) -> linux:138 (KEY_HELP) -> qnum:245 */
0x9e, /* linux:139 (KEY_MENU) -> linux:139 (KEY_MENU) -> qnum:158 */
0xa1, /* linux:140 (KEY_CALC) -> linux:140 (KEY_CALC) -> qnum:161 */
0x66, /* linux:141 (KEY_SETUP) -> linux:141 (KEY_SETUP) -> qnum:102 */
0xdf, /* linux:142 (KEY_SLEEP) -> linux:142 (KEY_SLEEP) -> qnum:223 */
0xe3, /* linux:143 (KEY_WAKEUP) -> linux:143 (KEY_WAKEUP) -> qnum:227 */
0x67, /* linux:144 (KEY_FILE) -> linux:144 (KEY_FILE) -> qnum:103 */
0x68, /* linux:145 (KEY_SENDFILE) -> linux:145 (KEY_SENDFILE) -> qnum:104 */
0x69, /* linux:146 (KEY_DELETEFILE) -> linux:146 (KEY_DELETEFILE) -> qnum:105 */
0x93, /* linux:147 (KEY_XFER) -> linux:147 (KEY_XFER) -> qnum:147 */
0x9f, /* linux:148 (KEY_PROG1) -> linux:148 (KEY_PROG1) -> qnum:159 */
0x97, /* linux:149 (KEY_PROG2) -> linux:149 (KEY_PROG2) -> qnum:151 */
0x82, /* linux:150 (KEY_WWW) -> linux:150 (KEY_WWW) -> qnum:130 */
0x6a, /* linux:151 (KEY_MSDOS) -> linux:151 (KEY_MSDOS) -> qnum:106 */
0x92, /* linux:152 (KEY_SCREENLOCK) -> linux:152 (KEY_SCREENLOCK) -> qnum:146 */
0x6b, /* linux:153 (KEY_DIRECTION) -> linux:153 (KEY_DIRECTION) -> qnum:107 */
0xa6, /* linux:154 (KEY_CYCLEWINDOWS) -> linux:154 (KEY_CYCLEWINDOWS) -> qnum:166 */
0xec, /* linux:155 (KEY_MAIL) -> linux:155 (KEY_MAIL) -> qnum:236 */
0xe6, /* linux:156 (KEY_BOOKMARKS) -> linux:156 (KEY_BOOKMARKS) -> qnum:230 */
0xeb, /* linux:157 (KEY_COMPUTER) -> linux:157 (KEY_COMPUTER) -> qnum:235 */
0xea, /* linux:158 (KEY_BACK) -> linux:158 (KEY_BACK) -> qnum:234 */
0xe9, /* linux:159 (KEY_FORWARD) -> linux:159 (KEY_FORWARD) -> qnum:233 */
0xa3, /* linux:160 (KEY_CLOSECD) -> linux:160 (KEY_CLOSECD) -> qnum:163 */
0x6c, /* linux:161 (KEY_EJECTCD) -> linux:161 (KEY_EJECTCD) -> qnum:108 */
0xfd, /* linux:162 (KEY_EJECTCLOSECD) -> linux:162 (KEY_EJECTCLOSECD) -> qnum:253 */
0x99, /* linux:163 (KEY_NEXTSONG) -> linux:163 (KEY_NEXTSONG) -> qnum:153 */
0xa2, /* linux:164 (KEY_PLAYPAUSE) -> linux:164 (KEY_PLAYPAUSE) -> qnum:162 */
0x90, /* linux:165 (KEY_PREVIOUSSONG) -> linux:165 (KEY_PREVIOUSSONG) -> qnum:144 */
0xa4, /* linux:166 (KEY_STOPCD) -> linux:166 (KEY_STOPCD) -> qnum:164 */
0xb1, /* linux:167 (KEY_RECORD) -> linux:167 (KEY_RECORD) -> qnum:177 */
0x98, /* linux:168 (KEY_REWIND) -> linux:168 (KEY_REWIND) -> qnum:152 */
0x63, /* linux:169 (KEY_PHONE) -> linux:169 (KEY_PHONE) -> qnum:99 */
0, /* linux:170 (KEY_ISO) -> linux:170 (KEY_ISO) -> qnum:None */
0x81, /* linux:171 (KEY_CONFIG) -> linux:171 (KEY_CONFIG) -> qnum:129 */
0xb2, /* linux:172 (KEY_HOMEPAGE) -> linux:172 (KEY_HOMEPAGE) -> qnum:178 */
0xe7, /* linux:173 (KEY_REFRESH) -> linux:173 (KEY_REFRESH) -> qnum:231 */
0, /* linux:174 (KEY_EXIT) -> linux:174 (KEY_EXIT) -> qnum:None */
0, /* linux:175 (KEY_MOVE) -> linux:175 (KEY_MOVE) -> qnum:None */
0x88, /* linux:176 (KEY_EDIT) -> linux:176 (KEY_EDIT) -> qnum:136 */
0x75, /* linux:177 (KEY_SCROLLUP) -> linux:177 (KEY_SCROLLUP) -> qnum:117 */
0x8f, /* linux:178 (KEY_SCROLLDOWN) -> linux:178 (KEY_SCROLLDOWN) -> qnum:143 */
0xf6, /* linux:179 (KEY_KPLEFTPAREN) -> linux:179 (KEY_KPLEFTPAREN) -> qnum:246 */
0xfb, /* linux:180 (KEY_KPRIGHTPAREN) -> linux:180 (KEY_KPRIGHTPAREN) -> qnum:251 */
0x89, /* linux:181 (KEY_NEW) -> linux:181 (KEY_NEW) -> qnum:137 */
0x8a, /* linux:182 (KEY_REDO) -> linux:182 (KEY_REDO) -> qnum:138 */
0x5d, /* linux:183 (KEY_F13) -> linux:183 (KEY_F13) -> qnum:93 */
0x5e, /* linux:184 (KEY_F14) -> linux:184 (KEY_F14) -> qnum:94 */
0x5f, /* linux:185 (KEY_F15) -> linux:185 (KEY_F15) -> qnum:95 */
0x55, /* linux:186 (KEY_F16) -> linux:186 (KEY_F16) -> qnum:85 */
0x83, /* linux:187 (KEY_F17) -> linux:187 (KEY_F17) -> qnum:131 */
0xf7, /* linux:188 (KEY_F18) -> linux:188 (KEY_F18) -> qnum:247 */
0x84, /* linux:189 (KEY_F19) -> linux:189 (KEY_F19) -> qnum:132 */
0x5a, /* linux:190 (KEY_F20) -> linux:190 (KEY_F20) -> qnum:90 */
0x74, /* linux:191 (KEY_F21) -> linux:191 (KEY_F21) -> qnum:116 */
0xf9, /* linux:192 (KEY_F22) -> linux:192 (KEY_F22) -> qnum:249 */
0x6d, /* linux:193 (KEY_F23) -> linux:193 (KEY_F23) -> qnum:109 */
0x6f, /* linux:194 (KEY_F24) -> linux:194 (KEY_F24) -> qnum:111 */
0x95, /* linux:195 (unnamed) -> linux:195 (unnamed) -> qnum:149 */
0x96, /* linux:196 (unnamed) -> linux:196 (unnamed) -> qnum:150 */
0x9a, /* linux:197 (unnamed) -> linux:197 (unnamed) -> qnum:154 */
0x9b, /* linux:198 (unnamed) -> linux:198 (unnamed) -> qnum:155 */
0xa7, /* linux:199 (unnamed) -> linux:199 (unnamed) -> qnum:167 */
0xa8, /* linux:200 (KEY_PLAYCD) -> linux:200 (KEY_PLAYCD) -> qnum:168 */
0xa9, /* linux:201 (KEY_PAUSECD) -> linux:201 (KEY_PAUSECD) -> qnum:169 */
0xab, /* linux:202 (KEY_PROG3) -> linux:202 (KEY_PROG3) -> qnum:171 */
0xac, /* linux:203 (KEY_PROG4) -> linux:203 (KEY_PROG4) -> qnum:172 */
0xad, /* linux:204 (KEY_DASHBOARD) -> linux:204 (KEY_DASHBOARD) -> qnum:173 */
0xa5, /* linux:205 (KEY_SUSPEND) -> linux:205 (KEY_SUSPEND) -> qnum:165 */
0xaf, /* linux:206 (KEY_CLOSE) -> linux:206 (KEY_CLOSE) -> qnum:175 */
0xb3, /* linux:207 (KEY_PLAY) -> linux:207 (KEY_PLAY) -> qnum:179 */
0xb4, /* linux:208 (KEY_FASTFORWARD) -> linux:208 (KEY_FASTFORWARD) -> qnum:180 */
0xb6, /* linux:209 (KEY_BASSBOOST) -> linux:209 (KEY_BASSBOOST) -> qnum:182 */
0xb9, /* linux:210 (KEY_PRINT) -> linux:210 (KEY_PRINT) -> qnum:185 */
0xba, /* linux:211 (KEY_HP) -> linux:211 (KEY_HP) -> qnum:186 */
0xbb, /* linux:212 (KEY_CAMERA) -> linux:212 (KEY_CAMERA) -> qnum:187 */
0xbd, /* linux:213 (KEY_SOUND) -> linux:213 (KEY_SOUND) -> qnum:189 */
0xbe, /* linux:214 (KEY_QUESTION) -> linux:214 (KEY_QUESTION) -> qnum:190 */
0xbf, /* linux:215 (KEY_EMAIL) -> linux:215 (KEY_EMAIL) -> qnum:191 */
0xc0, /* linux:216 (KEY_CHAT) -> linux:216 (KEY_CHAT) -> qnum:192 */
0xe5, /* linux:217 (KEY_SEARCH) -> linux:217 (KEY_SEARCH) -> qnum:229 */
0xc2, /* linux:218 (KEY_CONNECT) -> linux:218 (KEY_CONNECT) -> qnum:194 */
0xc3, /* linux:219 (KEY_FINANCE) -> linux:219 (KEY_FINANCE) -> qnum:195 */
0xc4, /* linux:220 (KEY_SPORT) -> linux:220 (KEY_SPORT) -> qnum:196 */
0xc5, /* linux:221 (KEY_SHOP) -> linux:221 (KEY_SHOP) -> qnum:197 */
0x94, /* linux:222 (KEY_ALTERASE) -> linux:222 (KEY_ALTERASE) -> qnum:148 */
0xca, /* linux:223 (KEY_CANCEL) -> linux:223 (KEY_CANCEL) -> qnum:202 */
0xcc, /* linux:224 (KEY_BRIGHTNESSDOWN) -> linux:224 (KEY_BRIGHTNESSDOWN) -> qnum:204 */
0xd4, /* linux:225 (KEY_BRIGHTNESSUP) -> linux:225 (KEY_BRIGHTNESSUP) -> qnum:212 */
0xed, /* linux:226 (KEY_MEDIA) -> linux:226 (KEY_MEDIA) -> qnum:237 */
0xd6, /* linux:227 (KEY_SWITCHVIDEOMODE) -> linux:227 (KEY_SWITCHVIDEOMODE) -> qnum:214 */
0xd7, /* linux:228 (KEY_KBDILLUMTOGGLE) -> linux:228 (KEY_KBDILLUMTOGGLE) -> qnum:215 */
0xd8, /* linux:229 (KEY_KBDILLUMDOWN) -> linux:229 (KEY_KBDILLUMDOWN) -> qnum:216 */
0xd9, /* linux:230 (KEY_KBDILLUMUP) -> linux:230 (KEY_KBDILLUMUP) -> qnum:217 */
0xda, /* linux:231 (KEY_SEND) -> linux:231 (KEY_SEND) -> qnum:218 */
0xe4, /* linux:232 (KEY_REPLY) -> linux:232 (KEY_REPLY) -> qnum:228 */
0x8e, /* linux:233 (KEY_FORWARDMAIL) -> linux:233 (KEY_FORWARDMAIL) -> qnum:142 */
0xd5, /* linux:234 (KEY_SAVE) -> linux:234 (KEY_SAVE) -> qnum:213 */
0xf0, /* linux:235 (KEY_DOCUMENTS) -> linux:235 (KEY_DOCUMENTS) -> qnum:240 */
0xf1, /* linux:236 (KEY_BATTERY) -> linux:236 (KEY_BATTERY) -> qnum:241 */
0xf2, /* linux:237 (KEY_BLUETOOTH) -> linux:237 (KEY_BLUETOOTH) -> qnum:242 */
0xf3, /* linux:238 (KEY_WLAN) -> linux:238 (KEY_WLAN) -> qnum:243 */
0xf4, /* linux:239 (KEY_UWB) -> linux:239 (KEY_UWB) -> qnum:244 */
0, /* linux:240 (KEY_UNKNOWN) -> linux:240 (KEY_UNKNOWN) -> qnum:None */
0, /* linux:241 (KEY_VIDEO_NEXT) -> linux:241 (KEY_VIDEO_NEXT) -> qnum:None */
0, /* linux:242 (KEY_VIDEO_PREV) -> linux:242 (KEY_VIDEO_PREV) -> qnum:None */
0, /* linux:243 (KEY_BRIGHTNESS_CYCLE) -> linux:243 (KEY_BRIGHTNESS_CYCLE) -> qnum:None */
0, /* linux:244 (KEY_BRIGHTNESS_ZERO) -> linux:244 (KEY_BRIGHTNESS_ZERO) -> qnum:None */
0, /* linux:245 (KEY_DISPLAY_OFF) -> linux:245 (KEY_DISPLAY_OFF) -> qnum:None */
0, /* linux:246 (KEY_WIMAX) -> linux:246 (KEY_WIMAX) -> qnum:None */
0, /* linux:247 (unnamed) -> linux:247 (unnamed) -> qnum:None */
0, /* linux:248 (unnamed) -> linux:248 (unnamed) -> qnum:None */
0, /* linux:249 (unnamed) -> linux:249 (unnamed) -> qnum:None */
0, /* linux:250 (unnamed) -> linux:250 (unnamed) -> qnum:None */
0, /* linux:251 (unnamed) -> linux:251 (unnamed) -> qnum:None */
0, /* linux:252 (unnamed) -> linux:252 (unnamed) -> qnum:None */
0, /* linux:253 (unnamed) -> linux:253 (unnamed) -> qnum:None */
0, /* linux:254 (unnamed) -> linux:254 (unnamed) -> qnum:None */
0, /* linux:255 (unnamed) -> linux:255 (unnamed) -> qnum:None */
0, /* linux:256 (BTN_0) -> linux:256 (BTN_0) -> qnum:None */
0, /* linux:257 (BTN_1) -> linux:257 (BTN_1) -> qnum:None */
0, /* linux:258 (BTN_2) -> linux:258 (BTN_2) -> qnum:None */
0, /* linux:259 (BTN_3) -> linux:259 (BTN_3) -> qnum:None */
0, /* linux:260 (BTN_4) -> linux:260 (BTN_4) -> qnum:None */
0, /* linux:261 (BTN_5) -> linux:261 (BTN_5) -> qnum:None */
0, /* linux:262 (BTN_6) -> linux:262 (BTN_6) -> qnum:None */
0, /* linux:263 (BTN_7) -> linux:263 (BTN_7) -> qnum:None */
0, /* linux:264 (BTN_8) -> linux:264 (BTN_8) -> qnum:None */
0, /* linux:265 (BTN_9) -> linux:265 (BTN_9) -> qnum:None */
0, /* linux:266 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:267 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:268 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:269 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:270 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:271 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:272 (BTN_LEFT) -> linux:272 (BTN_LEFT) -> qnum:None */
0, /* linux:273 (BTN_RIGHT) -> linux:273 (BTN_RIGHT) -> qnum:None */
0, /* linux:274 (BTN_MIDDLE) -> linux:274 (BTN_MIDDLE) -> qnum:None */
0, /* linux:275 (BTN_SIDE) -> linux:275 (BTN_SIDE) -> qnum:None */
0, /* linux:276 (BTN_EXTRA) -> linux:276 (BTN_EXTRA) -> qnum:None */
0, /* linux:277 (BTN_FORWARD) -> linux:277 (BTN_FORWARD) -> qnum:None */
0, /* linux:278 (BTN_BACK) -> linux:278 (BTN_BACK) -> qnum:None */
0, /* linux:279 (BTN_TASK) -> linux:279 (BTN_TASK) -> qnum:None */
0, /* linux:280 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:281 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:282 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:283 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:284 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:285 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:286 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:287 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:288 (BTN_TRIGGER) -> linux:288 (BTN_TRIGGER) -> qnum:None */
0, /* linux:289 (BTN_THUMB) -> linux:289 (BTN_THUMB) -> qnum:None */
0, /* linux:290 (BTN_THUMB2) -> linux:290 (BTN_THUMB2) -> qnum:None */
0, /* linux:291 (BTN_TOP) -> linux:291 (BTN_TOP) -> qnum:None */
0, /* linux:292 (BTN_TOP2) -> linux:292 (BTN_TOP2) -> qnum:None */
0, /* linux:293 (BTN_PINKIE) -> linux:293 (BTN_PINKIE) -> qnum:None */
0, /* linux:294 (BTN_BASE) -> linux:294 (BTN_BASE) -> qnum:None */
0, /* linux:295 (BTN_BASE2) -> linux:295 (BTN_BASE2) -> qnum:None */
0, /* linux:296 (BTN_BASE3) -> linux:296 (BTN_BASE3) -> qnum:None */
0, /* linux:297 (BTN_BASE4) -> linux:297 (BTN_BASE4) -> qnum:None */
0, /* linux:298 (BTN_BASE5) -> linux:298 (BTN_BASE5) -> qnum:None */
0, /* linux:299 (BTN_BASE6) -> linux:299 (BTN_BASE6) -> qnum:None */
0, /* linux:300 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:301 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:302 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:303 (BTN_DEAD) -> linux:303 (BTN_DEAD) -> qnum:None */
0, /* linux:304 (BTN_A) -> linux:304 (BTN_A) -> qnum:None */
0, /* linux:305 (BTN_B) -> linux:305 (BTN_B) -> qnum:None */
0, /* linux:306 (BTN_C) -> linux:306 (BTN_C) -> qnum:None */
0, /* linux:307 (BTN_X) -> linux:307 (BTN_X) -> qnum:None */
0, /* linux:308 (BTN_Y) -> linux:308 (BTN_Y) -> qnum:None */
0, /* linux:309 (BTN_Z) -> linux:309 (BTN_Z) -> qnum:None */
0, /* linux:310 (BTN_TL) -> linux:310 (BTN_TL) -> qnum:None */
0, /* linux:311 (BTN_TR) -> linux:311 (BTN_TR) -> qnum:None */
0, /* linux:312 (BTN_TL2) -> linux:312 (BTN_TL2) -> qnum:None */
0, /* linux:313 (BTN_TR2) -> linux:313 (BTN_TR2) -> qnum:None */
0, /* linux:314 (BTN_SELECT) -> linux:314 (BTN_SELECT) -> qnum:None */
0, /* linux:315 (BTN_START) -> linux:315 (BTN_START) -> qnum:None */
0, /* linux:316 (BTN_MODE) -> linux:316 (BTN_MODE) -> qnum:None */
0, /* linux:317 (BTN_THUMBL) -> linux:317 (BTN_THUMBL) -> qnum:None */
0, /* linux:318 (BTN_THUMBR) -> linux:318 (BTN_THUMBR) -> qnum:None */
0, /* linux:319 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:320 (BTN_TOOL_PEN) -> linux:320 (BTN_TOOL_PEN) -> qnum:None */
0, /* linux:321 (BTN_TOOL_RUBBER) -> linux:321 (BTN_TOOL_RUBBER) -> qnum:None */
0, /* linux:322 (BTN_TOOL_BRUSH) -> linux:322 (BTN_TOOL_BRUSH) -> qnum:None */
0, /* linux:323 (BTN_TOOL_PENCIL) -> linux:323 (BTN_TOOL_PENCIL) -> qnum:None */
0, /* linux:324 (BTN_TOOL_AIRBRUSH) -> linux:324 (BTN_TOOL_AIRBRUSH) -> qnum:None */
0, /* linux:325 (BTN_TOOL_FINGER) -> linux:325 (BTN_TOOL_FINGER) -> qnum:None */
0, /* linux:326 (BTN_TOOL_MOUSE) -> linux:326 (BTN_TOOL_MOUSE) -> qnum:None */
0, /* linux:327 (BTN_TOOL_LENS) -> linux:327 (BTN_TOOL_LENS) -> qnum:None */
0, /* linux:328 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:329 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:330 (BTN_TOUCH) -> linux:330 (BTN_TOUCH) -> qnum:None */
0, /* linux:331 (BTN_STYLUS) -> linux:331 (BTN_STYLUS) -> qnum:None */
0, /* linux:332 (BTN_STYLUS2) -> linux:332 (BTN_STYLUS2) -> qnum:None */
0, /* linux:333 (BTN_TOOL_DOUBLETAP) -> linux:333 (BTN_TOOL_DOUBLETAP) -> qnum:None */
0, /* linux:334 (BTN_TOOL_TRIPLETAP) -> linux:334 (BTN_TOOL_TRIPLETAP) -> qnum:None */
0, /* linux:335 (BTN_TOOL_QUADTAP) -> linux:335 (BTN_TOOL_QUADTAP) -> qnum:None */
0, /* linux:336 (BTN_GEAR_DOWN) -> linux:336 (BTN_GEAR_DOWN) -> qnum:None */
0, /* linux:337 (BTN_GEAR_UP) -> linux:337 (BTN_GEAR_UP) -> qnum:None */
0, /* linux:338 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:339 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:340 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:341 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:342 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:343 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:344 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:345 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:346 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:347 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:348 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:349 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:350 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:351 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:352 (KEY_OK) -> linux:352 (KEY_OK) -> qnum:None */
0, /* linux:353 (KEY_SELECT) -> linux:353 (KEY_SELECT) -> qnum:None */
0, /* linux:354 (KEY_GOTO) -> linux:354 (KEY_GOTO) -> qnum:None */
0, /* linux:355 (KEY_CLEAR) -> linux:355 (KEY_CLEAR) -> qnum:None */
0, /* linux:356 (KEY_POWER2) -> linux:356 (KEY_POWER2) -> qnum:None */
0, /* linux:357 (KEY_OPTION) -> linux:357 (KEY_OPTION) -> qnum:None */
0, /* linux:358 (KEY_INFO) -> linux:358 (KEY_INFO) -> qnum:None */
0, /* linux:359 (KEY_TIME) -> linux:359 (KEY_TIME) -> qnum:None */
0, /* linux:360 (KEY_VENDOR) -> linux:360 (KEY_VENDOR) -> qnum:None */
0, /* linux:361 (KEY_ARCHIVE) -> linux:361 (KEY_ARCHIVE) -> qnum:None */
0, /* linux:362 (KEY_PROGRAM) -> linux:362 (KEY_PROGRAM) -> qnum:None */
0, /* linux:363 (KEY_CHANNEL) -> linux:363 (KEY_CHANNEL) -> qnum:None */
0, /* linux:364 (KEY_FAVORITES) -> linux:364 (KEY_FAVORITES) -> qnum:None */
0, /* linux:365 (KEY_EPG) -> linux:365 (KEY_EPG) -> qnum:None */
0, /* linux:366 (KEY_PVR) -> linux:366 (KEY_PVR) -> qnum:None */
0, /* linux:367 (KEY_MHP) -> linux:367 (KEY_MHP) -> qnum:None */
0, /* linux:368 (KEY_LANGUAGE) -> linux:368 (KEY_LANGUAGE) -> qnum:None */
0, /* linux:369 (KEY_TITLE) -> linux:369 (KEY_TITLE) -> qnum:None */
0, /* linux:370 (KEY_SUBTITLE) -> linux:370 (KEY_SUBTITLE) -> qnum:None */
0, /* linux:371 (KEY_ANGLE) -> linux:371 (KEY_ANGLE) -> qnum:None */
0, /* linux:372 (KEY_ZOOM) -> linux:372 (KEY_ZOOM) -> qnum:None */
0, /* linux:373 (KEY_MODE) -> linux:373 (KEY_MODE) -> qnum:None */
0, /* linux:374 (KEY_KEYBOARD) -> linux:374 (KEY_KEYBOARD) -> qnum:None */
0, /* linux:375 (KEY_SCREEN) -> linux:375 (KEY_SCREEN) -> qnum:None */
0, /* linux:376 (KEY_PC) -> linux:376 (KEY_PC) -> qnum:None */
0, /* linux:377 (KEY_TV) -> linux:377 (KEY_TV) -> qnum:None */
0, /* linux:378 (KEY_TV2) -> linux:378 (KEY_TV2) -> qnum:None */
0, /* linux:379 (KEY_VCR) -> linux:379 (KEY_VCR) -> qnum:None */
0, /* linux:380 (KEY_VCR2) -> linux:380 (KEY_VCR2) -> qnum:None */
0, /* linux:381 (KEY_SAT) -> linux:381 (KEY_SAT) -> qnum:None */
0, /* linux:382 (KEY_SAT2) -> linux:382 (KEY_SAT2) -> qnum:None */
0, /* linux:383 (KEY_CD) -> linux:383 (KEY_CD) -> qnum:None */
0, /* linux:384 (KEY_TAPE) -> linux:384 (KEY_TAPE) -> qnum:None */
0, /* linux:385 (KEY_RADIO) -> linux:385 (KEY_RADIO) -> qnum:None */
0, /* linux:386 (KEY_TUNER) -> linux:386 (KEY_TUNER) -> qnum:None */
0, /* linux:387 (KEY_PLAYER) -> linux:387 (KEY_PLAYER) -> qnum:None */
0, /* linux:388 (KEY_TEXT) -> linux:388 (KEY_TEXT) -> qnum:None */
0, /* linux:389 (KEY_DVD) -> linux:389 (KEY_DVD) -> qnum:None */
0, /* linux:390 (KEY_AUX) -> linux:390 (KEY_AUX) -> qnum:None */
0, /* linux:391 (KEY_MP3) -> linux:391 (KEY_MP3) -> qnum:None */
0, /* linux:392 (KEY_AUDIO) -> linux:392 (KEY_AUDIO) -> qnum:None */
0, /* linux:393 (KEY_VIDEO) -> linux:393 (KEY_VIDEO) -> qnum:None */
0, /* linux:394 (KEY_DIRECTORY) -> linux:394 (KEY_DIRECTORY) -> qnum:None */
0, /* linux:395 (KEY_LIST) -> linux:395 (KEY_LIST) -> qnum:None */
0, /* linux:396 (KEY_MEMO) -> linux:396 (KEY_MEMO) -> qnum:None */
0, /* linux:397 (KEY_CALENDAR) -> linux:397 (KEY_CALENDAR) -> qnum:None */
0, /* linux:398 (KEY_RED) -> linux:398 (KEY_RED) -> qnum:None */
0, /* linux:399 (KEY_GREEN) -> linux:399 (KEY_GREEN) -> qnum:None */
0, /* linux:400 (KEY_YELLOW) -> linux:400 (KEY_YELLOW) -> qnum:None */
0, /* linux:401 (KEY_BLUE) -> linux:401 (KEY_BLUE) -> qnum:None */
0, /* linux:402 (KEY_CHANNELUP) -> linux:402 (KEY_CHANNELUP) -> qnum:None */
0, /* linux:403 (KEY_CHANNELDOWN) -> linux:403 (KEY_CHANNELDOWN) -> qnum:None */
0, /* linux:404 (KEY_FIRST) -> linux:404 (KEY_FIRST) -> qnum:None */
0, /* linux:405 (KEY_LAST) -> linux:405 (KEY_LAST) -> qnum:None */
0, /* linux:406 (KEY_AB) -> linux:406 (KEY_AB) -> qnum:None */
0, /* linux:407 (KEY_NEXT) -> linux:407 (KEY_NEXT) -> qnum:None */
0, /* linux:408 (KEY_RESTART) -> linux:408 (KEY_RESTART) -> qnum:None */
0, /* linux:409 (KEY_SLOW) -> linux:409 (KEY_SLOW) -> qnum:None */
0, /* linux:410 (KEY_SHUFFLE) -> linux:410 (KEY_SHUFFLE) -> qnum:None */
0, /* linux:411 (KEY_BREAK) -> linux:411 (KEY_BREAK) -> qnum:None */
0, /* linux:412 (KEY_PREVIOUS) -> linux:412 (KEY_PREVIOUS) -> qnum:None */
0, /* linux:413 (KEY_DIGITS) -> linux:413 (KEY_DIGITS) -> qnum:None */
0, /* linux:414 (KEY_TEEN) -> linux:414 (KEY_TEEN) -> qnum:None */
0, /* linux:415 (KEY_TWEN) -> linux:415 (KEY_TWEN) -> qnum:None */
0, /* linux:416 (KEY_VIDEOPHONE) -> linux:416 (KEY_VIDEOPHONE) -> qnum:None */
0, /* linux:417 (KEY_GAMES) -> linux:417 (KEY_GAMES) -> qnum:None */
0, /* linux:418 (KEY_ZOOMIN) -> linux:418 (KEY_ZOOMIN) -> qnum:None */
0, /* linux:419 (KEY_ZOOMOUT) -> linux:419 (KEY_ZOOMOUT) -> qnum:None */
0, /* linux:420 (KEY_ZOOMRESET) -> linux:420 (KEY_ZOOMRESET) -> qnum:None */
0, /* linux:421 (KEY_WORDPROCESSOR) -> linux:421 (KEY_WORDPROCESSOR) -> qnum:None */
0, /* linux:422 (KEY_EDITOR) -> linux:422 (KEY_EDITOR) -> qnum:None */
0, /* linux:423 (KEY_SPREADSHEET) -> linux:423 (KEY_SPREADSHEET) -> qnum:None */
0, /* linux:424 (KEY_GRAPHICSEDITOR) -> linux:424 (KEY_GRAPHICSEDITOR) -> qnum:None */
0, /* linux:425 (KEY_PRESENTATION) -> linux:425 (KEY_PRESENTATION) -> qnum:None */
0, /* linux:426 (KEY_DATABASE) -> linux:426 (KEY_DATABASE) -> qnum:None */
0, /* linux:427 (KEY_NEWS) -> linux:427 (KEY_NEWS) -> qnum:None */
0, /* linux:428 (KEY_VOICEMAIL) -> linux:428 (KEY_VOICEMAIL) -> qnum:None */
0, /* linux:429 (KEY_ADDRESSBOOK) -> linux:429 (KEY_ADDRESSBOOK) -> qnum:None */
0, /* linux:430 (KEY_MESSENGER) -> linux:430 (KEY_MESSENGER) -> qnum:None */
0, /* linux:431 (KEY_DISPLAYTOGGLE) -> linux:431 (KEY_DISPLAYTOGGLE) -> qnum:None */
0, /* linux:432 (KEY_SPELLCHECK) -> linux:432 (KEY_SPELLCHECK) -> qnum:None */
0, /* linux:433 (KEY_LOGOFF) -> linux:433 (KEY_LOGOFF) -> qnum:None */
0, /* linux:434 (KEY_DOLLAR) -> linux:434 (KEY_DOLLAR) -> qnum:None */
0, /* linux:435 (KEY_EURO) -> linux:435 (KEY_EURO) -> qnum:None */
0, /* linux:436 (KEY_FRAMEBACK) -> linux:436 (KEY_FRAMEBACK) -> qnum:None */
0, /* linux:437 (KEY_FRAMEFORWARD) -> linux:437 (KEY_FRAMEFORWARD) -> qnum:None */
0, /* linux:438 (KEY_CONTEXT_MENU) -> linux:438 (KEY_CONTEXT_MENU) -> qnum:None */
0, /* linux:439 (KEY_MEDIA_REPEAT) -> linux:439 (KEY_MEDIA_REPEAT) -> qnum:None */
0, /* linux:440 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:441 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:442 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:443 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:444 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:445 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:446 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:447 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:448 (KEY_DEL_EOL) -> linux:448 (KEY_DEL_EOL) -> qnum:None */
0, /* linux:449 (KEY_DEL_EOS) -> linux:449 (KEY_DEL_EOS) -> qnum:None */
0, /* linux:450 (KEY_INS_LINE) -> linux:450 (KEY_INS_LINE) -> qnum:None */
0, /* linux:451 (KEY_DEL_LINE) -> linux:451 (KEY_DEL_LINE) -> qnum:None */
0, /* linux:452 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:453 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:454 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:455 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:456 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:457 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:458 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:459 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:460 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:461 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:462 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:463 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:464 (KEY_FN) -> linux:464 (KEY_FN) -> qnum:None */
0, /* linux:465 (KEY_FN_ESC) -> linux:465 (KEY_FN_ESC) -> qnum:None */
0, /* linux:466 (KEY_FN_F1) -> linux:466 (KEY_FN_F1) -> qnum:None */
0, /* linux:467 (KEY_FN_F2) -> linux:467 (KEY_FN_F2) -> qnum:None */
0, /* linux:468 (KEY_FN_F3) -> linux:468 (KEY_FN_F3) -> qnum:None */
0, /* linux:469 (KEY_FN_F4) -> linux:469 (KEY_FN_F4) -> qnum:None */
0, /* linux:470 (KEY_FN_F5) -> linux:470 (KEY_FN_F5) -> qnum:None */
0, /* linux:471 (KEY_FN_F6) -> linux:471 (KEY_FN_F6) -> qnum:None */
0, /* linux:472 (KEY_FN_F7) -> linux:472 (KEY_FN_F7) -> qnum:None */
0, /* linux:473 (KEY_FN_F8) -> linux:473 (KEY_FN_F8) -> qnum:None */
0, /* linux:474 (KEY_FN_F9) -> linux:474 (KEY_FN_F9) -> qnum:None */
0, /* linux:475 (KEY_FN_F10) -> linux:475 (KEY_FN_F10) -> qnum:None */
0, /* linux:476 (KEY_FN_F11) -> linux:476 (KEY_FN_F11) -> qnum:None */
0, /* linux:477 (KEY_FN_F12) -> linux:477 (KEY_FN_F12) -> qnum:None */
0, /* linux:478 (KEY_FN_1) -> linux:478 (KEY_FN_1) -> qnum:None */
0, /* linux:479 (KEY_FN_2) -> linux:479 (KEY_FN_2) -> qnum:None */
0, /* linux:480 (KEY_FN_D) -> linux:480 (KEY_FN_D) -> qnum:None */
0, /* linux:481 (KEY_FN_E) -> linux:481 (KEY_FN_E) -> qnum:None */
0, /* linux:482 (KEY_FN_F) -> linux:482 (KEY_FN_F) -> qnum:None */
0, /* linux:483 (KEY_FN_S) -> linux:483 (KEY_FN_S) -> qnum:None */
0, /* linux:484 (KEY_FN_B) -> linux:484 (KEY_FN_B) -> qnum:None */
0, /* linux:485 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:486 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:487 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:488 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:489 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:490 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:491 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:492 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:493 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:494 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:495 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:496 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:497 (KEY_BRL_DOT1) -> linux:497 (KEY_BRL_DOT1) -> qnum:None */
0, /* linux:498 (KEY_BRL_DOT2) -> linux:498 (KEY_BRL_DOT2) -> qnum:None */
0, /* linux:499 (KEY_BRL_DOT3) -> linux:499 (KEY_BRL_DOT3) -> qnum:None */
0, /* linux:500 (KEY_BRL_DOT4) -> linux:500 (KEY_BRL_DOT4) -> qnum:None */
0, /* linux:501 (KEY_BRL_DOT5) -> linux:501 (KEY_BRL_DOT5) -> qnum:None */
0, /* linux:502 (KEY_BRL_DOT6) -> linux:502 (KEY_BRL_DOT6) -> qnum:None */
0, /* linux:503 (KEY_BRL_DOT7) -> linux:503 (KEY_BRL_DOT7) -> qnum:None */
0, /* linux:504 (KEY_BRL_DOT8) -> linux:504 (KEY_BRL_DOT8) -> qnum:None */
0, /* linux:505 (KEY_BRL_DOT9) -> linux:505 (KEY_BRL_DOT9) -> qnum:None */
0, /* linux:506 (KEY_BRL_DOT10) -> linux:506 (KEY_BRL_DOT10) -> qnum:None */
0, /* linux:507 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:508 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:509 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:510 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:511 (unnamed) -> linux:None (unnamed) -> qnum:None */
0, /* linux:512 (KEY_NUMERIC_0) -> linux:512 (KEY_NUMERIC_0) -> qnum:None */
0, /* linux:513 (KEY_NUMERIC_1) -> linux:513 (KEY_NUMERIC_1) -> qnum:None */
0, /* linux:514 (KEY_NUMERIC_2) -> linux:514 (KEY_NUMERIC_2) -> qnum:None */
0, /* linux:515 (KEY_NUMERIC_3) -> linux:515 (KEY_NUMERIC_3) -> qnum:None */
0, /* linux:516 (KEY_NUMERIC_4) -> linux:516 (KEY_NUMERIC_4) -> qnum:None */
0, /* linux:517 (KEY_NUMERIC_5) -> linux:517 (KEY_NUMERIC_5) -> qnum:None */
0, /* linux:518 (KEY_NUMERIC_6) -> linux:518 (KEY_NUMERIC_6) -> qnum:None */
0, /* linux:519 (KEY_NUMERIC_7) -> linux:519 (KEY_NUMERIC_7) -> qnum:None */
0, /* linux:520 (KEY_NUMERIC_8) -> linux:520 (KEY_NUMERIC_8) -> qnum:None */
0, /* linux:521 (KEY_NUMERIC_9) -> linux:521 (KEY_NUMERIC_9) -> qnum:None */
0, /* linux:522 (KEY_NUMERIC_STAR) -> linux:522 (KEY_NUMERIC_STAR) -> qnum:None */
0, /* linux:523 (KEY_NUMERIC_POUND) -> linux:523 (KEY_NUMERIC_POUND) -> qnum:None */
0, /* linux:524 (KEY_RFKILL) -> linux:524 (KEY_RFKILL) -> qnum:None */
};
third-party/qemu-keycodemapdb/qnum_to_linux.cpp +264 −0
@@ -1,0 +1,264 @@
/*
* This file is auto-generated from keymaps.csv
* Database checksum sha256(26a8da612aafebc67b69d7c47a35a86a912f5576d15f6e1447928d49b4de6117)
* To re-generate, run:
* keymap-gen code-map --lang=stdc++ --varname=qemu_keycodemapdb_qnum_to_linux keymaps.csv qnum linux
*/
#include <vector>
extern const std::vector<unsigned short> qemu_keycodemapdb_qnum_to_linux;
const std::vector<unsigned short> qemu_keycodemapdb_qnum_to_linux = {
0, /* qnum:0 -> linux:None (unnamed) -> linux:None (unnamed) */
0x1, /* qnum:1 -> linux:1 (KEY_ESC) -> linux:1 (KEY_ESC) */
0x2, /* qnum:2 -> linux:2 (KEY_1) -> linux:2 (KEY_1) */
0x3, /* qnum:3 -> linux:3 (KEY_2) -> linux:3 (KEY_2) */
0x4, /* qnum:4 -> linux:4 (KEY_3) -> linux:4 (KEY_3) */
0x5, /* qnum:5 -> linux:5 (KEY_4) -> linux:5 (KEY_4) */
0x6, /* qnum:6 -> linux:6 (KEY_5) -> linux:6 (KEY_5) */
0x7, /* qnum:7 -> linux:7 (KEY_6) -> linux:7 (KEY_6) */
0x8, /* qnum:8 -> linux:8 (KEY_7) -> linux:8 (KEY_7) */
0x9, /* qnum:9 -> linux:9 (KEY_8) -> linux:9 (KEY_8) */
0xa, /* qnum:10 -> linux:10 (KEY_9) -> linux:10 (KEY_9) */
0xb, /* qnum:11 -> linux:11 (KEY_0) -> linux:11 (KEY_0) */
0xc, /* qnum:12 -> linux:12 (KEY_MINUS) -> linux:12 (KEY_MINUS) */
0xd, /* qnum:13 -> linux:13 (KEY_EQUAL) -> linux:13 (KEY_EQUAL) */
0xe, /* qnum:14 -> linux:14 (KEY_BACKSPACE) -> linux:14 (KEY_BACKSPACE) */
0xf, /* qnum:15 -> linux:15 (KEY_TAB) -> linux:15 (KEY_TAB) */
0x10, /* qnum:16 -> linux:16 (KEY_Q) -> linux:16 (KEY_Q) */
0x11, /* qnum:17 -> linux:17 (KEY_W) -> linux:17 (KEY_W) */
0x12, /* qnum:18 -> linux:18 (KEY_E) -> linux:18 (KEY_E) */
0x13, /* qnum:19 -> linux:19 (KEY_R) -> linux:19 (KEY_R) */
0x14, /* qnum:20 -> linux:20 (KEY_T) -> linux:20 (KEY_T) */
0x15, /* qnum:21 -> linux:21 (KEY_Y) -> linux:21 (KEY_Y) */
0x16, /* qnum:22 -> linux:22 (KEY_U) -> linux:22 (KEY_U) */
0x17, /* qnum:23 -> linux:23 (KEY_I) -> linux:23 (KEY_I) */
0x18, /* qnum:24 -> linux:24 (KEY_O) -> linux:24 (KEY_O) */
0x19, /* qnum:25 -> linux:25 (KEY_P) -> linux:25 (KEY_P) */
0x1a, /* qnum:26 -> linux:26 (KEY_LEFTBRACE) -> linux:26 (KEY_LEFTBRACE) */
0x1b, /* qnum:27 -> linux:27 (KEY_RIGHTBRACE) -> linux:27 (KEY_RIGHTBRACE) */
0x1c, /* qnum:28 -> linux:28 (KEY_ENTER) -> linux:28 (KEY_ENTER) */
0x1d, /* qnum:29 -> linux:29 (KEY_LEFTCTRL) -> linux:29 (KEY_LEFTCTRL) */
0x1e, /* qnum:30 -> linux:30 (KEY_A) -> linux:30 (KEY_A) */
0x1f, /* qnum:31 -> linux:31 (KEY_S) -> linux:31 (KEY_S) */
0x20, /* qnum:32 -> linux:32 (KEY_D) -> linux:32 (KEY_D) */
0x21, /* qnum:33 -> linux:33 (KEY_F) -> linux:33 (KEY_F) */
0x22, /* qnum:34 -> linux:34 (KEY_G) -> linux:34 (KEY_G) */
0x23, /* qnum:35 -> linux:35 (KEY_H) -> linux:35 (KEY_H) */
0x24, /* qnum:36 -> linux:36 (KEY_J) -> linux:36 (KEY_J) */
0x25, /* qnum:37 -> linux:37 (KEY_K) -> linux:37 (KEY_K) */
0x26, /* qnum:38 -> linux:38 (KEY_L) -> linux:38 (KEY_L) */
0x27, /* qnum:39 -> linux:39 (KEY_SEMICOLON) -> linux:39 (KEY_SEMICOLON) */
0x28, /* qnum:40 -> linux:40 (KEY_APOSTROPHE) -> linux:40 (KEY_APOSTROPHE) */
0x29, /* qnum:41 -> linux:41 (KEY_GRAVE) -> linux:41 (KEY_GRAVE) */
0x2a, /* qnum:42 -> linux:42 (KEY_LEFTSHIFT) -> linux:42 (KEY_LEFTSHIFT) */
0x2b, /* qnum:43 -> linux:43 (KEY_BACKSLASH) -> linux:43 (KEY_BACKSLASH) */
0x2c, /* qnum:44 -> linux:44 (KEY_Z) -> linux:44 (KEY_Z) */
0x2d, /* qnum:45 -> linux:45 (KEY_X) -> linux:45 (KEY_X) */
0x2e, /* qnum:46 -> linux:46 (KEY_C) -> linux:46 (KEY_C) */
0x2f, /* qnum:47 -> linux:47 (KEY_V) -> linux:47 (KEY_V) */
0x30, /* qnum:48 -> linux:48 (KEY_B) -> linux:48 (KEY_B) */
0x31, /* qnum:49 -> linux:49 (KEY_N) -> linux:49 (KEY_N) */
0x32, /* qnum:50 -> linux:50 (KEY_M) -> linux:50 (KEY_M) */
0x33, /* qnum:51 -> linux:51 (KEY_COMMA) -> linux:51 (KEY_COMMA) */
0x34, /* qnum:52 -> linux:52 (KEY_DOT) -> linux:52 (KEY_DOT) */
0x35, /* qnum:53 -> linux:53 (KEY_SLASH) -> linux:53 (KEY_SLASH) */
0x36, /* qnum:54 -> linux:54 (KEY_RIGHTSHIFT) -> linux:54 (KEY_RIGHTSHIFT) */
0x37, /* qnum:55 -> linux:55 (KEY_KPASTERISK) -> linux:55 (KEY_KPASTERISK) */
0x38, /* qnum:56 -> linux:56 (KEY_LEFTALT) -> linux:56 (KEY_LEFTALT) */
0x39, /* qnum:57 -> linux:57 (KEY_SPACE) -> linux:57 (KEY_SPACE) */
0x3a, /* qnum:58 -> linux:58 (KEY_CAPSLOCK) -> linux:58 (KEY_CAPSLOCK) */
0x3b, /* qnum:59 -> linux:59 (KEY_F1) -> linux:59 (KEY_F1) */
0x3c, /* qnum:60 -> linux:60 (KEY_F2) -> linux:60 (KEY_F2) */
0x3d, /* qnum:61 -> linux:61 (KEY_F3) -> linux:61 (KEY_F3) */
0x3e, /* qnum:62 -> linux:62 (KEY_F4) -> linux:62 (KEY_F4) */
0x3f, /* qnum:63 -> linux:63 (KEY_F5) -> linux:63 (KEY_F5) */
0x40, /* qnum:64 -> linux:64 (KEY_F6) -> linux:64 (KEY_F6) */
0x41, /* qnum:65 -> linux:65 (KEY_F7) -> linux:65 (KEY_F7) */
0x42, /* qnum:66 -> linux:66 (KEY_F8) -> linux:66 (KEY_F8) */
0x43, /* qnum:67 -> linux:67 (KEY_F9) -> linux:67 (KEY_F9) */
0x44, /* qnum:68 -> linux:68 (KEY_F10) -> linux:68 (KEY_F10) */
0x45, /* qnum:69 -> linux:69 (KEY_NUMLOCK) -> linux:69 (KEY_NUMLOCK) */
0x46, /* qnum:70 -> linux:70 (KEY_SCROLLLOCK) -> linux:70 (KEY_SCROLLLOCK) */
0x47, /* qnum:71 -> linux:71 (KEY_KP7) -> linux:71 (KEY_KP7) */
0x48, /* qnum:72 -> linux:72 (KEY_KP8) -> linux:72 (KEY_KP8) */
0x49, /* qnum:73 -> linux:73 (KEY_KP9) -> linux:73 (KEY_KP9) */
0x4a, /* qnum:74 -> linux:74 (KEY_KPMINUS) -> linux:74 (KEY_KPMINUS) */
0x4b, /* qnum:75 -> linux:75 (KEY_KP4) -> linux:75 (KEY_KP4) */
0x4c, /* qnum:76 -> linux:76 (KEY_KP5) -> linux:76 (KEY_KP5) */
0x4d, /* qnum:77 -> linux:77 (KEY_KP6) -> linux:77 (KEY_KP6) */
0x4e, /* qnum:78 -> linux:78 (KEY_KPPLUS) -> linux:78 (KEY_KPPLUS) */
0x4f, /* qnum:79 -> linux:79 (KEY_KP1) -> linux:79 (KEY_KP1) */
0x50, /* qnum:80 -> linux:80 (KEY_KP2) -> linux:80 (KEY_KP2) */
0x51, /* qnum:81 -> linux:81 (KEY_KP3) -> linux:81 (KEY_KP3) */
0x52, /* qnum:82 -> linux:82 (KEY_KP0) -> linux:82 (KEY_KP0) */
0x53, /* qnum:83 -> linux:83 (KEY_KPDOT) -> linux:83 (KEY_KPDOT) */
0x63, /* qnum:84 -> linux:99 (KEY_SYSRQ) -> linux:99 (KEY_SYSRQ) */
0xba, /* qnum:85 -> linux:186 (KEY_F16) -> linux:186 (KEY_F16) */
0x56, /* qnum:86 -> linux:86 (KEY_102ND) -> linux:86 (KEY_102ND) */
0x57, /* qnum:87 -> linux:87 (KEY_F11) -> linux:87 (KEY_F11) */
0x58, /* qnum:88 -> linux:88 (KEY_F12) -> linux:88 (KEY_F12) */
0x75, /* qnum:89 -> linux:117 (KEY_KPEQUAL) -> linux:117 (KEY_KPEQUAL) */
0xbe, /* qnum:90 -> linux:190 (KEY_F20) -> linux:190 (KEY_F20) */
0x65, /* qnum:91 -> linux:101 (KEY_LINEFEED) -> linux:101 (KEY_LINEFEED) */
0x5f, /* qnum:92 -> linux:95 (KEY_KPJPCOMMA) -> linux:95 (KEY_KPJPCOMMA) */
0xb7, /* qnum:93 -> linux:183 (KEY_F13) -> linux:183 (KEY_F13) */
0xb8, /* qnum:94 -> linux:184 (KEY_F14) -> linux:184 (KEY_F14) */
0xb9, /* qnum:95 -> linux:185 (KEY_F15) -> linux:185 (KEY_F15) */
0, /* qnum:96 -> linux:None (unnamed) -> linux:None (unnamed) */
0, /* qnum:97 -> linux:None (unnamed) -> linux:None (unnamed) */
0, /* qnum:98 -> linux:None (unnamed) -> linux:None (unnamed) */
0xa9, /* qnum:99 -> linux:169 (KEY_PHONE) -> linux:169 (KEY_PHONE) */
0x86, /* qnum:100 -> linux:134 (KEY_OPEN) -> linux:134 (KEY_OPEN) */
0x87, /* qnum:101 -> linux:135 (KEY_PASTE) -> linux:135 (KEY_PASTE) */
0x8d, /* qnum:102 -> linux:141 (KEY_SETUP) -> linux:141 (KEY_SETUP) */
0x90, /* qnum:103 -> linux:144 (KEY_FILE) -> linux:144 (KEY_FILE) */
0x91, /* qnum:104 -> linux:145 (KEY_SENDFILE) -> linux:145 (KEY_SENDFILE) */
0x92, /* qnum:105 -> linux:146 (KEY_DELETEFILE) -> linux:146 (KEY_DELETEFILE) */
0x97, /* qnum:106 -> linux:151 (KEY_MSDOS) -> linux:151 (KEY_MSDOS) */
0x99, /* qnum:107 -> linux:153 (KEY_DIRECTION) -> linux:153 (KEY_DIRECTION) */
0xa1, /* qnum:108 -> linux:161 (KEY_EJECTCD) -> linux:161 (KEY_EJECTCD) */
0xc1, /* qnum:109 -> linux:193 (KEY_F23) -> linux:193 (KEY_F23) */
0, /* qnum:110 -> linux:None (unnamed) -> linux:None (unnamed) */
0xc2, /* qnum:111 -> linux:194 (KEY_F24) -> linux:194 (KEY_F24) */
0x5d, /* qnum:112 -> linux:93 (KEY_KATAKANAHIRAGANA) -> linux:93 (KEY_KATAKANAHIRAGANA) */
0x7b, /* qnum:113 -> linux:123 (KEY_HANJA) -> linux:123 (KEY_HANJA) */
0x7a, /* qnum:114 -> linux:122 (KEY_HANGEUL) -> linux:122 (KEY_HANGEUL) */
0x59, /* qnum:115 -> linux:89 (KEY_RO) -> linux:89 (KEY_RO) */
0xbf, /* qnum:116 -> linux:191 (KEY_F21) -> linux:191 (KEY_F21) */
0xb1, /* qnum:117 -> linux:177 (KEY_SCROLLUP) -> linux:177 (KEY_SCROLLUP) */
0x55, /* qnum:118 -> linux:85 (KEY_ZENKAKUHANKAKU) -> linux:85 (KEY_ZENKAKUHANKAKU) */
0x5b, /* qnum:119 -> linux:91 (KEY_HIRAGANA) -> linux:91 (KEY_HIRAGANA) */
0x5a, /* qnum:120 -> linux:90 (KEY_KATAKANA) -> linux:90 (KEY_KATAKANA) */
0x5c, /* qnum:121 -> linux:92 (KEY_HENKAN) -> linux:92 (KEY_HENKAN) */
0, /* qnum:122 -> linux:None (unnamed) -> linux:None (unnamed) */
0x5e, /* qnum:123 -> linux:94 (KEY_MUHENKAN) -> linux:94 (KEY_MUHENKAN) */
0, /* qnum:124 -> linux:None (unnamed) -> linux:None (unnamed) */
0x7c, /* qnum:125 -> linux:124 (KEY_YEN) -> linux:124 (KEY_YEN) */
0x79, /* qnum:126 -> linux:121 (KEY_KPCOMMA) -> linux:121 (KEY_KPCOMMA) */
0, /* qnum:127 -> linux:None (unnamed) -> linux:None (unnamed) */
0, /* qnum:128 -> linux:None (unnamed) -> linux:None (unnamed) */
0xab, /* qnum:129 -> linux:171 (KEY_CONFIG) -> linux:171 (KEY_CONFIG) */
0x96, /* qnum:130 -> linux:150 (KEY_WWW) -> linux:150 (KEY_WWW) */
0xbb, /* qnum:131 -> linux:187 (KEY_F17) -> linux:187 (KEY_F17) */
0xbd, /* qnum:132 -> linux:189 (KEY_F19) -> linux:189 (KEY_F19) */
0x81, /* qnum:133 -> linux:129 (KEY_AGAIN) -> linux:129 (KEY_AGAIN) */
0x82, /* qnum:134 -> linux:130 (KEY_PROPS) -> linux:130 (KEY_PROPS) */
0x83, /* qnum:135 -> linux:131 (KEY_UNDO) -> linux:131 (KEY_UNDO) */
0xb0, /* qnum:136 -> linux:176 (KEY_EDIT) -> linux:176 (KEY_EDIT) */
0xb5, /* qnum:137 -> linux:181 (KEY_NEW) -> linux:181 (KEY_NEW) */
0xb6, /* qnum:138 -> linux:182 (KEY_REDO) -> linux:182 (KEY_REDO) */
0x78, /* qnum:139 -> linux:120 (KEY_SCALE) -> linux:120 (KEY_SCALE) */
0x84, /* qnum:140 -> linux:132 (KEY_FRONT) -> linux:132 (KEY_FRONT) */
0, /* qnum:141 -> linux:None (unnamed) -> linux:None (unnamed) */
0xe9, /* qnum:142 -> linux:233 (KEY_FORWARDMAIL) -> linux:233 (KEY_FORWARDMAIL) */
0xb2, /* qnum:143 -> linux:178 (KEY_SCROLLDOWN) -> linux:178 (KEY_SCROLLDOWN) */
0xa5, /* qnum:144 -> linux:165 (KEY_PREVIOUSSONG) -> linux:165 (KEY_PREVIOUSSONG) */
0, /* qnum:145 -> linux:None (unnamed) -> linux:None (unnamed) */
0x98, /* qnum:146 -> linux:152 (KEY_SCREENLOCK) -> linux:152 (KEY_SCREENLOCK) */
0x93, /* qnum:147 -> linux:147 (KEY_XFER) -> linux:147 (KEY_XFER) */
0xde, /* qnum:148 -> linux:222 (KEY_ALTERASE) -> linux:222 (KEY_ALTERASE) */
0xc3, /* qnum:149 -> linux:195 (unnamed) -> linux:195 (unnamed) */
0xc4, /* qnum:150 -> linux:196 (unnamed) -> linux:196 (unnamed) */
0x95, /* qnum:151 -> linux:149 (KEY_PROG2) -> linux:149 (KEY_PROG2) */
0xa8, /* qnum:152 -> linux:168 (KEY_REWIND) -> linux:168 (KEY_REWIND) */
0xa3, /* qnum:153 -> linux:163 (KEY_NEXTSONG) -> linux:163 (KEY_NEXTSONG) */
0xc5, /* qnum:154 -> linux:197 (unnamed) -> linux:197 (unnamed) */
0xc6, /* qnum:155 -> linux:198 (unnamed) -> linux:198 (unnamed) */
0x60, /* qnum:156 -> linux:96 (KEY_KPENTER) -> linux:96 (KEY_KPENTER) */
0x61, /* qnum:157 -> linux:97 (KEY_RIGHTCTRL) -> linux:97 (KEY_RIGHTCTRL) */
0x8b, /* qnum:158 -> linux:139 (KEY_MENU) -> linux:139 (KEY_MENU) */
0x94, /* qnum:159 -> linux:148 (KEY_PROG1) -> linux:148 (KEY_PROG1) */
0x71, /* qnum:160 -> linux:113 (KEY_MUTE) -> linux:113 (KEY_MUTE) */
0x8c, /* qnum:161 -> linux:140 (KEY_CALC) -> linux:140 (KEY_CALC) */
0xa4, /* qnum:162 -> linux:164 (KEY_PLAYPAUSE) -> linux:164 (KEY_PLAYPAUSE) */
0xa0, /* qnum:163 -> linux:160 (KEY_CLOSECD) -> linux:160 (KEY_CLOSECD) */
0xa6, /* qnum:164 -> linux:166 (KEY_STOPCD) -> linux:166 (KEY_STOPCD) */
0xcd, /* qnum:165 -> linux:205 (KEY_SUSPEND) -> linux:205 (KEY_SUSPEND) */
0x9a, /* qnum:166 -> linux:154 (KEY_CYCLEWINDOWS) -> linux:154 (KEY_CYCLEWINDOWS) */
0xc7, /* qnum:167 -> linux:199 (unnamed) -> linux:199 (unnamed) */
0xc8, /* qnum:168 -> linux:200 (KEY_PLAYCD) -> linux:200 (KEY_PLAYCD) */
0xc9, /* qnum:169 -> linux:201 (KEY_PAUSECD) -> linux:201 (KEY_PAUSECD) */
0, /* qnum:170 -> linux:None (unnamed) -> linux:None (unnamed) */
0xca, /* qnum:171 -> linux:202 (KEY_PROG3) -> linux:202 (KEY_PROG3) */
0xcb, /* qnum:172 -> linux:203 (KEY_PROG4) -> linux:203 (KEY_PROG4) */
0xcc, /* qnum:173 -> linux:204 (KEY_DASHBOARD) -> linux:204 (KEY_DASHBOARD) */
0x72, /* qnum:174 -> linux:114 (KEY_VOLUMEDOWN) -> linux:114 (KEY_VOLUMEDOWN) */
0xce, /* qnum:175 -> linux:206 (KEY_CLOSE) -> linux:206 (KEY_CLOSE) */
0x73, /* qnum:176 -> linux:115 (KEY_VOLUMEUP) -> linux:115 (KEY_VOLUMEUP) */
0xa7, /* qnum:177 -> linux:167 (KEY_RECORD) -> linux:167 (KEY_RECORD) */
0xac, /* qnum:178 -> linux:172 (KEY_HOMEPAGE) -> linux:172 (KEY_HOMEPAGE) */
0xcf, /* qnum:179 -> linux:207 (KEY_PLAY) -> linux:207 (KEY_PLAY) */
0xd0, /* qnum:180 -> linux:208 (KEY_FASTFORWARD) -> linux:208 (KEY_FASTFORWARD) */
0x62, /* qnum:181 -> linux:98 (KEY_KPSLASH) -> linux:98 (KEY_KPSLASH) */
0xd1, /* qnum:182 -> linux:209 (KEY_BASSBOOST) -> linux:209 (KEY_BASSBOOST) */
0x63, /* qnum:183 -> linux:99 (KEY_SYSRQ) -> linux:99 (KEY_SYSRQ) */
0x64, /* qnum:184 -> linux:100 (KEY_RIGHTALT) -> linux:100 (KEY_RIGHTALT) */
0xd2, /* qnum:185 -> linux:210 (KEY_PRINT) -> linux:210 (KEY_PRINT) */
0xd3, /* qnum:186 -> linux:211 (KEY_HP) -> linux:211 (KEY_HP) */
0xd4, /* qnum:187 -> linux:212 (KEY_CAMERA) -> linux:212 (KEY_CAMERA) */
0x89, /* qnum:188 -> linux:137 (KEY_CUT) -> linux:137 (KEY_CUT) */
0xd5, /* qnum:189 -> linux:213 (KEY_SOUND) -> linux:213 (KEY_SOUND) */
0xd6, /* qnum:190 -> linux:214 (KEY_QUESTION) -> linux:214 (KEY_QUESTION) */
0xd7, /* qnum:191 -> linux:215 (KEY_EMAIL) -> linux:215 (KEY_EMAIL) */
0xd8, /* qnum:192 -> linux:216 (KEY_CHAT) -> linux:216 (KEY_CHAT) */
0x88, /* qnum:193 -> linux:136 (KEY_FIND) -> linux:136 (KEY_FIND) */
0xda, /* qnum:194 -> linux:218 (KEY_CONNECT) -> linux:218 (KEY_CONNECT) */
0xdb, /* qnum:195 -> linux:219 (KEY_FINANCE) -> linux:219 (KEY_FINANCE) */
0xdc, /* qnum:196 -> linux:220 (KEY_SPORT) -> linux:220 (KEY_SPORT) */
0xdd, /* qnum:197 -> linux:221 (KEY_SHOP) -> linux:221 (KEY_SHOP) */
0x77, /* qnum:198 -> linux:119 (KEY_PAUSE) -> linux:119 (KEY_PAUSE) */
0x66, /* qnum:199 -> linux:102 (KEY_HOME) -> linux:102 (KEY_HOME) */
0x67, /* qnum:200 -> linux:103 (KEY_UP) -> linux:103 (KEY_UP) */
0x68, /* qnum:201 -> linux:104 (KEY_PAGEUP) -> linux:104 (KEY_PAGEUP) */
0xdf, /* qnum:202 -> linux:223 (KEY_CANCEL) -> linux:223 (KEY_CANCEL) */
0x69, /* qnum:203 -> linux:105 (KEY_LEFT) -> linux:105 (KEY_LEFT) */
0xe0, /* qnum:204 -> linux:224 (KEY_BRIGHTNESSDOWN) -> linux:224 (KEY_BRIGHTNESSDOWN) */
0x6a, /* qnum:205 -> linux:106 (KEY_RIGHT) -> linux:106 (KEY_RIGHT) */
0x76, /* qnum:206 -> linux:118 (KEY_KPPLUSMINUS) -> linux:118 (KEY_KPPLUSMINUS) */
0x6b, /* qnum:207 -> linux:107 (KEY_END) -> linux:107 (KEY_END) */
0x6c, /* qnum:208 -> linux:108 (KEY_DOWN) -> linux:108 (KEY_DOWN) */
0x6d, /* qnum:209 -> linux:109 (KEY_PAGEDOWN) -> linux:109 (KEY_PAGEDOWN) */
0x6e, /* qnum:210 -> linux:110 (KEY_INSERT) -> linux:110 (KEY_INSERT) */
0x6f, /* qnum:211 -> linux:111 (KEY_DELETE) -> linux:111 (KEY_DELETE) */
0xe1, /* qnum:212 -> linux:225 (KEY_BRIGHTNESSUP) -> linux:225 (KEY_BRIGHTNESSUP) */
0xea, /* qnum:213 -> linux:234 (KEY_SAVE) -> linux:234 (KEY_SAVE) */
0xe3, /* qnum:214 -> linux:227 (KEY_SWITCHVIDEOMODE) -> linux:227 (KEY_SWITCHVIDEOMODE) */
0xe4, /* qnum:215 -> linux:228 (KEY_KBDILLUMTOGGLE) -> linux:228 (KEY_KBDILLUMTOGGLE) */
0xe5, /* qnum:216 -> linux:229 (KEY_KBDILLUMDOWN) -> linux:229 (KEY_KBDILLUMDOWN) */
0xe6, /* qnum:217 -> linux:230 (KEY_KBDILLUMUP) -> linux:230 (KEY_KBDILLUMUP) */
0xe7, /* qnum:218 -> linux:231 (KEY_SEND) -> linux:231 (KEY_SEND) */
0x7d, /* qnum:219 -> linux:125 (KEY_LEFTMETA) -> linux:125 (KEY_LEFTMETA) */
0x7e, /* qnum:220 -> linux:126 (KEY_RIGHTMETA) -> linux:126 (KEY_RIGHTMETA) */
0x7f, /* qnum:221 -> linux:127 (KEY_COMPOSE) -> linux:127 (KEY_COMPOSE) */
0x74, /* qnum:222 -> linux:116 (KEY_POWER) -> linux:116 (KEY_POWER) */
0x8e, /* qnum:223 -> linux:142 (KEY_SLEEP) -> linux:142 (KEY_SLEEP) */
0, /* qnum:224 -> linux:None (unnamed) -> linux:None (unnamed) */
0, /* qnum:225 -> linux:None (unnamed) -> linux:None (unnamed) */
0, /* qnum:226 -> linux:None (unnamed) -> linux:None (unnamed) */
0x8f, /* qnum:227 -> linux:143 (KEY_WAKEUP) -> linux:143 (KEY_WAKEUP) */
0xe8, /* qnum:228 -> linux:232 (KEY_REPLY) -> linux:232 (KEY_REPLY) */
0xd9, /* qnum:229 -> linux:217 (KEY_SEARCH) -> linux:217 (KEY_SEARCH) */
0x9c, /* qnum:230 -> linux:156 (KEY_BOOKMARKS) -> linux:156 (KEY_BOOKMARKS) */
0xad, /* qnum:231 -> linux:173 (KEY_REFRESH) -> linux:173 (KEY_REFRESH) */
0x80, /* qnum:232 -> linux:128 (KEY_STOP) -> linux:128 (KEY_STOP) */
0x9f, /* qnum:233 -> linux:159 (KEY_FORWARD) -> linux:159 (KEY_FORWARD) */
0x9e, /* qnum:234 -> linux:158 (KEY_BACK) -> linux:158 (KEY_BACK) */
0x9d, /* qnum:235 -> linux:157 (KEY_COMPUTER) -> linux:157 (KEY_COMPUTER) */
0x9b, /* qnum:236 -> linux:155 (KEY_MAIL) -> linux:155 (KEY_MAIL) */
0xe2, /* qnum:237 -> linux:226 (KEY_MEDIA) -> linux:226 (KEY_MEDIA) */
0, /* qnum:238 -> linux:None (unnamed) -> linux:None (unnamed) */
0x70, /* qnum:239 -> linux:112 (KEY_MACRO) -> linux:112 (KEY_MACRO) */
0xeb, /* qnum:240 -> linux:235 (KEY_DOCUMENTS) -> linux:235 (KEY_DOCUMENTS) */
0xec, /* qnum:241 -> linux:236 (KEY_BATTERY) -> linux:236 (KEY_BATTERY) */
0xed, /* qnum:242 -> linux:237 (KEY_BLUETOOTH) -> linux:237 (KEY_BLUETOOTH) */
0xee, /* qnum:243 -> linux:238 (KEY_WLAN) -> linux:238 (KEY_WLAN) */
0xef, /* qnum:244 -> linux:239 (KEY_UWB) -> linux:239 (KEY_UWB) */
0x8a, /* qnum:245 -> linux:138 (KEY_HELP) -> linux:138 (KEY_HELP) */
0xb3, /* qnum:246 -> linux:179 (KEY_KPLEFTPAREN) -> linux:179 (KEY_KPLEFTPAREN) */
0xbc, /* qnum:247 -> linux:188 (KEY_F18) -> linux:188 (KEY_F18) */
0x85, /* qnum:248 -> linux:133 (KEY_COPY) -> linux:133 (KEY_COPY) */
0xc0, /* qnum:249 -> linux:192 (KEY_F22) -> linux:192 (KEY_F22) */
0, /* qnum:250 -> linux:None (unnamed) -> linux:None (unnamed) */
0xb4, /* qnum:251 -> linux:180 (KEY_KPRIGHTPAREN) -> linux:180 (KEY_KPRIGHTPAREN) */
0, /* qnum:252 -> linux:None (unnamed) -> linux:None (unnamed) */
0xa2, /* qnum:253 -> linux:162 (KEY_EJECTCLOSECD) -> linux:162 (KEY_EJECTCLOSECD) */
};
third-party/qemu-keycodemapdb/README.md +52 −0
@@ -1,0 +1,52 @@
# QEMU keycodemapdb tables
`linux_to_qnum.cpp` and `qnum_to_linux.cpp` translate between Linux input key codes (`KEY_*`)
and QEMU key numbers ("qnum": the XT scan code with the `0xe0` prefix folded into the high bit).
QEMU's `org.qemu.Display1.Keyboard.Press`/`Release` take a qnum, and QEMU turns it back into a
Linux key code with the same database (`ui/input-keymap.c`, `qemu_input_key_number_to_linux`).
Sunshine's `capture = qemu` backend uses `linux_to_qnum.cpp` to send keys; the unit tests use
`qnum_to_linux.cpp` to check that every key round-trips the way QEMU decodes it.
The files are the unmodified output of keycodemapdb's `keymap-gen`. They are not edited by hand
and not formatted with clang-format (`third-party` is outside the lint directories).
## Provenance
| Item | Value |
|--------------------|------------------------------------------------------------------------------------------------------------|
| Upstream | https://gitlab.com/keycodemap/keycodemapdb, as pinned by QEMU (`https://gitlab.com/qemu-project/keycodemapdb.git`) |
| Revision | `f5772a62ec52591ff6870b7e8ef32482371f22c6`, the revision QEMU v11.1.1 pins in `subprojects/keycodemapdb.wrap` |
| `data/keymaps.csv` | sha256 `26a8da612aafebc67b69d7c47a35a86a912f5576d15f6e1447928d49b4de6117` (also named in each generated file) |
| `tools/keymap-gen` | sha256 `e384cc336ac803ae6df02aea954fca0db19f073215ed4cc9e71296abd956f6d2` |
| `LICENSE.BSD` | copied unchanged from the keycodemapdb root |
## License
keycodemapdb is dual licensed under the GNU General Public License, version 2 or later, and the
3-clause BSD license. Its README says: "The output files generated by keymap-gen may be distributed
& used under the terms of either of the above licenses." Both are compatible with Sunshine's
GPL-3.0 license. `LICENSE.BSD` holds the BSD license text and copyright notice.
## Regenerating
From a keycodemapdb checkout at the revision above (for example QEMU's `subprojects/keycodemapdb`):
```bash
cd keycodemapdb/data
python3 ../tools/keymap-gen code-map --lang stdc++ --varname qemu_keycodemapdb_linux_to_qnum \
keymaps.csv linux qnum > <sunshine>/third-party/qemu-keycodemapdb/linux_to_qnum.cpp
python3 ../tools/keymap-gen code-map --lang stdc++ --varname qemu_keycodemapdb_qnum_to_linux \
keymaps.csv qnum linux > <sunshine>/third-party/qemu-keycodemapdb/qnum_to_linux.cpp
```
Run the commands from `data/`, so the "To re-generate" comment in each file names `keymaps.csv`
without a path. When updating, record the new revision and checksums above and run the
`QemuKeymapTest` unit tests.
## The Windows virtual-key step
Moonlight sends Windows virtual-key codes. `src/platform/linux/qemu/keymap.cpp` maps them to Linux
key codes with the table Sunshine already uses on Linux (libvirtualhid's `key_code_to_linux` in
`third-party/libvirtualhid/src/platform/linux/uhid_backend.cpp`, MIT licensed), extended with the
keys that table lacks (Pause, Menu, media and browser keys, IME keys) from keycodemapdb's `Win32`
column. The comments in `keymap.cpp` list which entries come from where.