updates for docs/multiseat.txt
input: add support for kbd delays -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJTjsk/AAoJEEy22O7T6HE4llkP/Akl8GAwrjs+sUVB/I5iUthm vdgGBoH3R8VsuNt2LZIEWF+Cx1Nu4Bnrgmzd8K/TmGyWQp0r0Tb+BDyr7D/fMwBQ IwuBu7Hm1xolm9XTzgKZ6yaapAQzRdz5Ycsv9gKH+QCnrWrWc1dAz3/yUf2clpBS lOxwpfbvBC9eYDJIadse4DXJHdpXELw5B5BZDgC/BJu8qHMfApmdbc6WAxYIhV2w 2WemqFxFkg61I0Wh9/ngVxC9hpi6C0/u5TpjUiB+U6S6gypBvV8DQ45WLWvT3ZUO lpA4K79CifOg1XGLG45+EOGlInRWXuMI8A4qJw8SsOKWveaZdyALzImC1Xai++a3 ICCrrzcgtOWVScLApHc+n8txQJ5qrKBe46++T5tovMf7kE12NEzJT5jqPHrLg84j ZSyaKzjyZ9E+/b4fkA9Tk1obQ1FMs0OX2MKrBtxbI7sBsD3eCY3ZQ7X/exEyykMs +BTG4t9koOtlSt5YAmQ1OQgHuat9c5zeIQT5eRjU3Ti61UUdvreZeQa7opEv6Gs8 YN842oT7e8Paef5Wi8yKtbH7A/6ZG6YrJE5/8RrDLWGdEIxGpTdyrvUujIwP8aEr 5tlK0bG7avv5A9sKoowj6dLdL+JUyL8IoWQambKhuQ1rZhPhZHXPBZLQ2Gq89/Gp 1VR5P454ThZjOc6kBG9V =iNvW -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kraxel/tags/pull-input-10' into 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>
This commit is contained in:
commit
d4f005db9b
@ -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:
|
||||
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:
|
||||
|
||||
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
|
||||
[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
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
BTW: loginctl writes udev rules to /etc/udev/rules.d to make these
|
||||
device assignments permanent, so you need to do this only once.
|
||||
[root@fedora ~]# cat /etc/udev/rules.d/70-qemu-autoseat.rules
|
||||
SUBSYSTEMS=="pci", DEVPATH=="*/0000:00:12.0", TAG+="seat", ENV{ID_AUTOSEAT}="1"
|
||||
|
||||
Now simply restart gdm (rebooting will do too), and a login screen
|
||||
should show up on the second head.
|
||||
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!
|
||||
|
||||
|
@ -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);
|
||||
|
10
ui/curses.c
10
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];
|
||||
|
@ -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,
|
||||
|
108
ui/input.c
108
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);
|
||||
|
2
ui/vnc.c
2
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)
|
||||
|
Loading…
Reference in New Issue
Block a user