replay: recording of the user input
This records user input (keyboard and mouse events) in record mode and replays these input events in replay mode. Signed-off-by: Pavel Dovgalyuk <pavel.dovgaluk@ispras.ru> Message-Id: <20150917162524.8676.11696.stgit@PASHA-ISP.def.inno> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Pavel Dovgalyuk <Pavel.Dovgaluk@ispras.ru>
This commit is contained in:
parent
4c27b85972
commit
ee312992a3
@ -112,5 +112,9 @@ void replay_disable_events(void);
|
||||
bool replay_events_enabled(void);
|
||||
/*! Adds bottom half event to the queue */
|
||||
void replay_bh_schedule_event(QEMUBH *bh);
|
||||
/*! Adds input event to the queue */
|
||||
void replay_input_event(QemuConsole *src, InputEvent *evt);
|
||||
/*! Adds input sync event to the queue */
|
||||
void replay_input_sync_event(void);
|
||||
|
||||
#endif
|
||||
|
@ -33,7 +33,9 @@ void qemu_input_handler_bind(QemuInputHandlerState *s,
|
||||
const char *device_id, int head,
|
||||
Error **errp);
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt);
|
||||
void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt);
|
||||
void qemu_input_event_sync(void);
|
||||
void qemu_input_event_sync_impl(void);
|
||||
|
||||
InputEvent *qemu_input_event_new_key(KeyValue *key, bool down);
|
||||
void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down);
|
||||
|
@ -2,3 +2,4 @@ common-obj-y += replay.o
|
||||
common-obj-y += replay-internal.o
|
||||
common-obj-y += replay-events.o
|
||||
common-obj-y += replay-time.o
|
||||
common-obj-y += replay-input.o
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "block/aio.h"
|
||||
#include "ui/input.h"
|
||||
|
||||
typedef struct Event {
|
||||
ReplayAsyncEventKind event_kind;
|
||||
@ -39,6 +40,13 @@ static void replay_run_event(Event *event)
|
||||
case REPLAY_ASYNC_EVENT_BH:
|
||||
aio_bh_call(event->opaque);
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT:
|
||||
qemu_input_event_send_impl(NULL, (InputEvent *)event->opaque);
|
||||
qapi_free_InputEvent((InputEvent *)event->opaque);
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT_SYNC:
|
||||
qemu_input_event_sync_impl();
|
||||
break;
|
||||
default:
|
||||
error_report("Replay: invalid async event ID (%d) in the queue",
|
||||
event->event_kind);
|
||||
@ -131,6 +139,16 @@ void replay_bh_schedule_event(QEMUBH *bh)
|
||||
}
|
||||
}
|
||||
|
||||
void replay_add_input_event(struct InputEvent *event)
|
||||
{
|
||||
replay_add_event(REPLAY_ASYNC_EVENT_INPUT, event, NULL, 0);
|
||||
}
|
||||
|
||||
void replay_add_input_sync_event(void)
|
||||
{
|
||||
replay_add_event(REPLAY_ASYNC_EVENT_INPUT_SYNC, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
static void replay_save_event(Event *event, int checkpoint)
|
||||
{
|
||||
if (replay_mode != REPLAY_MODE_PLAY) {
|
||||
@ -144,6 +162,11 @@ static void replay_save_event(Event *event, int checkpoint)
|
||||
case REPLAY_ASYNC_EVENT_BH:
|
||||
replay_put_qword(event->id);
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT:
|
||||
replay_save_input_event(event->opaque);
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT_SYNC:
|
||||
break;
|
||||
default:
|
||||
error_report("Unknown ID %d of replay event", read_event_kind);
|
||||
exit(1);
|
||||
@ -187,6 +210,16 @@ static Event *replay_read_event(int checkpoint)
|
||||
read_id = replay_get_qword();
|
||||
}
|
||||
break;
|
||||
case REPLAY_ASYNC_EVENT_INPUT:
|
||||
event = g_malloc0(sizeof(Event));
|
||||
event->event_kind = read_event_kind;
|
||||
event->opaque = replay_read_input_event();
|
||||
return event;
|
||||
case REPLAY_ASYNC_EVENT_INPUT_SYNC:
|
||||
event = g_malloc0(sizeof(Event));
|
||||
event->event_kind = read_event_kind;
|
||||
event->opaque = 0;
|
||||
return event;
|
||||
default:
|
||||
error_report("Unknown ID %d of replay event", read_event_kind);
|
||||
exit(1);
|
||||
|
160
replay/replay-input.c
Normal file
160
replay/replay-input.c
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* replay-input.c
|
||||
*
|
||||
* Copyright (c) 2010-2015 Institute for System Programming
|
||||
* of the Russian Academy of Sciences.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "sysemu/replay.h"
|
||||
#include "replay-internal.h"
|
||||
#include "qemu/notify.h"
|
||||
#include "ui/input.h"
|
||||
#include "qapi/qmp-output-visitor.h"
|
||||
#include "qapi/qmp-input-visitor.h"
|
||||
#include "qapi-visit.h"
|
||||
|
||||
static InputEvent *qapi_clone_InputEvent(InputEvent *src)
|
||||
{
|
||||
QmpOutputVisitor *qov;
|
||||
QmpInputVisitor *qiv;
|
||||
Visitor *ov, *iv;
|
||||
QObject *obj;
|
||||
InputEvent *dst = NULL;
|
||||
|
||||
qov = qmp_output_visitor_new();
|
||||
ov = qmp_output_get_visitor(qov);
|
||||
visit_type_InputEvent(ov, &src, NULL, &error_abort);
|
||||
obj = qmp_output_get_qobject(qov);
|
||||
qmp_output_visitor_cleanup(qov);
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qiv = qmp_input_visitor_new(obj);
|
||||
iv = qmp_input_get_visitor(qiv);
|
||||
visit_type_InputEvent(iv, &dst, NULL, &error_abort);
|
||||
qmp_input_visitor_cleanup(qiv);
|
||||
qobject_decref(obj);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
void replay_save_input_event(InputEvent *evt)
|
||||
{
|
||||
replay_put_dword(evt->type);
|
||||
|
||||
switch (evt->type) {
|
||||
case INPUT_EVENT_KIND_KEY:
|
||||
replay_put_dword(evt->u.key->key->type);
|
||||
|
||||
switch (evt->u.key->key->type) {
|
||||
case KEY_VALUE_KIND_NUMBER:
|
||||
replay_put_qword(evt->u.key->key->u.number);
|
||||
replay_put_byte(evt->u.key->down);
|
||||
break;
|
||||
case KEY_VALUE_KIND_QCODE:
|
||||
replay_put_dword(evt->u.key->key->u.qcode);
|
||||
replay_put_byte(evt->u.key->down);
|
||||
break;
|
||||
case KEY_VALUE_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
replay_put_dword(evt->u.btn->button);
|
||||
replay_put_byte(evt->u.btn->down);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
replay_put_dword(evt->u.rel->axis);
|
||||
replay_put_qword(evt->u.rel->value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
replay_put_dword(evt->u.abs->axis);
|
||||
replay_put_qword(evt->u.abs->value);
|
||||
break;
|
||||
case INPUT_EVENT_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
InputEvent *replay_read_input_event(void)
|
||||
{
|
||||
InputEvent evt;
|
||||
KeyValue keyValue;
|
||||
InputKeyEvent key;
|
||||
key.key = &keyValue;
|
||||
InputBtnEvent btn;
|
||||
InputMoveEvent rel;
|
||||
InputMoveEvent abs;
|
||||
|
||||
evt.type = replay_get_dword();
|
||||
switch (evt.type) {
|
||||
case INPUT_EVENT_KIND_KEY:
|
||||
evt.u.key = &key;
|
||||
evt.u.key->key->type = replay_get_dword();
|
||||
|
||||
switch (evt.u.key->key->type) {
|
||||
case KEY_VALUE_KIND_NUMBER:
|
||||
evt.u.key->key->u.number = replay_get_qword();
|
||||
evt.u.key->down = replay_get_byte();
|
||||
break;
|
||||
case KEY_VALUE_KIND_QCODE:
|
||||
evt.u.key->key->u.qcode = (QKeyCode)replay_get_dword();
|
||||
evt.u.key->down = replay_get_byte();
|
||||
break;
|
||||
case KEY_VALUE_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case INPUT_EVENT_KIND_BTN:
|
||||
evt.u.btn = &btn;
|
||||
evt.u.btn->button = (InputButton)replay_get_dword();
|
||||
evt.u.btn->down = replay_get_byte();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_REL:
|
||||
evt.u.rel = &rel;
|
||||
evt.u.rel->axis = (InputAxis)replay_get_dword();
|
||||
evt.u.rel->value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_ABS:
|
||||
evt.u.abs = &abs;
|
||||
evt.u.abs->axis = (InputAxis)replay_get_dword();
|
||||
evt.u.abs->value = replay_get_qword();
|
||||
break;
|
||||
case INPUT_EVENT_KIND_MAX:
|
||||
/* keep gcc happy */
|
||||
break;
|
||||
}
|
||||
|
||||
return qapi_clone_InputEvent(&evt);
|
||||
}
|
||||
|
||||
void replay_input_event(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
/* Nothing */
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_add_input_event(qapi_clone_InputEvent(evt));
|
||||
} else {
|
||||
qemu_input_event_send_impl(src, evt);
|
||||
}
|
||||
}
|
||||
|
||||
void replay_input_sync_event(void)
|
||||
{
|
||||
if (replay_mode == REPLAY_MODE_PLAY) {
|
||||
/* Nothing */
|
||||
} else if (replay_mode == REPLAY_MODE_RECORD) {
|
||||
replay_add_input_sync_event();
|
||||
} else {
|
||||
qemu_input_event_sync_impl();
|
||||
}
|
||||
}
|
@ -42,6 +42,8 @@ enum ReplayEvents {
|
||||
|
||||
enum ReplayAsyncEventKind {
|
||||
REPLAY_ASYNC_EVENT_BH,
|
||||
REPLAY_ASYNC_EVENT_INPUT,
|
||||
REPLAY_ASYNC_EVENT_INPUT_SYNC,
|
||||
REPLAY_ASYNC_COUNT
|
||||
};
|
||||
|
||||
@ -124,4 +126,15 @@ void replay_save_events(int checkpoint);
|
||||
/*! Read events from the file into the input queue */
|
||||
void replay_read_events(int checkpoint);
|
||||
|
||||
/* Input events */
|
||||
|
||||
/*! Saves input event to the log */
|
||||
void replay_save_input_event(InputEvent *evt);
|
||||
/*! Reads input event from the log */
|
||||
InputEvent *replay_read_input_event(void);
|
||||
/*! Adds input event to the queue */
|
||||
void replay_add_input_event(struct InputEvent *event);
|
||||
/*! Adds input sync event to the queue */
|
||||
void replay_add_input_sync_event(void);
|
||||
|
||||
#endif
|
||||
|
27
ui/input.c
27
ui/input.c
@ -6,6 +6,7 @@
|
||||
#include "trace.h"
|
||||
#include "ui/input.h"
|
||||
#include "ui/console.h"
|
||||
#include "sysemu/replay.h"
|
||||
|
||||
struct QemuInputHandlerState {
|
||||
DeviceState *dev;
|
||||
@ -300,14 +301,10 @@ static void qemu_input_queue_sync(struct QemuInputEventQueueHead *queue)
|
||||
QTAILQ_INSERT_TAIL(queue, item, node);
|
||||
}
|
||||
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
QemuInputHandlerState *s;
|
||||
|
||||
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_input_event_trace(src, evt);
|
||||
|
||||
/* pre processing */
|
||||
@ -324,14 +321,19 @@ void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
s->events++;
|
||||
}
|
||||
|
||||
void qemu_input_event_sync(void)
|
||||
void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
|
||||
{
|
||||
QemuInputHandlerState *s;
|
||||
|
||||
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) {
|
||||
@ -345,6 +347,15 @@ void qemu_input_event_sync(void)
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_input_event_sync(void)
|
||||
{
|
||||
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
replay_input_sync_event();
|
||||
}
|
||||
|
||||
InputEvent *qemu_input_event_new_key(KeyValue *key, bool down)
|
||||
{
|
||||
InputEvent *evt = g_new0(InputEvent, 1);
|
||||
|
Loading…
Reference in New Issue
Block a user