| #include "qemu/osdep.h" | |
| #include "system/system.h" | |
| #include "qapi/error.h" | |
| #include "qapi/qapi-commands-ui.h" | |
| #include "trace.h" | |
| #include "ui/input.h" | |
| #include "ui/console.h" | |
| #include "system/replay.h" | |
| #include "system/runstate.h" | |
| struct QemuInputHandlerState { | |
| DeviceState *dev; | |
| const QemuInputHandler *handler; | |
| int id; | |
| int events; | |
| QemuConsole *con; | |
| uint32_t leds_mask; | |
| QTAILQ_ENTRY(QemuInputHandlerState) node; | |
| }; | |
| typedef struct QemuInputEventQueue QemuInputEventQueue; | |
| typedef QTAILQ_HEAD(QemuInputEventQueueHead, QemuInputEventQueue) | |
| QemuInputEventQueueHead; | |
| struct QemuInputEventQueue { | |
| enum { | |
| QEMU_INPUT_QUEUE_DELAY = 1, | |
| QEMU_INPUT_QUEUE_EVENT, | |
| QEMU_INPUT_QUEUE_SYNC, | |
| } type; | |
| QEMUTimer *timer; | |
| uint32_t delay_ms; | |
| QemuConsole *src; | |
| QemuInputEvent evt; | |
| QTAILQ_ENTRY(QemuInputEventQueue) node; | |
| }; | |
| static QTAILQ_HEAD(, QemuInputHandlerState) handlers = | |
| QTAILQ_HEAD_INITIALIZER(handlers); | |
| static NotifierList mouse_mode_notifiers = | |
| NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers); | |
| static NotifierList leds_notifiers = | |
| NOTIFIER_LIST_INITIALIZER(leds_notifiers); | |
| static QemuInputEventQueueHead kbd_queue = QTAILQ_HEAD_INITIALIZER(kbd_queue); | |
| static QEMUTimer *kbd_timer; | |
| static uint32_t kbd_default_delay_ms = 10; | |
| static uint32_t queue_count; | |
| static uint32_t queue_limit = 1024; | |
| static void notify_input_changed(uint32_t mask) | |
| { | |
| notifier_list_notify(&mouse_mode_notifiers, NULL); | |
| if (mask & INPUT_EVENT_MASK_KEY) { | |
| notifier_list_notify(&leds_notifiers, NULL); | |
| } | |
| } | |
| QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev, | |
| const QemuInputHandler *handler) | |
| { | |
| QemuInputHandlerState *s = g_new0(QemuInputHandlerState, 1); | |
| static int id = 1; | |
| s->dev = dev; | |
| s->handler = handler; | |
| s->id = id++; | |
| QTAILQ_INSERT_TAIL(&handlers, s, node); | |
| notify_input_changed(handler->mask); | |
| return s; | |
| } | |
| void qemu_input_handler_activate(QemuInputHandlerState *s) | |
| { | |
| QTAILQ_REMOVE(&handlers, s, node); | |
| QTAILQ_INSERT_HEAD(&handlers, s, node); | |
| notify_input_changed(s->handler->mask); | |
| } | |
| void qemu_input_handler_deactivate(QemuInputHandlerState *s) | |
| { | |
| QTAILQ_REMOVE(&handlers, s, node); | |
| QTAILQ_INSERT_TAIL(&handlers, s, node); | |
| notify_input_changed(s->handler->mask); | |
| } | |
| void qemu_input_handler_unregister(QemuInputHandlerState *s) | |
| { | |
| uint32_t mask = s->handler->mask; | |
| QTAILQ_REMOVE(&handlers, s, node); | |
| g_free(s); | |
| notify_input_changed(mask); | |
| } | |
| void qemu_input_handler_bind(QemuInputHandlerState *s, | |
| const char *device_id, int head, | |
| Error **errp) | |
| { | |
| QemuConsole *con; | |
| Error *err = NULL; | |
| con = qemu_console_lookup_by_device_name(device_id, head, &err); | |
| if (err) { | |
| error_propagate(errp, err); | |
| return; | |
| } | |
| s->con = con; | |
| } | |
| static QemuInputHandlerState* | |
| qemu_input_find_handler(uint32_t mask, const QemuConsole *con) | |
| { | |
| QemuInputHandlerState *s; | |
| QTAILQ_FOREACH(s, &handlers, node) { | |
| if (s->con == NULL || s->con != con) { | |
| continue; | |
| } | |
| if (mask & s->handler->mask) { | |
| return s; | |
| } | |
| } | |
| QTAILQ_FOREACH(s, &handlers, node) { | |
| if (s->con != NULL) { | |
| continue; | |
| } | |
| if (mask & s->handler->mask) { | |
| return s; | |
| } | |
| } | |
| return NULL; | |
| } | |
| void qemu_input_handler_set_leds_mask(QemuInputHandlerState *s, uint32_t leds_mask) | |
| { | |
| assert(s->handler->mask & INPUT_EVENT_MASK_KEY); | |
| s->leds_mask = leds_mask; | |
| notifier_list_notify(&leds_notifiers, NULL); | |
| } | |
| void qemu_input_led_notifier_add(Notifier *n) | |
| { | |
| notifier_list_add(&leds_notifiers, n); | |
| } | |
| void qemu_input_led_notifier_remove(Notifier *n) | |
| { | |
| notifier_remove(n); | |
| } | |
| void qmp_input_send_event(const char *device, | |
| bool has_head, int64_t head, | |
| InputEventList *events, Error **errp) | |
| { | |
| InputEventList *e; | |
| QemuConsole *con; | |
| Error *err = NULL; | |
| con = NULL; | |
| if (device) { | |
| if (!has_head) { | |
| head = 0; | |
| } | |
| con = qemu_console_lookup_by_device_name(device, head, &err); | |
| if (err) { | |
| error_propagate(errp, err); | |
| return; | |
| } | |
| } | |
| if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { | |
| error_setg(errp, "VM not running"); | |
| return; | |
| } | |
| for (e = events; e != NULL; e = e->next) { | |
| InputEvent *event = e->value; | |
| if (!qemu_input_find_handler(1 << event->type, con)) { | |
| error_setg(errp, "Input handler not found for " | |
| "event type %s", | |
| InputEventKind_str(event->type)); | |
| return; | |
| } | |
| } | |
| for (e = events; e != NULL; e = e->next) { | |
| InputEvent *qapi = e->value; | |
| QemuInputEvent evt; | |
| evt.type = qapi->type; | |
| switch (qapi->type) { | |
| case INPUT_EVENT_KIND_KEY: | |
| evt.key.key = qemu_input_key_value_to_linux(qapi->u.key.data->key); | |
| evt.key.down = qapi->u.key.data->down; | |
| break; | |
| case INPUT_EVENT_KIND_BTN: | |
| evt.btn = *qapi->u.btn.data; | |
| break; | |
| case INPUT_EVENT_KIND_REL: | |
| evt.rel = *qapi->u.rel.data; | |
| break; | |
| case INPUT_EVENT_KIND_ABS: | |
| evt.abs = *qapi->u.abs.data; | |
| break; | |
| case INPUT_EVENT_KIND_MTT: | |
| evt.mtt = *qapi->u.mtt.data; | |
| break; | |
| default: | |
| g_assert_not_reached(); | |
| } | |
| qemu_input_event_send(con, &evt); | |
| } | |
| qemu_input_event_sync(); | |
| } | |
| static void qemu_input_event_trace(QemuConsole *src, QemuInputEvent *evt) | |
| { | |
| const char *name; | |
| int idx = -1; | |
| QemuInputKeyEvent *key; | |
| InputBtnEvent *btn; | |
| InputMoveEvent *move; | |
| InputMultiTouchEvent *mtt; | |
| if (src) { | |
| idx = qemu_console_get_index(src); | |
| } | |
| switch (evt->type) { | |
| case INPUT_EVENT_KIND_KEY: | |
| key = &evt->key; | |
| name = QKeyCode_str(qemu_input_linux_to_qcode(key->key)); | |
| trace_input_event_key_qcode(idx, name, key->down); | |
| break; | |
| case INPUT_EVENT_KIND_BTN: | |
| btn = &evt->btn; | |
| name = btn->button < INPUT_BUTTON__MAX ? InputButton_str(btn->button) : "invalid"; | |
| trace_input_event_btn(idx, name, btn->down); | |
| break; | |
| case INPUT_EVENT_KIND_REL: | |
| move = &evt->rel; | |
| name = move->axis < INPUT_AXIS__MAX ? InputAxis_str(move->axis) : "invalid"; | |
| trace_input_event_rel(idx, name, move->value); | |
| break; | |
| case INPUT_EVENT_KIND_ABS: | |
| move = &evt->abs; | |
| name = move->axis < INPUT_AXIS__MAX ? InputAxis_str(move->axis) : "invalid"; | |
| trace_input_event_abs(idx, name, move->value); | |
| break; | |
| case INPUT_EVENT_KIND_MTT: | |
| mtt = &evt->mtt; | |
| name = mtt->axis < INPUT_AXIS__MAX ? InputAxis_str(mtt->axis) : "invalid"; | |
| trace_input_event_mtt(idx, name, mtt->value); | |
| break; | |
| case INPUT_EVENT_KIND__MAX: | |
| /* keep gcc happy */ | |
| break; | |
| } | |
| } | |
| static void qemu_input_queue_process(void *opaque) | |
| { | |
| QemuInputEventQueueHead *queue = opaque; | |
| QemuInputEventQueue *item; | |
| g_assert(!QTAILQ_EMPTY(queue)); | |
| item = QTAILQ_FIRST(queue); | |
| g_assert(item->type == QEMU_INPUT_QUEUE_DELAY); | |
| QTAILQ_REMOVE(queue, item, node); | |
| queue_count--; | |
| g_free(item); | |
| while (!QTAILQ_EMPTY(queue)) { | |
| item = QTAILQ_FIRST(queue); | |
| switch (item->type) { | |
| case QEMU_INPUT_QUEUE_DELAY: | |
| timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) | |
| + item->delay_ms); | |
| return; | |
| case QEMU_INPUT_QUEUE_EVENT: | |
| qemu_input_event_send(item->src, &item->evt); | |
| break; | |
| case QEMU_INPUT_QUEUE_SYNC: | |
| qemu_input_event_sync(); | |
| break; | |
| } | |
| QTAILQ_REMOVE(queue, item, node); | |
| queue_count--; | |
| g_free(item); | |
| } | |
| } | |
| static void qemu_input_queue_delay(QemuInputEventQueueHead *queue, | |
| QEMUTimer *timer, uint32_t delay_ms) | |
| { | |
| QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1); | |
| bool start_timer = QTAILQ_EMPTY(queue); | |
| item->type = QEMU_INPUT_QUEUE_DELAY; | |
| item->delay_ms = delay_ms; | |
| item->timer = timer; | |
| QTAILQ_INSERT_TAIL(queue, item, node); | |
| queue_count++; | |
| if (start_timer) { | |
| timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) | |
| + item->delay_ms); | |
| } | |
| } | |
| static void qemu_input_queue_event(QemuInputEventQueueHead *queue, | |
| QemuConsole *src, QemuInputEvent *evt) | |
| { | |
| QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1); | |
| item->type = QEMU_INPUT_QUEUE_EVENT; | |
| item->src = src; | |
| item->evt = *evt; | |
| QTAILQ_INSERT_TAIL(queue, item, node); | |
| queue_count++; | |
| } | |
| static void qemu_input_queue_sync(QemuInputEventQueueHead *queue) | |
| { | |
| QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1); | |
| item->type = QEMU_INPUT_QUEUE_SYNC; | |
| QTAILQ_INSERT_TAIL(queue, item, node); | |
| queue_count++; | |
| } | |
| void qemu_input_event_send_impl(QemuConsole *src, QemuInputEvent *evt) | |
| { | |
| QemuInputHandlerState *s; | |
| qemu_input_event_trace(src, evt); | |
| /* send event */ | |
| s = qemu_input_find_handler(1 << evt->type, src); | |
| if (!s) { | |
| return; | |
| } | |
| s->handler->event(s->dev, src, evt); | |
| s->events++; | |
| } | |
| void qemu_input_event_send(QemuConsole *src, QemuInputEvent *evt) | |
| { | |
| if (evt->type == INPUT_EVENT_KIND_KEY && !evt->key.key) { | |
| return; | |
| } | |
| if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { | |
| return; | |
| } | |
| replay_input_event(src, evt); | |
| } | |
| void qemu_input_event_sync_impl(void) | |
| { | |
| QemuInputHandlerState *s; | |
| trace_input_event_sync(); | |
| QTAILQ_FOREACH(s, &handlers, node) { | |
| if (!s->events) { | |
| continue; | |
| } | |
| if (s->handler->sync) { | |
| s->handler->sync(s->dev); | |
| } | |
| s->events = 0; | |
| } | |
| } | |
| void qemu_input_event_sync(void) | |
| { | |
| if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { | |
| return; | |
| } | |
| replay_input_sync_event(); | |
| } | |
| void qemu_input_event_send_key_linux(QemuConsole *src, unsigned int lnx, | |
| bool down) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_KEY, | |
| .key = { | |
| .key = lnx, | |
| .down = down, | |
| }, | |
| }; | |
| if (QTAILQ_EMPTY(&kbd_queue)) { | |
| qemu_input_event_send(src, &evt); | |
| qemu_input_event_sync(); | |
| } else if (queue_count < queue_limit) { | |
| qemu_input_queue_event(&kbd_queue, src, &evt); | |
| qemu_input_queue_sync(&kbd_queue); | |
| } | |
| } | |
| void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down) | |
| { | |
| unsigned int lnx = qemu_input_key_number_to_linux(num); | |
| qemu_input_event_send_key_linux(src, lnx, down); | |
| } | |
| void qemu_input_event_send_key_delay(uint32_t delay_ms) | |
| { | |
| if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { | |
| return; | |
| } | |
| if (!kbd_timer) { | |
| kbd_timer = timer_new_full(NULL, QEMU_CLOCK_VIRTUAL, | |
| SCALE_MS, QEMU_TIMER_ATTR_EXTERNAL, | |
| qemu_input_queue_process, &kbd_queue); | |
| } | |
| if (queue_count < queue_limit) { | |
| qemu_input_queue_delay(&kbd_queue, kbd_timer, | |
| delay_ms ? delay_ms : kbd_default_delay_ms); | |
| } | |
| } | |
| void qemu_input_queue_btn(QemuConsole *src, InputButton btn, bool down) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_BTN, | |
| .btn = { | |
| .button = btn, | |
| .down = down, | |
| } | |
| }; | |
| qemu_input_event_send(src, &evt); | |
| } | |
| void qemu_input_update_buttons(QemuConsole *src, uint32_t *button_map, | |
| uint32_t button_old, uint32_t button_new) | |
| { | |
| InputButton btn; | |
| uint32_t mask; | |
| for (btn = 0; btn < INPUT_BUTTON__MAX; btn++) { | |
| mask = button_map[btn]; | |
| if ((button_old & mask) == (button_new & mask)) { | |
| continue; | |
| } | |
| qemu_input_queue_btn(src, btn, button_new & mask); | |
| } | |
| } | |
| bool qemu_input_is_absolute(const QemuConsole *con) | |
| { | |
| QemuInputHandlerState *s; | |
| s = qemu_input_find_handler(INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS, | |
| con); | |
| return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS); | |
| } | |
| uint32_t qemu_input_get_leds_mask(const QemuConsole *con) | |
| { | |
| QemuInputHandlerState *s; | |
| s = qemu_input_find_handler(INPUT_EVENT_MASK_KEY, con); | |
| if (s) { | |
| return s->leds_mask; | |
| } | |
| return 0; | |
| } | |
| int qemu_input_scale_axis(int value, | |
| int min_in, int max_in, | |
| int min_out, int max_out) | |
| { | |
| int64_t range_in = (int64_t)max_in - min_in; | |
| int64_t range_out = (int64_t)max_out - min_out; | |
| if (range_in < 1) { | |
| return min_out + range_out / 2; | |
| } | |
| return ((int64_t)value - min_in) * range_out / range_in + min_out; | |
| } | |
| void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_REL, | |
| .rel = { | |
| .axis = axis, | |
| .value = value, | |
| }, | |
| }; | |
| qemu_input_event_send(src, &evt); | |
| } | |
| void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value, | |
| int min_in, int max_in) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_ABS, | |
| .abs = { | |
| .axis = axis, | |
| .value = qemu_input_scale_axis(value, min_in, max_in, | |
| INPUT_EVENT_ABS_MIN, | |
| INPUT_EVENT_ABS_MAX), | |
| }, | |
| }; | |
| qemu_input_event_send(src, &evt); | |
| } | |
| void qemu_input_queue_mtt(QemuConsole *src, InputMultiTouchType type, | |
| int slot, int tracking_id) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_MTT, | |
| .mtt = { | |
| .type = type, | |
| .slot = slot, | |
| .tracking_id = tracking_id, | |
| }, | |
| }; | |
| qemu_input_event_send(src, &evt); | |
| } | |
| void qemu_input_queue_mtt_abs(QemuConsole *src, InputAxis axis, int value, | |
| int min_in, int max_in, int slot, int tracking_id) | |
| { | |
| QemuInputEvent evt = { | |
| .type = INPUT_EVENT_KIND_MTT, | |
| .mtt = { | |
| .type = INPUT_MULTI_TOUCH_TYPE_DATA, | |
| .slot = slot, | |
| .tracking_id = tracking_id, | |
| .axis = axis, | |
| .value = qemu_input_scale_axis(value, min_in, max_in, | |
| INPUT_EVENT_ABS_MIN, | |
| INPUT_EVENT_ABS_MAX), | |
| } | |
| }; | |
| qemu_input_event_send(src, &evt); | |
| } | |
| void qemu_add_mouse_mode_change_notifier(Notifier *notify) | |
| { | |
| notifier_list_add(&mouse_mode_notifiers, notify); | |
| } | |
| void qemu_remove_mouse_mode_change_notifier(Notifier *notify) | |
| { | |
| notifier_remove(notify); | |
| } | |
| MouseInfoList *qmp_query_mice(Error **errp) | |
| { | |
| MouseInfoList *mice_list = NULL; | |
| MouseInfo *info; | |
| QemuInputHandlerState *s; | |
| bool current = true; | |
| QTAILQ_FOREACH(s, &handlers, node) { | |
| if (!(s->handler->mask & | |
| (INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS))) { | |
| continue; | |
| } | |
| info = g_new0(MouseInfo, 1); | |
| info->index = s->id; | |
| info->name = g_strdup(s->handler->name); | |
| info->absolute = s->handler->mask & INPUT_EVENT_MASK_ABS; | |
| info->current = current; | |
| current = false; | |
| QAPI_LIST_PREPEND(mice_list, info); | |
| } | |
| return mice_list; | |
| } | |
| bool qemu_mouse_set(int index, Error **errp) | |
| { | |
| QemuInputHandlerState *s; | |
| QTAILQ_FOREACH(s, &handlers, node) { | |
| if (s->id == index) { | |
| break; | |
| } | |
| } | |
| if (!s) { | |
| error_setg(errp, "Mouse at index '%d' not found", index); | |
| return false; | |
| } | |
| if (!(s->handler->mask & (INPUT_EVENT_MASK_REL | | |
| INPUT_EVENT_MASK_ABS))) { | |
| error_setg(errp, "Input device '%s' is not a mouse", | |
| s->handler->name); | |
| return false; | |
| } | |
| qemu_input_handler_activate(s); | |
| return true; | |
| } | |
| void qemu_input_touch_event(QemuConsole *con, | |
| struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX], | |
| uint64_t num_slot, | |
| int width, int height, | |
| double x, double y, | |
| InputMultiTouchType type, | |
| Error **errp) | |
| { | |
| struct touch_slot *slot; | |
| bool needs_sync = false; | |
| int update; | |
| int i; | |
| if (num_slot >= INPUT_EVENT_SLOTS_MAX) { | |
| error_setg(errp, | |
| "Unexpected touch slot number: % " PRId64" >= %d", | |
| num_slot, INPUT_EVENT_SLOTS_MAX); | |
| return; | |
| } | |
| slot = &touch_slots[num_slot]; | |
| slot->x = x; | |
| slot->y = y; | |
| if (type == INPUT_MULTI_TOUCH_TYPE_BEGIN) { | |
| slot->tracking_id = num_slot; | |
| } | |
| for (i = 0; i < INPUT_EVENT_SLOTS_MAX; ++i) { | |
| if (i == num_slot) { | |
| update = type; | |
| } else { | |
| update = INPUT_MULTI_TOUCH_TYPE_UPDATE; | |
| } | |
| slot = &touch_slots[i]; | |
| if (slot->tracking_id == -1) { | |
| continue; | |
| } | |
| if (update == INPUT_MULTI_TOUCH_TYPE_END) { | |
| slot->tracking_id = -1; | |
| qemu_input_queue_mtt(con, update, i, slot->tracking_id); | |
| needs_sync = true; | |
| } else { | |
| qemu_input_queue_mtt(con, update, i, slot->tracking_id); | |
| qemu_input_queue_btn(con, INPUT_BUTTON_TOUCH, true); | |
| qemu_input_queue_mtt_abs(con, | |
| INPUT_AXIS_X, (int) slot->x, | |
| 0, width, | |
| i, slot->tracking_id); | |
| qemu_input_queue_mtt_abs(con, | |
| INPUT_AXIS_Y, (int) slot->y, | |
| 0, height, | |
| i, slot->tracking_id); | |
| needs_sync = true; | |
| } | |
| } | |
| if (needs_sync) { | |
| qemu_input_event_sync(); | |
| } | |
| } | |
| void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time, | |
| Error **errp) | |
| { | |
| KeyValueList *p; | |
| unsigned int *up = NULL; | |
| int count = 0; | |
| if (!has_hold_time) { | |
| hold_time = 0; /* use default */ | |
| } | |
| for (p = keys; p != NULL; p = p->next) { | |
| up = g_realloc_n(up, count + 1, sizeof(*up)); | |
| up[count] = qemu_input_key_value_to_linux(p->value); | |
| qemu_input_event_send_key_linux(NULL, up[count], true); | |
| qemu_input_event_send_key_delay(hold_time); | |
| count++; | |
| } | |
| while (count) { | |
| count--; | |
| qemu_input_event_send_key_linux(NULL, up[count], false); | |
| qemu_input_event_send_key_delay(hold_time); | |
| } | |
| g_free(up); | |
| } |