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);
|
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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
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 {
|
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
|
||||||
|
27
ui/input.c
27
ui/input.c
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user