Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/kraxel/tags/pull-input-10' into…
Browse files Browse the repository at this point in the history
… staging

updates for docs/multiseat.txt
input: add support for kbd delays

# gpg: Signature made Wed 04 Jun 2014 08:22:39 BST using RSA key ID D3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>"
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>"
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>"

* remotes/kraxel/tags/pull-input-10:
  docs/multiseat.txt: add note about spice
  docs/multiseat.txt: gtk joined the party
  docs/multiseat.txt: use autoseat
  input/vnc: use kbd delays in press_key
  input/curses: add kbd delay between keydown and keyup events
  input: use kbd delays for send_key monitor command
  input: add support for kbd delays

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
  • Loading branch information
pm215 committed Jun 5, 2014
2 parents e00fcfe + 2082bac commit d4f005d
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 68 deletions.
78 changes: 52 additions & 26 deletions docs/multiseat.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ host side
---------

First you must compile qemu with a user interface supporting
multihead/multiseat and input event routing. Right now this list is
pretty short: sdl2.
multihead/multiseat and input event routing. Right now this
list includes sdl2 and gtk (both 2+3):

./configure --enable-sdl --with-sdlabi=2.0

or

./configure --enable-gtk


Next put together the qemu command line:

qemu -enable-kvm -usb $memory $disk $whatever \
-display sdl \
-display [ sdl | gtk ] \
-vga std \
-device usb-tablet

Expand All @@ -37,6 +41,20 @@ The "display=video2" sets up the input routing. Any input coming from
the window which belongs to the video.2 display adapter will be routed
to these input devices.

The sdl2 ui will start up with two windows, one for each display
device. The gtk ui will start with a single window and each display
in a separate tab. You can either simply switch tabs to switch heads,
or use the "View / Detach tab" menu item to move one of the displays
to its own window so you can see both display devices side-by-side.

Note on spice: Spice handles multihead just fine. But it can't do
multiseat. For tablet events the event source is sent to the spice
agent. But qemu can't figure it, so it can't do input routing.
Fixing this needs a new or extended input interface between
libspice-server and qemu. For keyboard events it is even worse: The
event source isn't included in the spice protocol, so the wire
protocol must be extended to support this.


guest side
----------
Expand All @@ -46,29 +64,37 @@ You need a pretty recent linux guest. systemd with loginctl. kernel
fully updated for the new kernel though, i.e. the live iso doesn't cut
it.

Now we'll have to configure the guest. Boot and login. By default
all devices belong to seat0. You can use "loginctl seat-status seat0"
to list them all (and to get the sysfs paths for cut+paste). Now
we'll go assign all pci devices connected the pci bridge in slot 12 to
a new head:

loginctl attach seat-qemu \
/sys/devices/pci0000:00/0000:00:12.0/0000:01:02.0/drm/card1
loginctl attach seat-qemu \
/sys/devices/pci0000:00/0000:00:12.0/0000:01:02.0/graphics/fb1
loginctl attach seat-qemu \
/sys/devices/pci0000:00/0000:00:12.0/0000:01:0f.0/usb2

Use "loginctl seat-status seat-qemu" to check the result. It isn't
needed to assign the usb devices to the head individually, assigning a
usb (root) hub will automatically assign all usb devices connected to
it too.

BTW: loginctl writes udev rules to /etc/udev/rules.d to make these
device assignments permanent, so you need to do this only once.

Now simply restart gdm (rebooting will do too), and a login screen
should show up on the second head.
Now we'll have to configure the guest. Boot and login. "lspci -vt"
should list the pci bridge with the display adapter and usb controller:

[root@fedora ~]# lspci -vt
-[0000:00]-+-00.0 Intel Corporation 440FX - 82441FX PMC [Natoma]
[ ... ]
\-12.0-[01]--+-02.0 Device 1234:1111
\-0f.0 NEC Corporation USB 3.0 Host Controller

Good. Now lets tell the system that the pci bridge and all devices
below it belong to a separate seat by dropping a file into
/etc/udev/rules.d:

[root@fedora ~]# cat /etc/udev/rules.d/70-qemu-autoseat.rules
SUBSYSTEMS=="pci", DEVPATH=="*/0000:00:12.0", TAG+="seat", ENV{ID_AUTOSEAT}="1"

Reboot. System should come up with two seats. With loginctl you can
check the configuration:

[root@fedora ~]# loginctl list-seats
SEAT
seat0
seat-pci-pci-0000_00_12_0

2 seats listed.

You can use "loginctl seat-status seat-pci-pci-0000_00_12_0" to list
the devices attached to the seat.

Background info is here:
http://www.freedesktop.org/wiki/Software/systemd/multiseat/

Enjoy!

Expand Down
1 change: 1 addition & 0 deletions include/ui/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ 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_number(QemuConsole *src, int num, bool down);
void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down);
void qemu_input_event_send_key_delay(uint32_t delay_ms);
int qemu_input_key_number_to_qcode(uint8_t nr);
int qemu_input_key_value_to_number(const KeyValue *value);
int qemu_input_key_value_to_qcode(const KeyValue *value);
Expand Down
10 changes: 10 additions & 0 deletions ui/curses.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,31 +277,41 @@ static void curses_refresh(DisplayChangeListener *dcl)
* events, we need to emit both for each key received */
if (keycode & SHIFT) {
qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
qemu_input_event_send_key_delay(0);
}
if (keycode & CNTRL) {
qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
qemu_input_event_send_key_delay(0);
}
if (keycode & ALT) {
qemu_input_event_send_key_number(NULL, ALT_CODE, true);
qemu_input_event_send_key_delay(0);
}
if (keycode & ALTGR) {
qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
qemu_input_event_send_key_delay(0);
}

qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
qemu_input_event_send_key_delay(0);
qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
qemu_input_event_send_key_delay(0);

if (keycode & ALTGR) {
qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
qemu_input_event_send_key_delay(0);
}
if (keycode & ALT) {
qemu_input_event_send_key_number(NULL, ALT_CODE, false);
qemu_input_event_send_key_delay(0);
}
if (keycode & CNTRL) {
qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
qemu_input_event_send_key_delay(0);
}
if (keycode & SHIFT) {
qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
qemu_input_event_send_key_delay(0);
}
} else {
keysym = curses2qemu[chr];
Expand Down
45 changes: 6 additions & 39 deletions ui/input-legacy.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,27 +74,6 @@ int index_from_key(const char *key)
return i;
}

static KeyValue **keyvalues;
static int keyvalues_size;
static QEMUTimer *key_timer;

static void free_keyvalues(void)
{
g_free(keyvalues);
keyvalues = NULL;
keyvalues_size = 0;
}

static void release_keys(void *opaque)
{
while (keyvalues_size > 0) {
qemu_input_event_send_key(NULL, keyvalues[--keyvalues_size],
false);
}

free_keyvalues();
}

static KeyValue *copy_key_value(KeyValue *src)
{
KeyValue *dst = g_new(KeyValue, 1);
Expand All @@ -107,30 +86,18 @@ void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time,
{
KeyValueList *p;

if (!key_timer) {
key_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, release_keys, NULL);
}

if (keyvalues != NULL) {
timer_del(key_timer);
release_keys(NULL);
}

if (!has_hold_time) {
hold_time = 100;
hold_time = 0; /* use default */
}

for (p = keys; p != NULL; p = p->next) {
qemu_input_event_send_key(NULL, copy_key_value(p->value), true);

keyvalues = g_realloc(keyvalues, sizeof(KeyValue *) *
(keyvalues_size + 1));
keyvalues[keyvalues_size++] = copy_key_value(p->value);
qemu_input_event_send_key_delay(hold_time);
}
for (p = keys; p != NULL; p = p->next) {
qemu_input_event_send_key(NULL, copy_key_value(p->value), false);
qemu_input_event_send_key_delay(hold_time);
}

/* delayed key up events */
timer_mod(key_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
muldiv64(get_ticks_per_sec(), hold_time, 1000));
}

static void legacy_kbd_event(DeviceState *dev, QemuConsole *src,
Expand Down
108 changes: 105 additions & 3 deletions ui/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,31 @@ struct QemuInputHandlerState {
QemuConsole *con;
QTAILQ_ENTRY(QemuInputHandlerState) node;
};

typedef struct QemuInputEventQueue QemuInputEventQueue;
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;
InputEvent *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 QTAILQ_HEAD(QemuInputEventQueueHead, QemuInputEventQueue) kbd_queue =
QTAILQ_HEAD_INITIALIZER(kbd_queue);
static QEMUTimer *kbd_timer;
static uint32_t kbd_default_delay_ms = 10;

QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev,
QemuInputHandler *handler)
{
Expand Down Expand Up @@ -171,6 +191,73 @@ static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt)
}
}

static void qemu_input_queue_process(void *opaque)
{
struct 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);
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);
qapi_free_InputEvent(item->evt);
break;
case QEMU_INPUT_QUEUE_SYNC:
qemu_input_event_sync();
break;
}
QTAILQ_REMOVE(queue, item, node);
g_free(item);
}
}

static void qemu_input_queue_delay(struct 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);

if (start_timer) {
timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL)
+ item->delay_ms);
}
}

static void qemu_input_queue_event(struct QemuInputEventQueueHead *queue,
QemuConsole *src, InputEvent *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);
}

static void qemu_input_queue_sync(struct QemuInputEventQueueHead *queue)
{
QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1);

item->type = QEMU_INPUT_QUEUE_SYNC;
QTAILQ_INSERT_TAIL(queue, item, node);
}

void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
{
QemuInputHandlerState *s;
Expand Down Expand Up @@ -230,9 +317,14 @@ void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down)
{
InputEvent *evt;
evt = qemu_input_event_new_key(key, down);
qemu_input_event_send(src, evt);
qemu_input_event_sync();
qapi_free_InputEvent(evt);
if (QTAILQ_EMPTY(&kbd_queue)) {
qemu_input_event_send(src, evt);
qemu_input_event_sync();
qapi_free_InputEvent(evt);
} else {
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)
Expand All @@ -251,6 +343,16 @@ void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down)
qemu_input_event_send_key(src, key, down);
}

void qemu_input_event_send_key_delay(uint32_t delay_ms)
{
if (!kbd_timer) {
kbd_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, qemu_input_queue_process,
&kbd_queue);
}
qemu_input_queue_delay(&kbd_queue, kbd_timer,
delay_ms ? delay_ms : kbd_default_delay_ms);
}

InputEvent *qemu_input_event_new_btn(InputButton btn, bool down)
{
InputEvent *evt = g_new0(InputEvent, 1);
Expand Down
2 changes: 2 additions & 0 deletions ui/vnc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,9 @@ static void press_key(VncState *vs, int keysym)
{
int keycode = keysym2scancode(vs->vd->kbd_layout, keysym) & SCANCODE_KEYMASK;
qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, true);
qemu_input_event_send_key_delay(0);
qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false);
qemu_input_event_send_key_delay(0);
}

static int current_led_state(VncState *vs)
Expand Down

0 comments on commit d4f005d

Please sign in to comment.