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:
Pavel Dovgalyuk 2015-09-17 19:25:24 +03:00 committed by Paolo Bonzini
parent 4c27b85972
commit ee312992a3
7 changed files with 232 additions and 8 deletions

View File

@ -112,5 +112,9 @@ void replay_disable_events(void);
bool replay_events_enabled(void); bool replay_events_enabled(void);
/*! Adds bottom half event to the queue */ /*! Adds bottom half event to the queue */
void replay_bh_schedule_event(QEMUBH *bh); 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 #endif

View File

@ -33,7 +33,9 @@ void qemu_input_handler_bind(QemuInputHandlerState *s,
const char *device_id, int head, const char *device_id, int head,
Error **errp); Error **errp);
void qemu_input_event_send(QemuConsole *src, InputEvent *evt); 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(void);
void qemu_input_event_sync_impl(void);
InputEvent *qemu_input_event_new_key(KeyValue *key, bool down); InputEvent *qemu_input_event_new_key(KeyValue *key, bool down);
void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down); void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down);

View File

@ -2,3 +2,4 @@ common-obj-y += replay.o
common-obj-y += replay-internal.o common-obj-y += replay-internal.o
common-obj-y += replay-events.o common-obj-y += replay-events.o
common-obj-y += replay-time.o common-obj-y += replay-time.o
common-obj-y += replay-input.o

View File

@ -14,6 +14,7 @@
#include "sysemu/replay.h" #include "sysemu/replay.h"
#include "replay-internal.h" #include "replay-internal.h"
#include "block/aio.h" #include "block/aio.h"
#include "ui/input.h"
typedef struct Event { typedef struct Event {
ReplayAsyncEventKind event_kind; ReplayAsyncEventKind event_kind;
@ -39,6 +40,13 @@ static void replay_run_event(Event *event)
case REPLAY_ASYNC_EVENT_BH: case REPLAY_ASYNC_EVENT_BH:
aio_bh_call(event->opaque); aio_bh_call(event->opaque);
break; 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: default:
error_report("Replay: invalid async event ID (%d) in the queue", error_report("Replay: invalid async event ID (%d) in the queue",
event->event_kind); 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) static void replay_save_event(Event *event, int checkpoint)
{ {
if (replay_mode != REPLAY_MODE_PLAY) { if (replay_mode != REPLAY_MODE_PLAY) {
@ -144,6 +162,11 @@ static void replay_save_event(Event *event, int checkpoint)
case REPLAY_ASYNC_EVENT_BH: case REPLAY_ASYNC_EVENT_BH:
replay_put_qword(event->id); replay_put_qword(event->id);
break; break;
case REPLAY_ASYNC_EVENT_INPUT:
replay_save_input_event(event->opaque);
break;
case REPLAY_ASYNC_EVENT_INPUT_SYNC:
break;
default: default:
error_report("Unknown ID %d of replay event", read_event_kind); error_report("Unknown ID %d of replay event", read_event_kind);
exit(1); exit(1);
@ -187,6 +210,16 @@ static Event *replay_read_event(int checkpoint)
read_id = replay_get_qword(); read_id = replay_get_qword();
} }
break; 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: default:
error_report("Unknown ID %d of replay event", read_event_kind); error_report("Unknown ID %d of replay event", read_event_kind);
exit(1); exit(1);

160
replay/replay-input.c Normal file
View 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();
}
}

View File

@ -42,6 +42,8 @@ enum ReplayEvents {
enum ReplayAsyncEventKind { enum ReplayAsyncEventKind {
REPLAY_ASYNC_EVENT_BH, REPLAY_ASYNC_EVENT_BH,
REPLAY_ASYNC_EVENT_INPUT,
REPLAY_ASYNC_EVENT_INPUT_SYNC,
REPLAY_ASYNC_COUNT REPLAY_ASYNC_COUNT
}; };
@ -124,4 +126,15 @@ void replay_save_events(int checkpoint);
/*! Read events from the file into the input queue */ /*! Read events from the file into the input queue */
void replay_read_events(int checkpoint); 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 #endif

View File

@ -6,6 +6,7 @@
#include "trace.h" #include "trace.h"
#include "ui/input.h" #include "ui/input.h"
#include "ui/console.h" #include "ui/console.h"
#include "sysemu/replay.h"
struct QemuInputHandlerState { struct QemuInputHandlerState {
DeviceState *dev; DeviceState *dev;
@ -300,14 +301,10 @@ static void qemu_input_queue_sync(struct QemuInputEventQueueHead *queue)
QTAILQ_INSERT_TAIL(queue, item, node); 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; QemuInputHandlerState *s;
if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
return;
}
qemu_input_event_trace(src, evt); qemu_input_event_trace(src, evt);
/* pre processing */ /* pre processing */
@ -324,14 +321,19 @@ void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
s->events++; 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)) { if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
return; return;
} }
replay_input_event(src, evt);
}
void qemu_input_event_sync_impl(void)
{
QemuInputHandlerState *s;
trace_input_event_sync(); trace_input_event_sync();
QTAILQ_FOREACH(s, &handlers, node) { 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 *qemu_input_event_new_key(KeyValue *key, bool down)
{ {
InputEvent *evt = g_new0(InputEvent, 1); InputEvent *evt = g_new0(InputEvent, 1);