aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/multiseat.txt78
-rw-r--r--include/ui/input.h1
-rw-r--r--ui/curses.c10
-rw-r--r--ui/input-legacy.c45
-rw-r--r--ui/input.c108
-rw-r--r--ui/vnc.c2
6 files changed, 176 insertions, 68 deletions
diff --git a/docs/multiseat.txt b/docs/multiseat.txt
index a6c71dd74f..67151e0849 100644
--- a/docs/multiseat.txt
+++ b/docs/multiseat.txt
@@ -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
@@ -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
----------
@@ -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!
diff --git a/include/ui/input.h b/include/ui/input.h
index aa99b0cac7..5d5ac00663 100644
--- a/include/ui/input.h
+++ b/include/ui/input.h
@@ -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);
diff --git a/ui/curses.c b/ui/curses.c
index de85f7610f..8edb038bb3 100644
--- a/ui/curses.c
+++ b/ui/curses.c
@@ -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];
diff --git a/ui/input-legacy.c b/ui/input-legacy.c
index 2a538607a2..3025f50f49 100644
--- a/ui/input-legacy.c
+++ b/ui/input-legacy.c
@@ -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);
@@ -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,
diff --git a/ui/input.c b/ui/input.c
index 14c9434f5e..89d9db78c0 100644
--- a/ui/input.c
+++ b/ui/input.c
@@ -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)
{
@@ -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;
@@ -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)
@@ -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);
diff --git a/ui/vnc.c b/ui/vnc.c
index 61b1f933bf..1684206184 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -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)