All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver.
@ 2013-06-26 11:38 Gerd Hoffmann
  2013-06-26 11:38 ` [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query Gerd Hoffmann
  2013-06-26 15:48 ` [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Luiz Capitulino
  0 siblings, 2 replies; 11+ messages in thread
From: Gerd Hoffmann @ 2013-06-26 11:38 UTC (permalink / raw)
  To: qemu-devel; +Cc: Anthony Liguori, Gerd Hoffmann

Display works, requires truecolor framebuffer with 16 or 32 bpp on the
host.  32bpp is recommended.  The framebuffer is used as-is, qemu
doesn't try to switch modes.  With LCD displays mode switching is pretty
pointless IMHO, also it wouldn't work anyway with the most common
fbdev drivers (vesafb, KMS).  Guest display is centered on the host
screen.

Mouse works, uses /dev/input/mice.

Keyboard works.  Guest screen has whatever keymap you load inside
the guest.  Text windows (monitor, serial, ...) have a simple en-us
keymap.  Good enough to type monitor commands.  Not goot enough to
work seriously on a serial terminal.  But the qemu terminal emulation
isn't good enough for that anyway ;)

Hot keys:
  Ctrl-Alt-F<nr>  -> host console switching.
  Ctrl-Alt-<nr>   -> qemu console switching.
  Ctrl-Alt-S      -> toggle display scaling.
  Ctrl-Alt-ESC    -> exit qemu.

Special feature:  Sane console switching.  Switching away stops screen
updates.  Switching back redraws the screen.  When started from the
linux console qemu uses the vt you've started it from (requires just
read/write access to /dev/fb0).  When starting from somewhere else qemu
tries to open a unused virtual terminal and switch to it (usually
requires root privileges to open /dev/tty<nr>).

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 configure               |   12 +
 include/sysemu/sysemu.h |    1 +
 include/ui/console.h    |    4 +
 qemu-options.hx         |    4 +-
 trace-events            |   14 +
 ui/Makefile.objs        |    1 +
 ui/fbdev.c              | 1164 +++++++++++++++++++++++++++++++++++++++++++++++
 ui/linux-keynames.h     |  388 ++++++++++++++++
 vl.c                    |   21 +
 9 files changed, 1608 insertions(+), 1 deletion(-)
 create mode 100644 ui/fbdev.c
 create mode 100644 ui/linux-keynames.h

diff --git a/configure b/configure
index 0e0adde..391e645 100755
--- a/configure
+++ b/configure
@@ -158,6 +158,7 @@ fdt=""
 nptl=""
 pixman=""
 sdl=""
+fbdev="no"
 virtfs=""
 vnc="yes"
 sparse="no"
@@ -549,6 +550,7 @@ Haiku)
   kvm="yes"
   vhost_net="yes"
   vhost_scsi="yes"
+  fbdev="yes"
   if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
     audio_possible_drivers="$audio_possible_drivers fmod"
   fi
@@ -700,6 +702,10 @@ for opt do
   ;;
   --enable-qom-cast-debug) qom_cast_debug="yes"
   ;;
+  --disable-fbdev) fbdev="no"
+  ;;
+  --enable-fbdev) fbdev="yes"
+  ;;
   --disable-virtfs) virtfs="no"
   ;;
   --enable-virtfs) virtfs="yes"
@@ -1051,6 +1057,8 @@ echo "  --disable-sdl            disable SDL"
 echo "  --enable-sdl             enable SDL"
 echo "  --disable-gtk            disable gtk UI"
 echo "  --enable-gtk             enable gtk UI"
+echo "  --disable-fbdev          disable linux framebuffer"
+echo "  --enable-fbdev           enable linux framebuffer"
 echo "  --disable-virtfs         disable VirtFS"
 echo "  --enable-virtfs          enable VirtFS"
 echo "  --disable-vnc            disable VNC"
@@ -3492,6 +3500,7 @@ fi
 echo "pixman            $pixman"
 echo "SDL support       $sdl"
 echo "GTK support       $gtk"
+echo "fbdev support     $fbdev"
 echo "curses support    $curses"
 echo "curl support      $curl"
 echo "mingw32 support   $mingw32"
@@ -3722,6 +3731,9 @@ if test "$sdl" = "yes" ; then
   echo "CONFIG_SDL=y" >> $config_host_mak
   echo "SDL_CFLAGS=$sdl_cflags" >> $config_host_mak
 fi
+if test "$fbdev" = "yes" ; then
+  echo "CONFIG_FBDEV=y" >> $config_host_mak
+fi
 if test "$cocoa" = "yes" ; then
   echo "CONFIG_COCOA=y" >> $config_host_mak
 fi
diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h
index 2fb71af..5922311 100644
--- a/include/sysemu/sysemu.h
+++ b/include/sysemu/sysemu.h
@@ -91,6 +91,7 @@ typedef enum DisplayType
     DT_CURSES,
     DT_SDL,
     DT_GTK,
+    DT_FBDEV,
     DT_NOGRAPHIC,
     DT_NONE,
 } DisplayType;
diff --git a/include/ui/console.h b/include/ui/console.h
index 98edf41..71b538a 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -308,6 +308,10 @@ void register_vc_handler(VcHandler *handler);
 /* sdl.c */
 void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
 
+/* fbdev.c */
+int fbdev_display_init(const char *device, bool scale, Error **err);
+void fbdev_display_uninit(void);
+
 /* cocoa.m */
 void cocoa_display_init(DisplayState *ds, int full_screen);
 
diff --git a/qemu-options.hx b/qemu-options.hx
index ca6fdf6..259e76f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -804,7 +804,7 @@ ETEXI
 
 DEF("display", HAS_ARG, QEMU_OPTION_display,
     "-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]\n"
-    "            [,window_close=on|off]|curses|none|\n"
+    "            [,window_close=on|off]|curses|fbdev|none|\n"
     "            vnc=<display>[,<optargs>]\n"
     "                select display type\n", QEMU_ARCH_ALL)
 STEXI
@@ -822,6 +822,8 @@ support a text mode, QEMU can display this output using a
 curses/ncurses interface. Nothing is displayed when the graphics
 device is in graphical mode or if the graphics device does not support
 a text mode. Generally only the VGA device models support text mode.
+@item fbdev
+Display video output on a linux framebuffer console.
 @item none
 Do not display video output. The guest will still see an emulated
 graphics card, but its output will not be displayed to the QEMU
diff --git a/trace-events b/trace-events
index c5f1ccb..7a73b2a 100644
--- a/trace-events
+++ b/trace-events
@@ -1161,3 +1161,17 @@ kvm_run_exit(int cpu_index, uint32_t reason) "cpu_index %d, reason %d"
 # qom/object.c
 object_dynamic_cast_assert(const char *type, const char *target, const char *file, int line, const char *func) "%s->%s (%s:%d:%s)"
 object_class_dynamic_cast_assert(const char *type, const char *target, const char *file, int line, const char *func) "%s->%s (%s:%d:%s)"
+
+# ui/fbdev.c
+fbdev_enabled(void) ""
+fbdev_cleanup(void) ""
+fbdev_vt_activate(int vtno, int wait) "vtno %d, wait %d"
+fbdev_vt_activated(void) ""
+fbdev_vt_release_request(void) ""
+fbdev_vt_released(void) ""
+fbdev_vt_aquire_request(void) ""
+fbdev_vt_aquired(void) ""
+fbdev_kbd_raw(int enable) "enable %d"
+fbdev_kbd_event(int keycode, const char *kname, int up) "keycode 0x%x [%s], down %d"
+fbdev_dpy_resize(int w, int h) "%dx%d"
+fbdev_dpy_redraw(void)
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index 6ddc0de..ee96ad5 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -14,6 +14,7 @@ common-obj-$(CONFIG_COCOA) += cocoa.o
 common-obj-$(CONFIG_CURSES) += curses.o
 common-obj-$(CONFIG_VNC) += $(vnc-obj-y)
 common-obj-$(CONFIG_GTK) += gtk.o x_keymap.o
+common-obj-$(CONFIG_FBDEV) += fbdev.o
 
 $(obj)/sdl.o $(obj)/sdl_zoom.o: QEMU_CFLAGS += $(SDL_CFLAGS) 
 
diff --git a/ui/fbdev.c b/ui/fbdev.c
new file mode 100644
index 0000000..91e11e5
--- /dev/null
+++ b/ui/fbdev.c
@@ -0,0 +1,1164 @@
+/*
+ * linux fbdev output driver.
+ *
+ * Author: Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <termios.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <linux/fb.h>
+
+#include "qemu-common.h"
+#include "keymaps.h"
+#include "ui/qemu-pixman.h"
+#include "ui/console.h"
+#include "sysemu/sysemu.h"
+
+/*
+ * must be last so we get the linux input layer
+ * KEY_* defines, not the ncurses ones.
+ */
+#include <linux/input.h>
+
+/* -------------------------------------------------------------------- */
+
+typedef struct FBDevState {
+    /* file handles */
+    int                        tty, fb, mice;
+
+    /* saved state, for restore on exit */
+    int                        orig_vtno;
+    int                        kd_omode;
+    struct vt_mode             vt_omode;
+    struct fb_var_screeninfo   fb_ovar;
+
+    /* framebuffer */
+    char                       *device;
+    struct fb_fix_screeninfo   fb_fix;
+    struct fb_var_screeninfo   fb_var;
+    uint8_t                    *fb_mem;
+    int                        fb_mem_offset;
+
+    /* linux console */
+    int                        vtno;
+    struct vt_mode             vt_mode;
+    struct termios             tty_attributes;
+    unsigned long              tty_mode;
+    unsigned int               tty_flags;
+    bool                       tty_mediumraw;
+    bool                       key_down[KEY_CNT];
+
+    /* qemu windup */
+    DisplayChangeListener      dcl;
+    int                        resize_screen;
+    int                        redraw_screen;
+    int                        cx, cy, cw, ch;
+    Notifier                   exit_notifier;
+    DisplaySurface             *surface;
+    pixman_image_t             *sref, *swork;
+    pixman_image_t             *framebuffer;
+    pixman_transform_t         transform;
+    pixman_region16_t          dirty;
+    double                     scale;
+
+    QEMUCursor                 *ptr_cursor;
+    pixman_image_t             *ptr_image;
+    int                        ptr_refresh;
+    int                        px, py, pw, ph;
+    int                        mx, my, mon, ax, ay;
+
+    /* options */
+    int                        use_scale;
+    pixman_filter_t            pfilter;
+} FBDevState;
+
+static FBDevState *fb;
+
+/* console switching */
+#define SIG_ACQ      (SIGRTMIN+6)
+#define SIG_REL      (SIGRTMIN+7)
+#define FB_ACTIVE    0
+#define FB_REL_REQ   1
+#define FB_INACTIVE  2
+#define FB_ACQ_REQ   3
+int fb_switch_state;
+
+/* fwd decls */
+static int fbdev_activate_vt(int tty, int vtno, bool wait);
+
+/* -------------------------------------------------------------------- */
+/* pixman helpers                                                       */
+
+static pixman_image_t *pixman_from_framebuffer(FBDevState *s)
+{
+    pixman_format_code_t format;
+    pixman_image_t *image;
+    int type;
+
+    type = qemu_pixman_get_type(s->fb_var.red.offset,
+                                s->fb_var.green.offset,
+                                s->fb_var.blue.offset);
+    format = PIXMAN_FORMAT(s->fb_var.bits_per_pixel, type,
+                           s->fb_var.transp.length,
+                           s->fb_var.red.length,
+                           s->fb_var.green.length,
+                           s->fb_var.blue.length);
+    image = pixman_image_create_bits(format, s->fb_var.xres, s->fb_var.yres,
+                                     (void *)s->fb_mem, s->fb_fix.line_length);
+    return image;
+}
+
+static pixman_image_t *pixman_image_clone(pixman_image_t *i)
+{
+    return pixman_image_create_bits(pixman_image_get_format(i),
+                                    pixman_image_get_width(i),
+                                    pixman_image_get_height(i),
+                                    pixman_image_get_data(i),
+                                    pixman_image_get_stride(i));
+}
+
+/* -------------------------------------------------------------------- */
+/* mouse                                                                */
+
+static void read_mouse(void *opaque)
+{
+    FBDevState *s = opaque;
+    int8_t buf[3];
+    int rc, x, y, b;
+
+    rc = read(s->mice, buf, sizeof(buf));
+    if (rc != sizeof(buf)) {
+        return;
+    }
+
+    if (fb_switch_state != FB_ACTIVE) {
+        return;
+    }
+
+    x = buf[1];
+    y = -buf[2];
+    b = buf[0] & 0x7;
+
+    if (kbd_mouse_is_absolute()) {
+        s->ax += x; s->ay += y;
+        if (s->ax < 0) {
+            s->ax = 0;
+        }
+        if (s->ay < 0) {
+            s->ay = 0;
+        }
+        if (s->ax >= s->cw * s->scale) {
+            s->ax = s->cw * s->scale - 1;
+        }
+        if (s->ay >= s->ch * s->scale) {
+            s->ay = s->ch * s->scale-1;
+        }
+        kbd_mouse_event(s->ax * 0x7FFF / (s->cw * s->scale),
+                        s->ay * 0x7FFF / (s->ch * s->scale), 0, b);
+    } else {
+        kbd_mouse_event(x, y, 0, b);
+    }
+}
+
+static int init_mouse(FBDevState *s)
+{
+    s->mice = open("/dev/input/mice", O_RDONLY);
+    if (s->mice == -1) {
+        return -1;
+    }
+    qemu_set_fd_handler(s->mice, read_mouse, NULL, s);
+    return 0;
+}
+
+static void uninit_mouse(FBDevState *s)
+{
+    if (s->mice == -1) {
+        return;
+    }
+    qemu_set_fd_handler(s->mice, NULL, NULL, NULL);
+    close(s->mice);
+    s->mice = -1;
+}
+
+/* -------------------------------------------------------------------- */
+/* keyboard                                                             */
+
+static const char *keynames[] = {
+#include "linux-keynames.h"
+};
+
+static const int scancode_map[KEY_CNT] = {
+    [KEY_ESC]           = 0x01,
+    [KEY_1]             = 0x02,
+    [KEY_2]             = 0x03,
+    [KEY_3]             = 0x04,
+    [KEY_4]             = 0x05,
+    [KEY_5]             = 0x06,
+    [KEY_6]             = 0x07,
+    [KEY_7]             = 0x08,
+    [KEY_8]             = 0x09,
+    [KEY_9]             = 0x0a,
+    [KEY_0]             = 0x0b,
+    [KEY_MINUS]         = 0x0c,
+    [KEY_EQUAL]         = 0x0d,
+    [KEY_BACKSPACE]     = 0x0e,
+
+    [KEY_TAB]           = 0x0f,
+    [KEY_Q]             = 0x10,
+    [KEY_W]             = 0x11,
+    [KEY_E]             = 0x12,
+    [KEY_R]             = 0x13,
+    [KEY_T]             = 0x14,
+    [KEY_Y]             = 0x15,
+    [KEY_U]             = 0x16,
+    [KEY_I]             = 0x17,
+    [KEY_O]             = 0x18,
+    [KEY_P]             = 0x19,
+    [KEY_LEFTBRACE]     = 0x1a,
+    [KEY_RIGHTBRACE]    = 0x1b,
+    [KEY_ENTER]         = 0x1c,
+
+    [KEY_A]             = 0x1e,
+    [KEY_S]             = 0x1f,
+    [KEY_D]             = 0x20,
+    [KEY_F]             = 0x21,
+    [KEY_G]             = 0x22,
+    [KEY_H]             = 0x23,
+    [KEY_J]             = 0x24,
+    [KEY_K]             = 0x25,
+    [KEY_L]             = 0x26,
+    [KEY_SEMICOLON]     = 0x27,
+    [KEY_APOSTROPHE]    = 0x28,
+    [KEY_GRAVE]         = 0x29,
+    [KEY_LEFTSHIFT]     = 0x2a,
+    [KEY_BACKSLASH]     = 0x2b,
+
+    [KEY_Z]             = 0x2c,
+    [KEY_X]             = 0x2d,
+    [KEY_C]             = 0x2e,
+    [KEY_V]             = 0x2f,
+    [KEY_B]             = 0x30,
+    [KEY_N]             = 0x31,
+    [KEY_M]             = 0x32,
+    [KEY_COMMA]         = 0x33,
+    [KEY_DOT]           = 0x34,
+    [KEY_SLASH]         = 0x35,
+    [KEY_RIGHTSHIFT]    = 0x36,
+    [KEY_SPACE]         = 0x39,
+
+    [KEY_F1]            = 0x3b,
+    [KEY_F2]            = 0x3c,
+    [KEY_F3]            = 0x3d,
+    [KEY_F4]            = 0x3e,
+    [KEY_F5]            = 0x3f,
+    [KEY_F6]            = 0x40,
+    [KEY_F7]            = 0x41,
+    [KEY_F8]            = 0x42,
+    [KEY_F9]            = 0x43,
+    [KEY_F10]           = 0x44,
+    [KEY_F11]           = 0x57,
+    [KEY_F12]           = 0x58,
+
+    [KEY_SYSRQ]         = 0xb7,
+    [KEY_SCROLLLOCK]    = 0x46,
+#if 0
+    [KEY_PAUSE]         = FIXME,
+#endif
+    [KEY_CAPSLOCK]      = 0x3a,
+    [KEY_102ND]         = 0x56,
+
+    [KEY_LEFTCTRL]      = 0x1d,
+    [KEY_LEFTMETA]      = 0xdb,
+    [KEY_LEFTALT]       = 0x38,
+    [KEY_RIGHTALT]      = 0xb8,
+    [KEY_RIGHTMETA]     = 0xdc,
+    [KEY_RIGHTCTRL]     = 0x9d,
+    [KEY_COMPOSE]       = 0xdd,
+
+    [KEY_INSERT]        = 0xd2,
+    [KEY_DELETE]        = 0xd3,
+    [KEY_HOME]          = 0xc7,
+    [KEY_END]           = 0xcf,
+    [KEY_PAGEUP]        = 0xc9,
+    [KEY_PAGEDOWN]      = 0xd1,
+
+    [KEY_UP]            = 0xc8,
+    [KEY_LEFT]          = 0xcb,
+    [KEY_RIGHT]         = 0xcd,
+    [KEY_DOWN]          = 0xd0,
+
+    [KEY_NUMLOCK]       = 0x45,
+    [KEY_KPSLASH]       = 0xb5,
+    [KEY_KPASTERISK]    = 0x37,
+    [KEY_KP7]           = 0x47,
+    [KEY_KP8]           = 0x48,
+    [KEY_KP9]           = 0x49,
+    [KEY_KPMINUS]       = 0x4a,
+    [KEY_KP4]           = 0x4b,
+    [KEY_KP5]           = 0x4c,
+    [KEY_KP6]           = 0x4d,
+    [KEY_KPPLUS]        = 0x4e,
+    [KEY_KP1]           = 0x4f,
+    [KEY_KP2]           = 0x50,
+    [KEY_KP3]           = 0x51,
+    [KEY_KP0]           = 0x52,
+    [KEY_KPDOT]         = 0x53,
+    [KEY_KPENTER]       = 0x9c,
+};
+
+static const struct keysym_map {
+    int  normal, shifted;
+} keysym_map_en_us[KEY_CNT] = {
+    [KEY_A] = { .normal = 'a', .shifted = 'A' },
+    [KEY_B] = { .normal = 'b', .shifted = 'B' },
+    [KEY_C] = { .normal = 'c', .shifted = 'C' },
+    [KEY_D] = { .normal = 'd', .shifted = 'D' },
+    [KEY_E] = { .normal = 'e', .shifted = 'E' },
+    [KEY_F] = { .normal = 'f', .shifted = 'F' },
+    [KEY_G] = { .normal = 'g', .shifted = 'G' },
+    [KEY_H] = { .normal = 'h', .shifted = 'H' },
+    [KEY_I] = { .normal = 'i', .shifted = 'I' },
+    [KEY_J] = { .normal = 'j', .shifted = 'J' },
+    [KEY_K] = { .normal = 'k', .shifted = 'K' },
+    [KEY_L] = { .normal = 'l', .shifted = 'L' },
+    [KEY_M] = { .normal = 'm', .shifted = 'M' },
+    [KEY_N] = { .normal = 'n', .shifted = 'N' },
+    [KEY_O] = { .normal = 'o', .shifted = 'O' },
+    [KEY_P] = { .normal = 'p', .shifted = 'P' },
+    [KEY_Q] = { .normal = 'q', .shifted = 'Q' },
+    [KEY_R] = { .normal = 'r', .shifted = 'R' },
+    [KEY_S] = { .normal = 's', .shifted = 'S' },
+    [KEY_T] = { .normal = 't', .shifted = 'T' },
+    [KEY_U] = { .normal = 'u', .shifted = 'U' },
+    [KEY_V] = { .normal = 'v', .shifted = 'V' },
+    [KEY_W] = { .normal = 'w', .shifted = 'W' },
+    [KEY_X] = { .normal = 'x', .shifted = 'X' },
+    [KEY_Y] = { .normal = 'y', .shifted = 'Y' },
+    [KEY_Z] = { .normal = 'z', .shifted = 'Z' },
+
+    [KEY_1] = { .normal = '1', .shifted = '!' },
+    [KEY_2] = { .normal = '2', .shifted = '@' },
+    [KEY_3] = { .normal = '3', .shifted = '#' },
+    [KEY_4] = { .normal = '4', .shifted = '$' },
+    [KEY_5] = { .normal = '5', .shifted = '%' },
+    [KEY_6] = { .normal = '6', .shifted = '^' },
+    [KEY_7] = { .normal = '7', .shifted = '&' },
+    [KEY_8] = { .normal = '8', .shifted = '*' },
+    [KEY_9] = { .normal = '9', .shifted = '(' },
+    [KEY_0] = { .normal = '0', .shifted = ')' },
+
+    [KEY_MINUS]       = { .normal = '-',  .shifted = '_'  },
+    [KEY_EQUAL]       = { .normal = '=',  .shifted = '+'  },
+    [KEY_TAB]         = { .normal = '\t'  },
+    [KEY_LEFTBRACE]   = { .normal = '[',  .shifted = '{'  },
+    [KEY_RIGHTBRACE]  = { .normal = ']',  .shifted = '}'  },
+    [KEY_ENTER]       = { .normal = '\n', },
+    [KEY_SEMICOLON]   = { .normal = ';',  .shifted = ':'  },
+    [KEY_APOSTROPHE]  = { .normal = '"',  .shifted = '\'' },
+    [KEY_BACKSLASH]   = { .normal = '\\', .shifted = '|'  },
+    [KEY_COMMA]       = { .normal = ',',  .shifted = '<'  },
+    [KEY_DOT]         = { .normal = '.',  .shifted = '>'  },
+    [KEY_SLASH]       = { .normal = '/',  .shifted = '?'  },
+    [KEY_SPACE]       = { .normal = ' '   },
+
+    [KEY_BACKSPACE]   = { .normal = QEMU_KEY_BACKSPACE  },
+    [KEY_UP]          = { .normal = QEMU_KEY_UP         },
+    [KEY_DOWN]        = { .normal = QEMU_KEY_DOWN       },
+    [KEY_LEFT]        = { .normal = QEMU_KEY_LEFT       },
+    [KEY_RIGHT]       = { .normal = QEMU_KEY_RIGHT      },
+};
+
+static void start_mediumraw(FBDevState *s)
+{
+    struct termios tattr;
+
+    if (s->tty_mediumraw) {
+        return;
+    }
+    trace_fbdev_kbd_raw(1);
+
+    /* save state */
+    tcgetattr(s->tty, &s->tty_attributes);
+    ioctl(s->tty, KDGKBMODE, &s->tty_mode);
+    s->tty_flags = fcntl(s->tty, F_GETFL, NULL);
+
+    /* setup */
+    tattr = s->tty_attributes;
+    tattr.c_cflag &= ~(IXON|IXOFF);
+    tattr.c_lflag &= ~(ICANON|ECHO|ISIG);
+    tattr.c_iflag = 0;
+    tattr.c_cc[VMIN] = 1;
+    tattr.c_cc[VTIME] = 0;
+    tcsetattr(s->tty, TCSAFLUSH, &tattr);
+    ioctl(s->tty, KDSKBMODE, K_MEDIUMRAW);
+    fcntl(s->tty, F_SETFL, s->tty_flags | O_NONBLOCK);
+
+    s->tty_mediumraw = true;
+}
+
+static void stop_mediumraw(FBDevState *s)
+{
+    if (!s->tty_mediumraw) {
+        return;
+    }
+    trace_fbdev_kbd_raw(0);
+
+    /* restore state */
+    tcsetattr(s->tty, TCSANOW, &s->tty_attributes);
+    ioctl(s->tty, KDSKBMODE, s->tty_mode);
+    fcntl(s->tty, F_SETFL, s->tty_flags);
+
+    s->tty_mediumraw = false;
+}
+
+static void send_scancode(int keycode, int up)
+{
+    int scancode = scancode_map[keycode];
+
+    if (!scancode) {
+        fprintf(stderr, "%s: unmapped key: 0x%x %s\n",
+                __func__, keycode, keynames[keycode]);
+        return;
+    }
+    if (scancode & SCANCODE_GREY) {
+        kbd_put_keycode(SCANCODE_EMUL0);
+    }
+    if (up) {
+        kbd_put_keycode(scancode | SCANCODE_UP);
+    } else {
+        kbd_put_keycode(scancode & SCANCODE_KEYCODEMASK);
+    }
+}
+
+static void send_keysym(int keycode, int shift)
+{
+    const struct keysym_map *keysym_map = keysym_map_en_us;
+    int keysym;
+
+    if (shift && keysym_map[keycode].shifted) {
+        keysym = keysym_map[keycode].shifted;
+    } else if (keysym_map[keycode].normal) {
+        keysym = keysym_map[keycode].normal;
+    } else {
+        fprintf(stderr, "%s: unmapped key: 0x%x %s\n",
+                __func__, keycode, keynames[keycode]);
+        return;
+    }
+    kbd_put_keysym(keysym);
+}
+
+static void reset_keys(FBDevState *s)
+{
+    int keycode;
+
+    for (keycode = 0; keycode < KEY_MAX; keycode++) {
+        if (s->key_down[keycode]) {
+            if (qemu_console_is_graphic(NULL)) {
+                send_scancode(keycode, 1);
+            }
+            s->key_down[keycode] = false;
+        }
+    }
+}
+
+static void read_mediumraw(void *opaque)
+{
+    FBDevState *s = opaque;
+    uint8_t buf[32];
+    int i, rc, up, keycode;
+    bool ctrl, alt, shift;
+
+    rc = read(s->tty, buf, sizeof(buf));
+    switch (rc) {
+    case -1:
+        perror("read tty");
+        goto err;
+    case 0:
+        fprintf(stderr, "%s: eof\n", __func__);
+        goto err;
+    default:
+        for (i = 0; i < rc; i++) {
+            up      = buf[i] & 0x80;
+            keycode = buf[i] & 0x7f;
+            if (keycode == 0) {
+                keycode  = (buf[i+1] & 0x7f) << 7;
+                keycode |= buf[i+2] & 0x7f;
+                i += 2;
+            }
+            if (keycode > KEY_MAX) {
+                continue;
+            }
+
+            if (up) {
+                if (!s->key_down[keycode]) {
+                    continue;
+                }
+                s->key_down[keycode] = false;
+            } else {
+                s->key_down[keycode] = true;
+            }
+
+            trace_fbdev_kbd_event(keycode, keynames[keycode], !up);
+
+            alt   = s->key_down[KEY_LEFTALT]   || s->key_down[KEY_RIGHTALT];
+            ctrl  = s->key_down[KEY_LEFTCTRL]  || s->key_down[KEY_RIGHTCTRL];
+            shift = s->key_down[KEY_LEFTSHIFT] || s->key_down[KEY_RIGHTSHIFT];
+
+            if (ctrl && alt && !up) {
+                if (keycode == KEY_ESC) {
+                    fprintf(stderr, "=== fbdev emergency escape "
+                            "(ctrl-alt-esc) ===\n");
+                    exit(1);
+                }
+                if (keycode == KEY_S) {
+                    s->use_scale = !s->use_scale;
+                    s->resize_screen++;
+                    s->redraw_screen++;
+                    continue;
+                }
+                if (keycode >= KEY_F1 && keycode <= KEY_F10) {
+                    fbdev_activate_vt(s->tty, keycode+1-KEY_F1, false);
+                    s->key_down[keycode] = false;
+                    continue;
+                }
+                if (keycode >= KEY_1 && keycode <= KEY_9) {
+                    console_select(keycode-KEY_1);
+                    reset_keys(s);
+                    continue;
+                }
+            }
+
+            if (qemu_console_is_graphic(NULL)) {
+                /* send scancode to guest kbd emulation */
+                send_scancode(keycode, up);
+            } else if (!up) {
+                /* send keysym to text console (aka '-chardev vc') */
+                send_keysym(keycode, shift);
+            }
+        }
+    }
+    return;
+
+err:
+    exit(1);
+}
+
+/* -------------------------------------------------------------------- */
+
+static void fbdev_cls(FBDevState *s)
+{
+    memset(s->fb_mem + s->fb_mem_offset, 0,
+           s->fb_fix.line_length * s->fb_var.yres);
+}
+
+static int fbdev_activate_vt(int tty, int vtno, bool wait)
+{
+    trace_fbdev_vt_activate(vtno, wait);
+
+    if (ioctl(tty, VT_ACTIVATE, vtno) < 0) {
+        perror("ioctl VT_ACTIVATE");
+        return -1;
+    }
+
+    if (wait) {
+        if (ioctl(tty, VT_WAITACTIVE, vtno) < 0) {
+            perror("ioctl VT_WAITACTIVE");
+            return -1;
+        }
+        trace_fbdev_vt_activated();
+    }
+
+    return 0;
+}
+
+static void fbdev_cleanup(FBDevState *s)
+{
+    trace_fbdev_cleanup();
+
+    /* release pixman stuff */
+    pixman_region_fini(&s->dirty);
+    if (s->framebuffer) {
+        pixman_image_unref(s->framebuffer);
+        s->framebuffer = NULL;
+    }
+    if (s->sref) {
+        pixman_image_unref(s->sref);
+        s->sref = NULL;
+    }
+    if (s->swork) {
+        pixman_image_unref(s->swork);
+        s->swork = NULL;
+    }
+
+    /* restore console */
+    if (s->fb_mem != NULL) {
+        munmap(s->fb_mem, s->fb_fix.smem_len+s->fb_mem_offset);
+        s->fb_mem = NULL;
+    }
+    if (s->fb != -1) {
+        if (ioctl(s->fb, FBIOPUT_VSCREENINFO, &s->fb_ovar) < 0) {
+            perror("ioctl FBIOPUT_VSCREENINFO");
+        }
+        close(s->fb);
+        s->fb = -1;
+    }
+
+    if (s->tty != -1) {
+        stop_mediumraw(s);
+        if (ioctl(s->tty, KDSETMODE, s->kd_omode) < 0) {
+            perror("ioctl KDSETMODE");
+        }
+        if (ioctl(s->tty, VT_SETMODE, &s->vt_omode) < 0) {
+            perror("ioctl VT_SETMODE");
+        }
+        if (s->orig_vtno) {
+            fbdev_activate_vt(s->tty, s->orig_vtno, true);
+        }
+        qemu_set_fd_handler(s->tty, NULL, NULL, NULL);
+        close(s->tty);
+        s->tty = -1;
+    }
+
+    g_free(s->device);
+    s->device = NULL;
+}
+
+static int fbdev_init(FBDevState *s, const char *device, Error **err)
+{
+    struct vt_stat vts;
+    unsigned long page_mask;
+    char ttyname[32];
+
+    /* open framebuffer */
+    if (device == NULL) {
+        device = getenv("FRAMEBUFFER");
+    }
+    if (device == NULL) {
+        device = "/dev/fb0";
+    }
+    s->fb = open(device, O_RDWR);
+    if (s->fb == -1) {
+        error_setg(err, "open %s: %s\n", device, strerror(errno));
+        return -1;
+    }
+
+    /* open virtual console */
+    s->tty = 0;
+    if (ioctl(s->tty, VT_GETSTATE, &vts) < 0) {
+        fprintf(stderr, "Not started from virtual terminal, "
+                "trying to open one.\n");
+
+        snprintf(ttyname, sizeof(ttyname), "/dev/tty0");
+        s->tty = open(ttyname, O_RDWR);
+        if (s->tty == -1) {
+            error_setg(err, "open %s: %s\n", ttyname, strerror(errno));
+            goto err_early;
+        }
+        if (ioctl(s->tty, VT_OPENQRY, &s->vtno) < 0) {
+            error_setg(err, "ioctl VT_OPENQRY: %s\n", strerror(errno));
+            goto err_early;
+        }
+        if (ioctl(s->tty, VT_GETSTATE, &vts) < 0) {
+            error_setg(err, "ioctl VT_GETSTATE: %s\n", strerror(errno));
+            goto err_early;
+        }
+        close(s->tty);
+
+        snprintf(ttyname, sizeof(ttyname), "/dev/tty%d", s->vtno);
+        s->tty = open(ttyname, O_RDWR);
+        if (s->tty == -1) {
+            error_setg(err, "open %s: %s\n", ttyname, strerror(errno));
+            goto err_early;
+        }
+        s->orig_vtno = vts.v_active;
+        fprintf(stderr, "Switching to vt %d (current %d).\n",
+                s->vtno, s->orig_vtno);
+    } else {
+        s->orig_vtno = 0;
+        s->vtno = vts.v_active;
+        fprintf(stderr, "Started at vt %d, using it.\n", s->vtno);
+    }
+    fbdev_activate_vt(s->tty, s->vtno, true);
+
+    /* get current settings (which we have to restore) */
+    if (ioctl(s->fb, FBIOGET_VSCREENINFO, &s->fb_ovar) < 0) {
+        error_setg(err, "ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
+        goto err_early;
+    }
+    if (ioctl(s->tty, KDGETMODE, &s->kd_omode) < 0) {
+        error_setg(err, "ioctl KDGETMODE: %s\n", strerror(errno));
+        goto err_early;
+    }
+    if (ioctl(s->tty, VT_GETMODE, &s->vt_omode) < 0) {
+        error_setg(err, "ioctl VT_GETMODE: %s\n", strerror(errno));
+        goto err_early;
+    }
+
+    /* checks & initialisation */
+    if (ioctl(s->fb, FBIOGET_FSCREENINFO, &s->fb_fix) < 0) {
+        error_setg(err, "ioctl : %s\n", strerror(errno));
+        perror("ioctl FBIOGET_FSCREENINFO");
+        goto err;
+    }
+    if (ioctl(s->fb, FBIOGET_VSCREENINFO, &s->fb_var) < 0) {
+        error_setg(err, "ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
+        goto err;
+    }
+    if (s->fb_fix.type != FB_TYPE_PACKED_PIXELS) {
+        error_setg(err, "can handle only packed pixel frame buffers\n");
+        goto err;
+    }
+    switch (s->fb_var.bits_per_pixel) {
+    case 32:
+        break;
+    default:
+        error_setg(err, "can't handle %d bpp frame buffers\n",
+                   s->fb_var.bits_per_pixel);
+        goto err;
+    }
+
+    page_mask = getpagesize()-1;
+    fb_switch_state = FB_ACTIVE;
+    s->fb_mem_offset = (unsigned long)(s->fb_fix.smem_start) & page_mask;
+    s->fb_mem = mmap(NULL, s->fb_fix.smem_len+s->fb_mem_offset,
+                     PROT_READ|PROT_WRITE, MAP_SHARED, s->fb, 0);
+    if (s->fb_mem == MAP_FAILED) {
+        error_setg(err, "mmap: %s\n", strerror(errno));
+        goto err;
+    }
+    /* move viewport to upper left corner */
+    if (s->fb_var.xoffset != 0 || s->fb_var.yoffset != 0) {
+        s->fb_var.xoffset = 0;
+        s->fb_var.yoffset = 0;
+        if (ioctl(s->fb, FBIOPAN_DISPLAY, &s->fb_var) < 0) {
+            error_setg(err, "ioctl FBIOPAN_DISPLAY: %s\n", strerror(errno));
+            goto err;
+        }
+    }
+    if (ioctl(s->tty, KDSETMODE, KD_GRAPHICS) < 0) {
+        error_setg(err, "ioctl KDSETMODE: %s\n", strerror(errno));
+        goto err;
+    }
+    /* some fb drivers need this again after switching to graphics ... */
+    fbdev_activate_vt(s->tty, s->vtno, true);
+
+    fbdev_cls(s);
+
+    start_mediumraw(s);
+    qemu_set_fd_handler(s->tty, read_mediumraw, NULL, s);
+
+    s->framebuffer = pixman_from_framebuffer(s);
+    pixman_region_init(&s->dirty);
+    s->device = g_strdup(device);
+    return 0;
+
+err_early:
+    if (s->tty > 0) {
+        close(s->tty);
+    }
+    close(s->fb);
+    return -1;
+
+err:
+    fbdev_cleanup(s);
+    return -1;
+}
+
+static void
+fbdev_catch_fatal_signal(int signr)
+{
+    fprintf(stderr, "%s: %s, restoring linux console state ...\n",
+            __func__, strsignal(signr));
+    fbdev_cleanup(fb);
+    signal(SIGABRT, SIG_DFL);
+    fprintf(stderr, "%s: ... done, going abort() now.\n", __func__);
+    abort();
+}
+
+static void fbdev_catch_exit_signals(void)
+{
+    static const int signals[] = {
+        SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS
+    };
+    struct sigaction act, old;
+    int i;
+
+    memset(&act, 0, sizeof(act));
+    act.sa_handler = fbdev_catch_fatal_signal;
+    act.sa_flags = SA_RESETHAND;
+    sigemptyset(&act.sa_mask);
+    for (i = 0; i < ARRAY_SIZE(signals); i++) {
+        sigaction(signals[i], &act, &old);
+    }
+}
+
+/* -------------------------------------------------------------------- */
+/* console switching                                                    */
+
+static void fbdev_switch_signal(int signal)
+{
+    if (signal == SIG_REL) {
+        /* release */
+        trace_fbdev_vt_release_request();
+        fb_switch_state = FB_REL_REQ;
+    }
+    if (signal == SIG_ACQ) {
+        /* acquisition */
+        trace_fbdev_vt_aquire_request();
+        fb_switch_state = FB_ACQ_REQ;
+    }
+}
+
+static void fbdev_switch_release(FBDevState *s)
+{
+    stop_mediumraw(s);
+    ioctl(s->tty, KDSETMODE, s->kd_omode);
+    ioctl(s->tty, VT_RELDISP, 1);
+    fb_switch_state = FB_INACTIVE;
+    trace_fbdev_vt_released();
+}
+
+static void fbdev_switch_acquire(FBDevState *s)
+{
+    ioctl(s->tty, VT_RELDISP, VT_ACKACQ);
+    start_mediumraw(s);
+    reset_keys(s);
+    ioctl(s->tty, KDSETMODE, KD_GRAPHICS);
+    fb_switch_state = FB_ACTIVE;
+    trace_fbdev_vt_aquired();
+}
+
+static int fbdev_switch_init(FBDevState *s)
+{
+    struct sigaction act, old;
+
+    memset(&act, 0, sizeof(act));
+    act.sa_handler  = fbdev_switch_signal;
+    sigemptyset(&act.sa_mask);
+    sigaction(SIG_REL, &act, &old);
+    sigaction(SIG_ACQ, &act, &old);
+
+    if (ioctl(s->tty, VT_GETMODE, &s->vt_mode) < 0) {
+        perror("ioctl VT_GETMODE");
+        exit(1);
+    }
+    s->vt_mode.mode   = VT_PROCESS;
+    s->vt_mode.waitv  = 0;
+    s->vt_mode.relsig = SIG_REL;
+    s->vt_mode.acqsig = SIG_ACQ;
+
+    if (ioctl(s->tty, VT_SETMODE, &s->vt_mode) < 0) {
+        perror("ioctl VT_SETMODE");
+        exit(1);
+    }
+    return 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* rendering                                                            */
+
+static void fbdev_render(FBDevState *s)
+{
+    assert(s->surface);
+
+    pixman_image_set_clip_region(s->swork, &s->dirty);
+    pixman_image_composite(PIXMAN_OP_SRC, s->swork, NULL, s->framebuffer,
+                           0, 0, 0, 0, 0, 0, s->fb_var.xres, s->fb_var.yres);
+    pixman_region_fini(&s->dirty);
+    pixman_region_init(&s->dirty);
+}
+
+static void fbdev_unrender_ptr(FBDevState *s)
+{
+    if (!s->pw && !s->ph) {
+        return;
+    }
+    pixman_region_union_rect(&s->dirty, &s->dirty,
+                             s->px, s->py, s->pw, s->ph);
+    s->ph = s->pw = 0;
+}
+
+static void fbdev_render_ptr(FBDevState *s)
+{
+    pixman_region16_t region;
+    pixman_transform_t transform;
+
+    if (!s->mon || !s->ptr_image) {
+        return;
+    }
+    if (s->mx < 0 || s->mx >= s->cw || s->my < 0 || s->my >= s->ch) {
+        return;
+    }
+
+    s->px = s->mx - s->ptr_cursor->hot_x;
+    s->py = s->my - s->ptr_cursor->hot_y;
+    s->pw = s->ptr_cursor->width;
+    s->ph = s->ptr_cursor->height;
+
+    pixman_transform_init_identity(&transform);
+    pixman_transform_translate(&transform, NULL,
+                               pixman_int_to_fixed(-s->cx),
+                               pixman_int_to_fixed(-s->cy));
+    if (s->use_scale) {
+        pixman_transform_scale(&transform, NULL,
+                               pixman_double_to_fixed(1/s->scale),
+                               pixman_double_to_fixed(1/s->scale));
+    }
+    pixman_transform_translate(&transform, NULL,
+                               pixman_int_to_fixed(-s->px),
+                               pixman_int_to_fixed(-s->py));
+    pixman_image_set_transform(s->ptr_image, &transform);
+
+    pixman_region_init_rect(&region, 0, 0, s->pw, s->ph);
+    pixman_image_set_clip_region(s->ptr_image, &region);
+
+    pixman_image_composite(PIXMAN_OP_OVER, s->ptr_image, NULL, s->framebuffer,
+                           0, 0, 0, 0, 0, 0, s->fb_var.xres, s->fb_var.yres);
+
+    pixman_region_fini(&region);
+    s->ptr_refresh = 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* qemu interfaces                                                      */
+
+static void fbdev_update(DisplayChangeListener *dcl,
+                         int x, int y, int w, int h)
+{
+    FBDevState *s = container_of(dcl, FBDevState, dcl);
+
+    if (fb_switch_state != FB_ACTIVE) {
+        return;
+    }
+
+    if (s->resize_screen) {
+        double xs, ys;
+
+        trace_fbdev_dpy_resize(surface_width(s->surface),
+                               surface_height(s->surface));
+        s->resize_screen = 0;
+        s->cx = 0; s->cy = 0;
+        s->cw = surface_width(s->surface);
+        s->ch = surface_height(s->surface);
+
+        if (s->use_scale) {
+            xs = (double)s->fb_var.xres / s->cw;
+            ys = (double)s->fb_var.yres / s->ch;
+            if (xs > ys) {
+                s->scale = ys;
+                s->cx = (s->fb_var.xres -
+                         surface_width(s->surface)*s->scale) / 2;
+            } else {
+                s->scale = xs;
+                s->cy = (s->fb_var.yres -
+                         surface_height(s->surface)*s->scale) / 2;
+            }
+        } else {
+            s->scale = 1;
+            if (surface_width(s->surface) < s->fb_var.xres) {
+                s->cx = (s->fb_var.xres - surface_width(s->surface)) / 2;
+            }
+            if (surface_height(s->surface) < s->fb_var.yres) {
+                s->cy = (s->fb_var.yres - surface_height(s->surface)) / 2;
+            }
+        }
+        if (s->sref) {
+            pixman_image_unref(s->sref);
+        }
+        s->sref = pixman_image_ref(s->surface->image);
+
+        if (s->swork) {
+            pixman_image_unref(s->swork);
+        }
+        s->swork = pixman_image_clone(s->sref);
+
+        pixman_transform_init_identity(&s->transform);
+        pixman_transform_translate(&s->transform, NULL,
+                                   pixman_int_to_fixed(-s->cx),
+                                   pixman_int_to_fixed(-s->cy));
+        if (s->use_scale) {
+            pixman_transform_scale(&s->transform, NULL,
+                                   pixman_double_to_fixed(1/s->scale),
+                                   pixman_double_to_fixed(1/s->scale));
+        }
+        pixman_image_set_transform(s->swork, &s->transform);
+
+        pixman_image_set_filter(s->swork, s->pfilter, NULL, 0);
+    }
+
+    if (s->redraw_screen) {
+        trace_fbdev_dpy_redraw();
+        s->redraw_screen = 0;
+        fbdev_cls(s);
+        x = 0; y = 0;
+        w = surface_width(s->surface);
+        h = surface_height(s->surface);
+    }
+
+    pixman_region_union_rect(&s->dirty, &s->dirty, x, y, w, h);
+    if (s->ptr_image && s->mon && s->pw && s->ph) {
+        s->ptr_refresh++;
+    }
+}
+
+static void fbdev_switch(DisplayChangeListener *dcl,
+                         DisplaySurface *new_surface)
+{
+    FBDevState *s = container_of(dcl, FBDevState, dcl);
+
+    s->surface = new_surface;
+    s->resize_screen++;
+    s->redraw_screen++;
+}
+
+static void fbdev_refresh(DisplayChangeListener *dcl)
+{
+    FBDevState *s = container_of(dcl, FBDevState, dcl);
+
+    switch (fb_switch_state) {
+    case FB_REL_REQ:
+        fbdev_switch_release(s);
+        /* fall though */
+    case FB_INACTIVE:
+        return;
+    case FB_ACQ_REQ:
+        fbdev_switch_acquire(s);
+        s->redraw_screen++;
+        /* fall though */
+    case FB_ACTIVE:
+        break;
+    }
+
+    graphic_hw_update(NULL);
+    if (s->redraw_screen) {
+        fbdev_update(dcl, 0, 0, 0, 0);
+    }
+
+    if (s->ptr_refresh) {
+        fbdev_unrender_ptr(s);
+    }
+    if (pixman_region_not_empty(&s->dirty)) {
+        fbdev_render(s);
+    }
+    if (s->ptr_refresh) {
+        fbdev_render_ptr(s);
+    }
+}
+
+static void fbdev_mouse_set(DisplayChangeListener *dcl, int x, int y, int on)
+{
+    FBDevState *s = container_of(dcl, FBDevState, dcl);
+
+    s->ptr_refresh++;
+    s->mx = x;
+    s->my = y;
+    s->mon = on;
+}
+
+static void fbdev_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
+{
+    FBDevState *s = container_of(dcl, FBDevState, dcl);
+
+    s->ptr_refresh++;
+
+    if (s->ptr_cursor) {
+        cursor_put(s->ptr_cursor);
+        s->ptr_cursor = NULL;
+    }
+    if (s->ptr_image) {
+        pixman_image_unref(s->ptr_image);
+        s->ptr_image = NULL;
+    }
+
+    if (!cursor) {
+        return;
+    }
+
+    s->ptr_cursor = cursor;
+    cursor_get(s->ptr_cursor);
+    s->ptr_image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
+                                            cursor->width, cursor->height,
+                                            cursor->data,
+                                            cursor->width * 4);
+    pixman_image_set_filter(s->ptr_image, s->pfilter, NULL, 0);
+}
+
+static const DisplayChangeListenerOps fbdev_ops = {
+    .dpy_name          = "fbdev",
+    .dpy_gfx_update    = fbdev_update,
+    .dpy_gfx_switch    = fbdev_switch,
+    .dpy_refresh       = fbdev_refresh,
+    .dpy_mouse_set     = fbdev_mouse_set,
+    .dpy_cursor_define = fbdev_cursor_define,
+};
+
+static void fbdev_exit_notifier(Notifier *notifier, void *data)
+{
+    FBDevState *s = container_of(notifier, FBDevState, exit_notifier);
+    fbdev_cleanup(s);
+}
+
+int fbdev_display_init(const char *device, bool scale, Error **err)
+{
+    FBDevState *s;
+
+    if (fb != NULL) {
+        return 0;
+    }
+
+    s = g_new0(FBDevState, 1);
+    s->tty = -1;
+    s->fb = -1;
+    s->mice = -1;
+    s->pfilter = PIXMAN_FILTER_GOOD;
+
+    if (fbdev_init(s, device, err) != 0) {
+        g_free(s);
+        return -1;
+    }
+
+    s->exit_notifier.notify = fbdev_exit_notifier;
+    qemu_add_exit_notifier(&s->exit_notifier);
+    fbdev_switch_init(s);
+    fbdev_catch_exit_signals();
+    init_mouse(s);
+    s->use_scale = scale;
+
+    s->dcl.ops = &fbdev_ops;
+    register_displaychangelistener(&s->dcl);
+
+    trace_fbdev_enabled();
+    fb = s;
+    return 0;
+}
+
+void fbdev_display_uninit(void)
+{
+    FBDevState *s = fb;
+
+    if (s == NULL) {
+        return;
+    }
+
+    unregister_displaychangelistener(&s->dcl);
+    qemu_remove_exit_notifier(&s->exit_notifier);
+    fbdev_cleanup(s);
+    uninit_mouse(s);
+    g_free(s);
+    fb = NULL;
+}
diff --git a/ui/linux-keynames.h b/ui/linux-keynames.h
new file mode 100644
index 0000000..058af28
--- /dev/null
+++ b/ui/linux-keynames.h
@@ -0,0 +1,388 @@
+/*
+ *   awk '/#define KEY_/ { printf("    [%s] = \"%s\",\n",$2,$2); }' \
+ *       /usr/include/linux/input.h
+ */
+    [KEY_RESERVED] = "KEY_RESERVED",
+    [KEY_ESC] = "KEY_ESC",
+    [KEY_1] = "KEY_1",
+    [KEY_2] = "KEY_2",
+    [KEY_3] = "KEY_3",
+    [KEY_4] = "KEY_4",
+    [KEY_5] = "KEY_5",
+    [KEY_6] = "KEY_6",
+    [KEY_7] = "KEY_7",
+    [KEY_8] = "KEY_8",
+    [KEY_9] = "KEY_9",
+    [KEY_0] = "KEY_0",
+    [KEY_MINUS] = "KEY_MINUS",
+    [KEY_EQUAL] = "KEY_EQUAL",
+    [KEY_BACKSPACE] = "KEY_BACKSPACE",
+    [KEY_TAB] = "KEY_TAB",
+    [KEY_Q] = "KEY_Q",
+    [KEY_W] = "KEY_W",
+    [KEY_E] = "KEY_E",
+    [KEY_R] = "KEY_R",
+    [KEY_T] = "KEY_T",
+    [KEY_Y] = "KEY_Y",
+    [KEY_U] = "KEY_U",
+    [KEY_I] = "KEY_I",
+    [KEY_O] = "KEY_O",
+    [KEY_P] = "KEY_P",
+    [KEY_LEFTBRACE] = "KEY_LEFTBRACE",
+    [KEY_RIGHTBRACE] = "KEY_RIGHTBRACE",
+    [KEY_ENTER] = "KEY_ENTER",
+    [KEY_LEFTCTRL] = "KEY_LEFTCTRL",
+    [KEY_A] = "KEY_A",
+    [KEY_S] = "KEY_S",
+    [KEY_D] = "KEY_D",
+    [KEY_F] = "KEY_F",
+    [KEY_G] = "KEY_G",
+    [KEY_H] = "KEY_H",
+    [KEY_J] = "KEY_J",
+    [KEY_K] = "KEY_K",
+    [KEY_L] = "KEY_L",
+    [KEY_SEMICOLON] = "KEY_SEMICOLON",
+    [KEY_APOSTROPHE] = "KEY_APOSTROPHE",
+    [KEY_GRAVE] = "KEY_GRAVE",
+    [KEY_LEFTSHIFT] = "KEY_LEFTSHIFT",
+    [KEY_BACKSLASH] = "KEY_BACKSLASH",
+    [KEY_Z] = "KEY_Z",
+    [KEY_X] = "KEY_X",
+    [KEY_C] = "KEY_C",
+    [KEY_V] = "KEY_V",
+    [KEY_B] = "KEY_B",
+    [KEY_N] = "KEY_N",
+    [KEY_M] = "KEY_M",
+    [KEY_COMMA] = "KEY_COMMA",
+    [KEY_DOT] = "KEY_DOT",
+    [KEY_SLASH] = "KEY_SLASH",
+    [KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT",
+    [KEY_KPASTERISK] = "KEY_KPASTERISK",
+    [KEY_LEFTALT] = "KEY_LEFTALT",
+    [KEY_SPACE] = "KEY_SPACE",
+    [KEY_CAPSLOCK] = "KEY_CAPSLOCK",
+    [KEY_F1] = "KEY_F1",
+    [KEY_F2] = "KEY_F2",
+    [KEY_F3] = "KEY_F3",
+    [KEY_F4] = "KEY_F4",
+    [KEY_F5] = "KEY_F5",
+    [KEY_F6] = "KEY_F6",
+    [KEY_F7] = "KEY_F7",
+    [KEY_F8] = "KEY_F8",
+    [KEY_F9] = "KEY_F9",
+    [KEY_F10] = "KEY_F10",
+    [KEY_NUMLOCK] = "KEY_NUMLOCK",
+    [KEY_SCROLLLOCK] = "KEY_SCROLLLOCK",
+    [KEY_KP7] = "KEY_KP7",
+    [KEY_KP8] = "KEY_KP8",
+    [KEY_KP9] = "KEY_KP9",
+    [KEY_KPMINUS] = "KEY_KPMINUS",
+    [KEY_KP4] = "KEY_KP4",
+    [KEY_KP5] = "KEY_KP5",
+    [KEY_KP6] = "KEY_KP6",
+    [KEY_KPPLUS] = "KEY_KPPLUS",
+    [KEY_KP1] = "KEY_KP1",
+    [KEY_KP2] = "KEY_KP2",
+    [KEY_KP3] = "KEY_KP3",
+    [KEY_KP0] = "KEY_KP0",
+    [KEY_KPDOT] = "KEY_KPDOT",
+    [KEY_ZENKAKUHANKAKU] = "KEY_ZENKAKUHANKAKU",
+    [KEY_102ND] = "KEY_102ND",
+    [KEY_F11] = "KEY_F11",
+    [KEY_F12] = "KEY_F12",
+    [KEY_RO] = "KEY_RO",
+    [KEY_KATAKANA] = "KEY_KATAKANA",
+    [KEY_HIRAGANA] = "KEY_HIRAGANA",
+    [KEY_HENKAN] = "KEY_HENKAN",
+    [KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA",
+    [KEY_MUHENKAN] = "KEY_MUHENKAN",
+    [KEY_KPJPCOMMA] = "KEY_KPJPCOMMA",
+    [KEY_KPENTER] = "KEY_KPENTER",
+    [KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
+    [KEY_KPSLASH] = "KEY_KPSLASH",
+    [KEY_SYSRQ] = "KEY_SYSRQ",
+    [KEY_RIGHTALT] = "KEY_RIGHTALT",
+    [KEY_LINEFEED] = "KEY_LINEFEED",
+    [KEY_HOME] = "KEY_HOME",
+    [KEY_UP] = "KEY_UP",
+    [KEY_PAGEUP] = "KEY_PAGEUP",
+    [KEY_LEFT] = "KEY_LEFT",
+    [KEY_RIGHT] = "KEY_RIGHT",
+    [KEY_END] = "KEY_END",
+    [KEY_DOWN] = "KEY_DOWN",
+    [KEY_PAGEDOWN] = "KEY_PAGEDOWN",
+    [KEY_INSERT] = "KEY_INSERT",
+    [KEY_DELETE] = "KEY_DELETE",
+    [KEY_MACRO] = "KEY_MACRO",
+    [KEY_MUTE] = "KEY_MUTE",
+    [KEY_VOLUMEDOWN] = "KEY_VOLUMEDOWN",
+    [KEY_VOLUMEUP] = "KEY_VOLUMEUP",
+    [KEY_POWER] = "KEY_POWER",
+    [KEY_KPEQUAL] = "KEY_KPEQUAL",
+    [KEY_KPPLUSMINUS] = "KEY_KPPLUSMINUS",
+    [KEY_PAUSE] = "KEY_PAUSE",
+    [KEY_SCALE] = "KEY_SCALE",
+    [KEY_KPCOMMA] = "KEY_KPCOMMA",
+    [KEY_HANGEUL] = "KEY_HANGEUL",
+    [KEY_HANGUEL] = "KEY_HANGUEL",
+    [KEY_HANJA] = "KEY_HANJA",
+    [KEY_YEN] = "KEY_YEN",
+    [KEY_LEFTMETA] = "KEY_LEFTMETA",
+    [KEY_RIGHTMETA] = "KEY_RIGHTMETA",
+    [KEY_COMPOSE] = "KEY_COMPOSE",
+    [KEY_STOP] = "KEY_STOP",
+    [KEY_AGAIN] = "KEY_AGAIN",
+    [KEY_PROPS] = "KEY_PROPS",
+    [KEY_UNDO] = "KEY_UNDO",
+    [KEY_FRONT] = "KEY_FRONT",
+    [KEY_COPY] = "KEY_COPY",
+    [KEY_OPEN] = "KEY_OPEN",
+    [KEY_PASTE] = "KEY_PASTE",
+    [KEY_FIND] = "KEY_FIND",
+    [KEY_CUT] = "KEY_CUT",
+    [KEY_HELP] = "KEY_HELP",
+    [KEY_MENU] = "KEY_MENU",
+    [KEY_CALC] = "KEY_CALC",
+    [KEY_SETUP] = "KEY_SETUP",
+    [KEY_SLEEP] = "KEY_SLEEP",
+    [KEY_WAKEUP] = "KEY_WAKEUP",
+    [KEY_FILE] = "KEY_FILE",
+    [KEY_SENDFILE] = "KEY_SENDFILE",
+    [KEY_DELETEFILE] = "KEY_DELETEFILE",
+    [KEY_XFER] = "KEY_XFER",
+    [KEY_PROG1] = "KEY_PROG1",
+    [KEY_PROG2] = "KEY_PROG2",
+    [KEY_WWW] = "KEY_WWW",
+    [KEY_MSDOS] = "KEY_MSDOS",
+    [KEY_COFFEE] = "KEY_COFFEE",
+    [KEY_SCREENLOCK] = "KEY_SCREENLOCK",
+    [KEY_DIRECTION] = "KEY_DIRECTION",
+    [KEY_CYCLEWINDOWS] = "KEY_CYCLEWINDOWS",
+    [KEY_MAIL] = "KEY_MAIL",
+    [KEY_BOOKMARKS] = "KEY_BOOKMARKS",
+    [KEY_COMPUTER] = "KEY_COMPUTER",
+    [KEY_BACK] = "KEY_BACK",
+    [KEY_FORWARD] = "KEY_FORWARD",
+    [KEY_CLOSECD] = "KEY_CLOSECD",
+    [KEY_EJECTCD] = "KEY_EJECTCD",
+    [KEY_EJECTCLOSECD] = "KEY_EJECTCLOSECD",
+    [KEY_NEXTSONG] = "KEY_NEXTSONG",
+    [KEY_PLAYPAUSE] = "KEY_PLAYPAUSE",
+    [KEY_PREVIOUSSONG] = "KEY_PREVIOUSSONG",
+    [KEY_STOPCD] = "KEY_STOPCD",
+    [KEY_RECORD] = "KEY_RECORD",
+    [KEY_REWIND] = "KEY_REWIND",
+    [KEY_PHONE] = "KEY_PHONE",
+    [KEY_ISO] = "KEY_ISO",
+    [KEY_CONFIG] = "KEY_CONFIG",
+    [KEY_HOMEPAGE] = "KEY_HOMEPAGE",
+    [KEY_REFRESH] = "KEY_REFRESH",
+    [KEY_EXIT] = "KEY_EXIT",
+    [KEY_MOVE] = "KEY_MOVE",
+    [KEY_EDIT] = "KEY_EDIT",
+    [KEY_SCROLLUP] = "KEY_SCROLLUP",
+    [KEY_SCROLLDOWN] = "KEY_SCROLLDOWN",
+    [KEY_KPLEFTPAREN] = "KEY_KPLEFTPAREN",
+    [KEY_KPRIGHTPAREN] = "KEY_KPRIGHTPAREN",
+    [KEY_NEW] = "KEY_NEW",
+    [KEY_REDO] = "KEY_REDO",
+    [KEY_F13] = "KEY_F13",
+    [KEY_F14] = "KEY_F14",
+    [KEY_F15] = "KEY_F15",
+    [KEY_F16] = "KEY_F16",
+    [KEY_F17] = "KEY_F17",
+    [KEY_F18] = "KEY_F18",
+    [KEY_F19] = "KEY_F19",
+    [KEY_F20] = "KEY_F20",
+    [KEY_F21] = "KEY_F21",
+    [KEY_F22] = "KEY_F22",
+    [KEY_F23] = "KEY_F23",
+    [KEY_F24] = "KEY_F24",
+    [KEY_PLAYCD] = "KEY_PLAYCD",
+    [KEY_PAUSECD] = "KEY_PAUSECD",
+    [KEY_PROG3] = "KEY_PROG3",
+    [KEY_PROG4] = "KEY_PROG4",
+    [KEY_DASHBOARD] = "KEY_DASHBOARD",
+    [KEY_SUSPEND] = "KEY_SUSPEND",
+    [KEY_CLOSE] = "KEY_CLOSE",
+    [KEY_PLAY] = "KEY_PLAY",
+    [KEY_FASTFORWARD] = "KEY_FASTFORWARD",
+    [KEY_BASSBOOST] = "KEY_BASSBOOST",
+    [KEY_PRINT] = "KEY_PRINT",
+    [KEY_HP] = "KEY_HP",
+    [KEY_CAMERA] = "KEY_CAMERA",
+    [KEY_SOUND] = "KEY_SOUND",
+    [KEY_QUESTION] = "KEY_QUESTION",
+    [KEY_EMAIL] = "KEY_EMAIL",
+    [KEY_CHAT] = "KEY_CHAT",
+    [KEY_SEARCH] = "KEY_SEARCH",
+    [KEY_CONNECT] = "KEY_CONNECT",
+    [KEY_FINANCE] = "KEY_FINANCE",
+    [KEY_SPORT] = "KEY_SPORT",
+    [KEY_SHOP] = "KEY_SHOP",
+    [KEY_ALTERASE] = "KEY_ALTERASE",
+    [KEY_CANCEL] = "KEY_CANCEL",
+    [KEY_BRIGHTNESSDOWN] = "KEY_BRIGHTNESSDOWN",
+    [KEY_BRIGHTNESSUP] = "KEY_BRIGHTNESSUP",
+    [KEY_MEDIA] = "KEY_MEDIA",
+    [KEY_SWITCHVIDEOMODE] = "KEY_SWITCHVIDEOMODE",
+    [KEY_KBDILLUMTOGGLE] = "KEY_KBDILLUMTOGGLE",
+    [KEY_KBDILLUMDOWN] = "KEY_KBDILLUMDOWN",
+    [KEY_KBDILLUMUP] = "KEY_KBDILLUMUP",
+    [KEY_SEND] = "KEY_SEND",
+    [KEY_REPLY] = "KEY_REPLY",
+    [KEY_FORWARDMAIL] = "KEY_FORWARDMAIL",
+    [KEY_SAVE] = "KEY_SAVE",
+    [KEY_DOCUMENTS] = "KEY_DOCUMENTS",
+    [KEY_BATTERY] = "KEY_BATTERY",
+    [KEY_BLUETOOTH] = "KEY_BLUETOOTH",
+    [KEY_WLAN] = "KEY_WLAN",
+    [KEY_UWB] = "KEY_UWB",
+    [KEY_UNKNOWN] = "KEY_UNKNOWN",
+    [KEY_VIDEO_NEXT] = "KEY_VIDEO_NEXT",
+    [KEY_VIDEO_PREV] = "KEY_VIDEO_PREV",
+    [KEY_BRIGHTNESS_CYCLE] = "KEY_BRIGHTNESS_CYCLE",
+    [KEY_BRIGHTNESS_ZERO] = "KEY_BRIGHTNESS_ZERO",
+    [KEY_DISPLAY_OFF] = "KEY_DISPLAY_OFF",
+    [KEY_WIMAX] = "KEY_WIMAX",
+    [KEY_OK] = "KEY_OK",
+    [KEY_SELECT] = "KEY_SELECT",
+    [KEY_GOTO] = "KEY_GOTO",
+    [KEY_CLEAR] = "KEY_CLEAR",
+    [KEY_POWER2] = "KEY_POWER2",
+    [KEY_OPTION] = "KEY_OPTION",
+    [KEY_INFO] = "KEY_INFO",
+    [KEY_TIME] = "KEY_TIME",
+    [KEY_VENDOR] = "KEY_VENDOR",
+    [KEY_ARCHIVE] = "KEY_ARCHIVE",
+    [KEY_PROGRAM] = "KEY_PROGRAM",
+    [KEY_CHANNEL] = "KEY_CHANNEL",
+    [KEY_FAVORITES] = "KEY_FAVORITES",
+    [KEY_EPG] = "KEY_EPG",
+    [KEY_PVR] = "KEY_PVR",
+    [KEY_MHP] = "KEY_MHP",
+    [KEY_LANGUAGE] = "KEY_LANGUAGE",
+    [KEY_TITLE] = "KEY_TITLE",
+    [KEY_SUBTITLE] = "KEY_SUBTITLE",
+    [KEY_ANGLE] = "KEY_ANGLE",
+    [KEY_ZOOM] = "KEY_ZOOM",
+    [KEY_MODE] = "KEY_MODE",
+    [KEY_KEYBOARD] = "KEY_KEYBOARD",
+    [KEY_SCREEN] = "KEY_SCREEN",
+    [KEY_PC] = "KEY_PC",
+    [KEY_TV] = "KEY_TV",
+    [KEY_TV2] = "KEY_TV2",
+    [KEY_VCR] = "KEY_VCR",
+    [KEY_VCR2] = "KEY_VCR2",
+    [KEY_SAT] = "KEY_SAT",
+    [KEY_SAT2] = "KEY_SAT2",
+    [KEY_CD] = "KEY_CD",
+    [KEY_TAPE] = "KEY_TAPE",
+    [KEY_RADIO] = "KEY_RADIO",
+    [KEY_TUNER] = "KEY_TUNER",
+    [KEY_PLAYER] = "KEY_PLAYER",
+    [KEY_TEXT] = "KEY_TEXT",
+    [KEY_DVD] = "KEY_DVD",
+    [KEY_AUX] = "KEY_AUX",
+    [KEY_MP3] = "KEY_MP3",
+    [KEY_AUDIO] = "KEY_AUDIO",
+    [KEY_VIDEO] = "KEY_VIDEO",
+    [KEY_DIRECTORY] = "KEY_DIRECTORY",
+    [KEY_LIST] = "KEY_LIST",
+    [KEY_MEMO] = "KEY_MEMO",
+    [KEY_CALENDAR] = "KEY_CALENDAR",
+    [KEY_RED] = "KEY_RED",
+    [KEY_GREEN] = "KEY_GREEN",
+    [KEY_YELLOW] = "KEY_YELLOW",
+    [KEY_BLUE] = "KEY_BLUE",
+    [KEY_CHANNELUP] = "KEY_CHANNELUP",
+    [KEY_CHANNELDOWN] = "KEY_CHANNELDOWN",
+    [KEY_FIRST] = "KEY_FIRST",
+    [KEY_LAST] = "KEY_LAST",
+    [KEY_AB] = "KEY_AB",
+    [KEY_NEXT] = "KEY_NEXT",
+    [KEY_RESTART] = "KEY_RESTART",
+    [KEY_SLOW] = "KEY_SLOW",
+    [KEY_SHUFFLE] = "KEY_SHUFFLE",
+    [KEY_BREAK] = "KEY_BREAK",
+    [KEY_PREVIOUS] = "KEY_PREVIOUS",
+    [KEY_DIGITS] = "KEY_DIGITS",
+    [KEY_TEEN] = "KEY_TEEN",
+    [KEY_TWEN] = "KEY_TWEN",
+    [KEY_VIDEOPHONE] = "KEY_VIDEOPHONE",
+    [KEY_GAMES] = "KEY_GAMES",
+    [KEY_ZOOMIN] = "KEY_ZOOMIN",
+    [KEY_ZOOMOUT] = "KEY_ZOOMOUT",
+    [KEY_ZOOMRESET] = "KEY_ZOOMRESET",
+    [KEY_WORDPROCESSOR] = "KEY_WORDPROCESSOR",
+    [KEY_EDITOR] = "KEY_EDITOR",
+    [KEY_SPREADSHEET] = "KEY_SPREADSHEET",
+    [KEY_GRAPHICSEDITOR] = "KEY_GRAPHICSEDITOR",
+    [KEY_PRESENTATION] = "KEY_PRESENTATION",
+    [KEY_DATABASE] = "KEY_DATABASE",
+    [KEY_NEWS] = "KEY_NEWS",
+    [KEY_VOICEMAIL] = "KEY_VOICEMAIL",
+    [KEY_ADDRESSBOOK] = "KEY_ADDRESSBOOK",
+    [KEY_MESSENGER] = "KEY_MESSENGER",
+    [KEY_DISPLAYTOGGLE] = "KEY_DISPLAYTOGGLE",
+    [KEY_SPELLCHECK] = "KEY_SPELLCHECK",
+    [KEY_LOGOFF] = "KEY_LOGOFF",
+    [KEY_DOLLAR] = "KEY_DOLLAR",
+    [KEY_EURO] = "KEY_EURO",
+    [KEY_FRAMEBACK] = "KEY_FRAMEBACK",
+    [KEY_FRAMEFORWARD] = "KEY_FRAMEFORWARD",
+    [KEY_CONTEXT_MENU] = "KEY_CONTEXT_MENU",
+    [KEY_MEDIA_REPEAT] = "KEY_MEDIA_REPEAT",
+    [KEY_10CHANNELSUP] = "KEY_10CHANNELSUP",
+    [KEY_10CHANNELSDOWN] = "KEY_10CHANNELSDOWN",
+    [KEY_DEL_EOL] = "KEY_DEL_EOL",
+    [KEY_DEL_EOS] = "KEY_DEL_EOS",
+    [KEY_INS_LINE] = "KEY_INS_LINE",
+    [KEY_DEL_LINE] = "KEY_DEL_LINE",
+    [KEY_FN] = "KEY_FN",
+    [KEY_FN_ESC] = "KEY_FN_ESC",
+    [KEY_FN_F1] = "KEY_FN_F1",
+    [KEY_FN_F2] = "KEY_FN_F2",
+    [KEY_FN_F3] = "KEY_FN_F3",
+    [KEY_FN_F4] = "KEY_FN_F4",
+    [KEY_FN_F5] = "KEY_FN_F5",
+    [KEY_FN_F6] = "KEY_FN_F6",
+    [KEY_FN_F7] = "KEY_FN_F7",
+    [KEY_FN_F8] = "KEY_FN_F8",
+    [KEY_FN_F9] = "KEY_FN_F9",
+    [KEY_FN_F10] = "KEY_FN_F10",
+    [KEY_FN_F11] = "KEY_FN_F11",
+    [KEY_FN_F12] = "KEY_FN_F12",
+    [KEY_FN_1] = "KEY_FN_1",
+    [KEY_FN_2] = "KEY_FN_2",
+    [KEY_FN_D] = "KEY_FN_D",
+    [KEY_FN_E] = "KEY_FN_E",
+    [KEY_FN_F] = "KEY_FN_F",
+    [KEY_FN_S] = "KEY_FN_S",
+    [KEY_FN_B] = "KEY_FN_B",
+    [KEY_BRL_DOT1] = "KEY_BRL_DOT1",
+    [KEY_BRL_DOT2] = "KEY_BRL_DOT2",
+    [KEY_BRL_DOT3] = "KEY_BRL_DOT3",
+    [KEY_BRL_DOT4] = "KEY_BRL_DOT4",
+    [KEY_BRL_DOT5] = "KEY_BRL_DOT5",
+    [KEY_BRL_DOT6] = "KEY_BRL_DOT6",
+    [KEY_BRL_DOT7] = "KEY_BRL_DOT7",
+    [KEY_BRL_DOT8] = "KEY_BRL_DOT8",
+    [KEY_BRL_DOT9] = "KEY_BRL_DOT9",
+    [KEY_BRL_DOT10] = "KEY_BRL_DOT10",
+    [KEY_NUMERIC_0] = "KEY_NUMERIC_0",
+    [KEY_NUMERIC_1] = "KEY_NUMERIC_1",
+    [KEY_NUMERIC_2] = "KEY_NUMERIC_2",
+    [KEY_NUMERIC_3] = "KEY_NUMERIC_3",
+    [KEY_NUMERIC_4] = "KEY_NUMERIC_4",
+    [KEY_NUMERIC_5] = "KEY_NUMERIC_5",
+    [KEY_NUMERIC_6] = "KEY_NUMERIC_6",
+    [KEY_NUMERIC_7] = "KEY_NUMERIC_7",
+    [KEY_NUMERIC_8] = "KEY_NUMERIC_8",
+    [KEY_NUMERIC_9] = "KEY_NUMERIC_9",
+    [KEY_NUMERIC_STAR] = "KEY_NUMERIC_STAR",
+    [KEY_NUMERIC_POUND] = "KEY_NUMERIC_POUND",
+    [KEY_RFKILL] = "KEY_RFKILL",
+    [KEY_MIN_INTERESTING] = "KEY_MIN_INTERESTING",
+    [KEY_MAX] = "KEY_MAX",
+    [KEY_CNT] = "KEY_CNT",
diff --git a/vl.c b/vl.c
index 0a8f056..bf337f9 100644
--- a/vl.c
+++ b/vl.c
@@ -2240,6 +2240,13 @@ static DisplayType select_display(const char *p)
         fprintf(stderr, "GTK support is disabled\n");
         exit(1);
 #endif
+    } else if (strstart(p, "fbdev", &opts)) {
+#ifdef CONFIG_FBDEV
+        display = DT_FBDEV;
+#else
+        fprintf(stderr, "fbdev support is disabled\n");
+        exit(1);
+#endif
     } else if (strstart(p, "none", &opts)) {
         display = DT_NONE;
     } else {
@@ -4300,6 +4307,20 @@ int main(int argc, char **argv, char **envp)
         curses_display_init(ds, full_screen);
         break;
 #endif
+#if defined(CONFIG_FBDEV)
+    case DT_FBDEV:
+    {
+        Error *errp = NULL;
+        if (fbdev_display_init(NULL, false, &errp) != 0) {
+            if (error_is_set(&errp)) {
+                fprintf(stderr, "%s\n", error_get_pretty(errp));
+                error_free(errp);
+            }
+            exit(1);
+        }
+        break;
+    }
+#endif
 #if defined(CONFIG_SDL)
     case DT_SDL:
         sdl_display_init(ds, full_screen, no_frame);
-- 
1.7.9.7

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-26 11:38 [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Gerd Hoffmann
@ 2013-06-26 11:38 ` Gerd Hoffmann
  2013-06-26 15:56   ` Luiz Capitulino
  2013-06-26 15:48 ` [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Luiz Capitulino
  1 sibling, 1 reply; 11+ messages in thread
From: Gerd Hoffmann @ 2013-06-26 11:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Markus Armbruster, Anthony Liguori, Gerd Hoffmann, Luiz Capitulino

This patch adds a fbdev monitor command to enable/disable
the fbdev display at runtime to both qmp and hmp.

qmp: framebuffer-display enable=on|off scale=on|off device=/dev/fb<n>
hmp: framebuffer-display on|off

There is also a query-framebuffer command for qmp.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 hmp-commands.hx      |   14 ++++++++++++++
 hmp.c                |    9 +++++++++
 hmp.h                |    1 +
 include/ui/console.h |    1 +
 qapi-schema.json     |   43 +++++++++++++++++++++++++++++++++++++++++++
 qmp-commands.hx      |   12 ++++++++++++
 qmp.c                |   31 +++++++++++++++++++++++++++++++
 ui/fbdev.c           |   20 ++++++++++++++++++++
 8 files changed, 131 insertions(+)

diff --git a/hmp-commands.hx b/hmp-commands.hx
index 915b0d1..283106d 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1563,7 +1563,21 @@ STEXI
 @findex qemu-io
 
 Executes a qemu-io command on the given block device.
+ETEXI
+
+    {
+        .name       = "framebuffer-display",
+        .args_type  = "enable:b",
+        .params     = "on|off",
+        .help       = "enable/disable linux console framebuffer display",
+        .mhandler.cmd = hmp_framebuffer_display,
+    },
+
+STEXI
+@item framebuffer-display on | off
+@findex framebuffer-display
 
+enable/disable linux console framebuffer display.
 ETEXI
 
     {
diff --git a/hmp.c b/hmp.c
index 494a9aa..55f195f 100644
--- a/hmp.c
+++ b/hmp.c
@@ -1464,3 +1464,12 @@ void hmp_qemu_io(Monitor *mon, const QDict *qdict)
 
     hmp_handle_error(mon, &err);
 }
+
+void hmp_framebuffer_display(Monitor *mon, const QDict *qdict)
+{
+    int enable = qdict_get_bool(qdict, "enable");
+    Error *errp = NULL;
+
+    qmp_framebuffer_display(enable, false, false, false, NULL, &errp);
+    hmp_handle_error(mon, &errp);
+}
diff --git a/hmp.h b/hmp.h
index 56d2e92..c3a48e4 100644
--- a/hmp.h
+++ b/hmp.h
@@ -86,5 +86,6 @@ void hmp_nbd_server_stop(Monitor *mon, const QDict *qdict);
 void hmp_chardev_add(Monitor *mon, const QDict *qdict);
 void hmp_chardev_remove(Monitor *mon, const QDict *qdict);
 void hmp_qemu_io(Monitor *mon, const QDict *qdict);
+void hmp_framebuffer_display(Monitor *mon, const QDict *qdict);
 
 #endif
diff --git a/include/ui/console.h b/include/ui/console.h
index 71b538a..5a9207d 100644
--- a/include/ui/console.h
+++ b/include/ui/console.h
@@ -311,6 +311,7 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
 /* fbdev.c */
 int fbdev_display_init(const char *device, bool scale, Error **err);
 void fbdev_display_uninit(void);
+FramebufferInfo *framebuffer_info(void);
 
 /* cocoa.m */
 void cocoa_display_init(DisplayState *ds, int full_screen);
diff --git a/qapi-schema.json b/qapi-schema.json
index 6cc07c2..715dc1f 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3608,3 +3608,46 @@
             '*cpuid-input-ecx': 'int',
             'cpuid-register': 'X86CPURegister32',
             'features': 'int' } }
+
+##
+# @framebuffer-display:
+#
+# Enable/disable linux console framebuffer display.
+#
+# @enable: whenever the framebuffer display should be enabled or disabled.
+#
+# @scale: #optional enables display scaling, default: off
+#
+# @device: #optional specifies framebuffer device, default: /dev/fb0
+#
+# Returns: Nothing.
+#
+# Since: 1.6
+#
+##
+{ 'command': 'framebuffer-display', 'data': {'enable'  : 'bool',
+                                             '*scale'  : 'bool',
+                                             '*device' : 'str' } }
+
+##
+# @FramebufferInfo:
+#
+# Since 1.6
+##
+{ 'type': 'FramebufferInfo',
+  'data': { 'enabled': 'bool',
+            '*scale' : 'bool',
+            '*device': 'str',
+            '*vtno'  : 'int' } }
+
+##
+# @query-framebuffer:
+#
+# Query linux console framebuffer state.
+#
+# Returns: FramebufferInfo.
+#
+# Since: 1.6
+#
+##
+{ 'command': 'query-framebuffer', 'returns': 'FramebufferInfo' }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 8cea5e5..e0661f0 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -2997,3 +2997,15 @@ Example:
 <- { "return": {} }
 
 EQMP
+
+    {
+        .name       = "framebuffer-display",
+        .args_type  = "enable:b,scale:b?,device:s?",
+        .mhandler.cmd_new = qmp_marshal_input_framebuffer_display,
+    },
+
+    {
+        .name       = "query-framebuffer",
+        .args_type  = "",
+        .mhandler.cmd_new = qmp_marshal_input_query_framebuffer,
+    },
diff --git a/qmp.c b/qmp.c
index 4c149b3..b6d826c 100644
--- a/qmp.c
+++ b/qmp.c
@@ -404,6 +404,37 @@ void qmp_change(const char *device, const char *target,
     }
 }
 
+void qmp_framebuffer_display(bool enable,
+                             bool has_scale, bool scale,
+                             bool has_device, const char *device,
+                             Error **errp)
+{
+#if defined(CONFIG_FBDEV)
+    if (enable) {
+        if (fbdev_display_init(has_device ? device : NULL,
+                               has_scale  ? scale  : false,
+                               errp) != 0) {
+            if (!error_is_set(errp)) {
+                error_setg(errp, "fbdev initialization failed");
+            }
+        }
+    } else {
+        fbdev_display_uninit();
+    }
+#else
+    error_setg(errp, "fbdev support disabled at compile time");
+#endif
+}
+
+FramebufferInfo *qmp_query_framebuffer(Error **errp)
+{
+#if defined(CONFIG_FBDEV)
+    return framebuffer_info();
+#else
+    error_setg(errp, "fbdev support disabled at compile time");
+#endif
+}
+
 static void qom_list_types_tramp(ObjectClass *klass, void *data)
 {
     ObjectTypeInfoList *e, **pret = data;
diff --git a/ui/fbdev.c b/ui/fbdev.c
index 91e11e5..326bba1 100644
--- a/ui/fbdev.c
+++ b/ui/fbdev.c
@@ -1162,3 +1162,23 @@ void fbdev_display_uninit(void)
     g_free(s);
     fb = NULL;
 }
+
+FramebufferInfo *framebuffer_info(void)
+{
+    FramebufferInfo *info = g_new0(FramebufferInfo, 1);
+    FBDevState *s = fb;
+
+    if (s == NULL) {
+        info->enabled = false;
+        return info;
+    }
+
+    info->enabled    = true;
+    info->has_device = true;
+    info->device     = g_strdup(s->device);
+    info->has_scale  = true;
+    info->scale      = s->use_scale;
+    info->has_vtno   = true;
+    info->vtno       = s->vtno;
+    return info;
+}
-- 
1.7.9.7

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver.
  2013-06-26 11:38 [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Gerd Hoffmann
  2013-06-26 11:38 ` [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query Gerd Hoffmann
@ 2013-06-26 15:48 ` Luiz Capitulino
  2013-06-27  9:58   ` Gerd Hoffmann
  1 sibling, 1 reply; 11+ messages in thread
From: Luiz Capitulino @ 2013-06-26 15:48 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Anthony Liguori, qemu-devel

On Wed, 26 Jun 2013 13:38:03 +0200
Gerd Hoffmann <kraxel@redhat.com> wrote:

> Display works, requires truecolor framebuffer with 16 or 32 bpp on the
> host.  32bpp is recommended.  The framebuffer is used as-is, qemu
> doesn't try to switch modes.  With LCD displays mode switching is pretty
> pointless IMHO, also it wouldn't work anyway with the most common
> fbdev drivers (vesafb, KMS).  Guest display is centered on the host
> screen.
> 
> Mouse works, uses /dev/input/mice.
> 
> Keyboard works.  Guest screen has whatever keymap you load inside
> the guest.  Text windows (monitor, serial, ...) have a simple en-us
> keymap.  Good enough to type monitor commands.  Not goot enough to
> work seriously on a serial terminal.  But the qemu terminal emulation
> isn't good enough for that anyway ;)
> 
> Hot keys:
>   Ctrl-Alt-F<nr>  -> host console switching.
>   Ctrl-Alt-<nr>   -> qemu console switching.
>   Ctrl-Alt-S      -> toggle display scaling.
>   Ctrl-Alt-ESC    -> exit qemu.
> 
> Special feature:  Sane console switching.  Switching away stops screen
> updates.  Switching back redraws the screen.  When started from the
> linux console qemu uses the vt you've started it from (requires just
> read/write access to /dev/fb0).  When starting from somewhere else qemu
> tries to open a unused virtual terminal and switch to it (usually
> requires root privileges to open /dev/tty<nr>).
> 
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  configure               |   12 +
>  include/sysemu/sysemu.h |    1 +
>  include/ui/console.h    |    4 +
>  qemu-options.hx         |    4 +-
>  trace-events            |   14 +
>  ui/Makefile.objs        |    1 +
>  ui/fbdev.c              | 1164 +++++++++++++++++++++++++++++++++++++++++++++++
>  ui/linux-keynames.h     |  388 ++++++++++++++++
>  vl.c                    |   21 +
>  9 files changed, 1608 insertions(+), 1 deletion(-)
>  create mode 100644 ui/fbdev.c
>  create mode 100644 ui/linux-keynames.h
> 
> diff --git a/configure b/configure
> index 0e0adde..391e645 100755
> --- a/configure
> +++ b/configure
> @@ -158,6 +158,7 @@ fdt=""
>  nptl=""
>  pixman=""
>  sdl=""
> +fbdev="no"
>  virtfs=""
>  vnc="yes"
>  sparse="no"
> @@ -549,6 +550,7 @@ Haiku)
>    kvm="yes"
>    vhost_net="yes"
>    vhost_scsi="yes"
> +  fbdev="yes"
>    if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then
>      audio_possible_drivers="$audio_possible_drivers fmod"
>    fi
> @@ -700,6 +702,10 @@ for opt do
>    ;;
>    --enable-qom-cast-debug) qom_cast_debug="yes"
>    ;;
> +  --disable-fbdev) fbdev="no"
> +  ;;
> +  --enable-fbdev) fbdev="yes"
> +  ;;
>    --disable-virtfs) virtfs="no"
>    ;;
>    --enable-virtfs) virtfs="yes"
> @@ -1051,6 +1057,8 @@ echo "  --disable-sdl            disable SDL"
>  echo "  --enable-sdl             enable SDL"
>  echo "  --disable-gtk            disable gtk UI"
>  echo "  --enable-gtk             enable gtk UI"
> +echo "  --disable-fbdev          disable linux framebuffer"
> +echo "  --enable-fbdev           enable linux framebuffer"
>  echo "  --disable-virtfs         disable VirtFS"
>  echo "  --enable-virtfs          enable VirtFS"
>  echo "  --disable-vnc            disable VNC"
> @@ -3492,6 +3500,7 @@ fi
>  echo "pixman            $pixman"
>  echo "SDL support       $sdl"
>  echo "GTK support       $gtk"
> +echo "fbdev support     $fbdev"
>  echo "curses support    $curses"
>  echo "curl support      $curl"
>  echo "mingw32 support   $mingw32"
> @@ -3722,6 +3731,9 @@ if test "$sdl" = "yes" ; then
>    echo "CONFIG_SDL=y" >> $config_host_mak
>    echo "SDL_CFLAGS=$sdl_cflags" >> $config_host_mak
>  fi
> +if test "$fbdev" = "yes" ; then
> +  echo "CONFIG_FBDEV=y" >> $config_host_mak
> +fi
>  if test "$cocoa" = "yes" ; then
>    echo "CONFIG_COCOA=y" >> $config_host_mak
>  fi
> diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h
> index 2fb71af..5922311 100644
> --- a/include/sysemu/sysemu.h
> +++ b/include/sysemu/sysemu.h
> @@ -91,6 +91,7 @@ typedef enum DisplayType
>      DT_CURSES,
>      DT_SDL,
>      DT_GTK,
> +    DT_FBDEV,
>      DT_NOGRAPHIC,
>      DT_NONE,
>  } DisplayType;
> diff --git a/include/ui/console.h b/include/ui/console.h
> index 98edf41..71b538a 100644
> --- a/include/ui/console.h
> +++ b/include/ui/console.h
> @@ -308,6 +308,10 @@ void register_vc_handler(VcHandler *handler);
>  /* sdl.c */
>  void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
>  
> +/* fbdev.c */
> +int fbdev_display_init(const char *device, bool scale, Error **err);
> +void fbdev_display_uninit(void);
> +
>  /* cocoa.m */
>  void cocoa_display_init(DisplayState *ds, int full_screen);
>  
> diff --git a/qemu-options.hx b/qemu-options.hx
> index ca6fdf6..259e76f 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -804,7 +804,7 @@ ETEXI
>  
>  DEF("display", HAS_ARG, QEMU_OPTION_display,
>      "-display sdl[,frame=on|off][,alt_grab=on|off][,ctrl_grab=on|off]\n"
> -    "            [,window_close=on|off]|curses|none|\n"
> +    "            [,window_close=on|off]|curses|fbdev|none|\n"
>      "            vnc=<display>[,<optargs>]\n"
>      "                select display type\n", QEMU_ARCH_ALL)
>  STEXI
> @@ -822,6 +822,8 @@ support a text mode, QEMU can display this output using a
>  curses/ncurses interface. Nothing is displayed when the graphics
>  device is in graphical mode or if the graphics device does not support
>  a text mode. Generally only the VGA device models support text mode.
> +@item fbdev
> +Display video output on a linux framebuffer console.
>  @item none
>  Do not display video output. The guest will still see an emulated
>  graphics card, but its output will not be displayed to the QEMU
> diff --git a/trace-events b/trace-events
> index c5f1ccb..7a73b2a 100644
> --- a/trace-events
> +++ b/trace-events
> @@ -1161,3 +1161,17 @@ kvm_run_exit(int cpu_index, uint32_t reason) "cpu_index %d, reason %d"
>  # qom/object.c
>  object_dynamic_cast_assert(const char *type, const char *target, const char *file, int line, const char *func) "%s->%s (%s:%d:%s)"
>  object_class_dynamic_cast_assert(const char *type, const char *target, const char *file, int line, const char *func) "%s->%s (%s:%d:%s)"
> +
> +# ui/fbdev.c
> +fbdev_enabled(void) ""
> +fbdev_cleanup(void) ""
> +fbdev_vt_activate(int vtno, int wait) "vtno %d, wait %d"
> +fbdev_vt_activated(void) ""
> +fbdev_vt_release_request(void) ""
> +fbdev_vt_released(void) ""
> +fbdev_vt_aquire_request(void) ""
> +fbdev_vt_aquired(void) ""
> +fbdev_kbd_raw(int enable) "enable %d"
> +fbdev_kbd_event(int keycode, const char *kname, int up) "keycode 0x%x [%s], down %d"
> +fbdev_dpy_resize(int w, int h) "%dx%d"
> +fbdev_dpy_redraw(void)
> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
> index 6ddc0de..ee96ad5 100644
> --- a/ui/Makefile.objs
> +++ b/ui/Makefile.objs
> @@ -14,6 +14,7 @@ common-obj-$(CONFIG_COCOA) += cocoa.o
>  common-obj-$(CONFIG_CURSES) += curses.o
>  common-obj-$(CONFIG_VNC) += $(vnc-obj-y)
>  common-obj-$(CONFIG_GTK) += gtk.o x_keymap.o
> +common-obj-$(CONFIG_FBDEV) += fbdev.o
>  
>  $(obj)/sdl.o $(obj)/sdl_zoom.o: QEMU_CFLAGS += $(SDL_CFLAGS) 
>  
> diff --git a/ui/fbdev.c b/ui/fbdev.c
> new file mode 100644
> index 0000000..91e11e5
> --- /dev/null
> +++ b/ui/fbdev.c
> @@ -0,0 +1,1164 @@
> +/*
> + * linux fbdev output driver.
> + *
> + * Author: Gerd Hoffmann <kraxel@redhat.com>
> + *
> + * 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 <stdio.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <signal.h>
> +#include <termios.h>
> +
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +
> +#include <linux/kd.h>
> +#include <linux/vt.h>
> +#include <linux/fb.h>
> +
> +#include "qemu-common.h"
> +#include "keymaps.h"
> +#include "ui/qemu-pixman.h"
> +#include "ui/console.h"
> +#include "sysemu/sysemu.h"
> +
> +/*
> + * must be last so we get the linux input layer
> + * KEY_* defines, not the ncurses ones.
> + */
> +#include <linux/input.h>
> +
> +/* -------------------------------------------------------------------- */
> +
> +typedef struct FBDevState {
> +    /* file handles */
> +    int                        tty, fb, mice;
> +
> +    /* saved state, for restore on exit */
> +    int                        orig_vtno;
> +    int                        kd_omode;
> +    struct vt_mode             vt_omode;
> +    struct fb_var_screeninfo   fb_ovar;
> +
> +    /* framebuffer */
> +    char                       *device;
> +    struct fb_fix_screeninfo   fb_fix;
> +    struct fb_var_screeninfo   fb_var;
> +    uint8_t                    *fb_mem;
> +    int                        fb_mem_offset;
> +
> +    /* linux console */
> +    int                        vtno;
> +    struct vt_mode             vt_mode;
> +    struct termios             tty_attributes;
> +    unsigned long              tty_mode;
> +    unsigned int               tty_flags;
> +    bool                       tty_mediumraw;
> +    bool                       key_down[KEY_CNT];
> +
> +    /* qemu windup */
> +    DisplayChangeListener      dcl;
> +    int                        resize_screen;
> +    int                        redraw_screen;
> +    int                        cx, cy, cw, ch;
> +    Notifier                   exit_notifier;
> +    DisplaySurface             *surface;
> +    pixman_image_t             *sref, *swork;
> +    pixman_image_t             *framebuffer;
> +    pixman_transform_t         transform;
> +    pixman_region16_t          dirty;
> +    double                     scale;
> +
> +    QEMUCursor                 *ptr_cursor;
> +    pixman_image_t             *ptr_image;
> +    int                        ptr_refresh;
> +    int                        px, py, pw, ph;
> +    int                        mx, my, mon, ax, ay;
> +
> +    /* options */
> +    int                        use_scale;
> +    pixman_filter_t            pfilter;
> +} FBDevState;
> +
> +static FBDevState *fb;
> +
> +/* console switching */
> +#define SIG_ACQ      (SIGRTMIN+6)
> +#define SIG_REL      (SIGRTMIN+7)
> +#define FB_ACTIVE    0
> +#define FB_REL_REQ   1
> +#define FB_INACTIVE  2
> +#define FB_ACQ_REQ   3
> +int fb_switch_state;
> +
> +/* fwd decls */
> +static int fbdev_activate_vt(int tty, int vtno, bool wait);
> +
> +/* -------------------------------------------------------------------- */
> +/* pixman helpers                                                       */
> +
> +static pixman_image_t *pixman_from_framebuffer(FBDevState *s)
> +{
> +    pixman_format_code_t format;
> +    pixman_image_t *image;
> +    int type;
> +
> +    type = qemu_pixman_get_type(s->fb_var.red.offset,
> +                                s->fb_var.green.offset,
> +                                s->fb_var.blue.offset);
> +    format = PIXMAN_FORMAT(s->fb_var.bits_per_pixel, type,
> +                           s->fb_var.transp.length,
> +                           s->fb_var.red.length,
> +                           s->fb_var.green.length,
> +                           s->fb_var.blue.length);
> +    image = pixman_image_create_bits(format, s->fb_var.xres, s->fb_var.yres,
> +                                     (void *)s->fb_mem, s->fb_fix.line_length);
> +    return image;
> +}
> +
> +static pixman_image_t *pixman_image_clone(pixman_image_t *i)
> +{
> +    return pixman_image_create_bits(pixman_image_get_format(i),
> +                                    pixman_image_get_width(i),
> +                                    pixman_image_get_height(i),
> +                                    pixman_image_get_data(i),
> +                                    pixman_image_get_stride(i));
> +}
> +
> +/* -------------------------------------------------------------------- */
> +/* mouse                                                                */
> +
> +static void read_mouse(void *opaque)
> +{
> +    FBDevState *s = opaque;
> +    int8_t buf[3];
> +    int rc, x, y, b;
> +
> +    rc = read(s->mice, buf, sizeof(buf));
> +    if (rc != sizeof(buf)) {
> +        return;
> +    }
> +
> +    if (fb_switch_state != FB_ACTIVE) {
> +        return;
> +    }
> +
> +    x = buf[1];
> +    y = -buf[2];
> +    b = buf[0] & 0x7;
> +
> +    if (kbd_mouse_is_absolute()) {
> +        s->ax += x; s->ay += y;
> +        if (s->ax < 0) {
> +            s->ax = 0;
> +        }
> +        if (s->ay < 0) {
> +            s->ay = 0;
> +        }
> +        if (s->ax >= s->cw * s->scale) {
> +            s->ax = s->cw * s->scale - 1;
> +        }
> +        if (s->ay >= s->ch * s->scale) {
> +            s->ay = s->ch * s->scale-1;
> +        }
> +        kbd_mouse_event(s->ax * 0x7FFF / (s->cw * s->scale),
> +                        s->ay * 0x7FFF / (s->ch * s->scale), 0, b);
> +    } else {
> +        kbd_mouse_event(x, y, 0, b);
> +    }
> +}
> +
> +static int init_mouse(FBDevState *s)
> +{
> +    s->mice = open("/dev/input/mice", O_RDONLY);
> +    if (s->mice == -1) {
> +        return -1;
> +    }
> +    qemu_set_fd_handler(s->mice, read_mouse, NULL, s);
> +    return 0;
> +}
> +
> +static void uninit_mouse(FBDevState *s)
> +{
> +    if (s->mice == -1) {
> +        return;
> +    }
> +    qemu_set_fd_handler(s->mice, NULL, NULL, NULL);
> +    close(s->mice);
> +    s->mice = -1;
> +}
> +
> +/* -------------------------------------------------------------------- */
> +/* keyboard                                                             */
> +
> +static const char *keynames[] = {
> +#include "linux-keynames.h"
> +};
> +
> +static const int scancode_map[KEY_CNT] = {
> +    [KEY_ESC]           = 0x01,
> +    [KEY_1]             = 0x02,
> +    [KEY_2]             = 0x03,
> +    [KEY_3]             = 0x04,
> +    [KEY_4]             = 0x05,
> +    [KEY_5]             = 0x06,
> +    [KEY_6]             = 0x07,
> +    [KEY_7]             = 0x08,
> +    [KEY_8]             = 0x09,
> +    [KEY_9]             = 0x0a,
> +    [KEY_0]             = 0x0b,
> +    [KEY_MINUS]         = 0x0c,
> +    [KEY_EQUAL]         = 0x0d,
> +    [KEY_BACKSPACE]     = 0x0e,
> +
> +    [KEY_TAB]           = 0x0f,
> +    [KEY_Q]             = 0x10,
> +    [KEY_W]             = 0x11,
> +    [KEY_E]             = 0x12,
> +    [KEY_R]             = 0x13,
> +    [KEY_T]             = 0x14,
> +    [KEY_Y]             = 0x15,
> +    [KEY_U]             = 0x16,
> +    [KEY_I]             = 0x17,
> +    [KEY_O]             = 0x18,
> +    [KEY_P]             = 0x19,
> +    [KEY_LEFTBRACE]     = 0x1a,
> +    [KEY_RIGHTBRACE]    = 0x1b,
> +    [KEY_ENTER]         = 0x1c,
> +
> +    [KEY_A]             = 0x1e,
> +    [KEY_S]             = 0x1f,
> +    [KEY_D]             = 0x20,
> +    [KEY_F]             = 0x21,
> +    [KEY_G]             = 0x22,
> +    [KEY_H]             = 0x23,
> +    [KEY_J]             = 0x24,
> +    [KEY_K]             = 0x25,
> +    [KEY_L]             = 0x26,
> +    [KEY_SEMICOLON]     = 0x27,
> +    [KEY_APOSTROPHE]    = 0x28,
> +    [KEY_GRAVE]         = 0x29,
> +    [KEY_LEFTSHIFT]     = 0x2a,
> +    [KEY_BACKSLASH]     = 0x2b,
> +
> +    [KEY_Z]             = 0x2c,
> +    [KEY_X]             = 0x2d,
> +    [KEY_C]             = 0x2e,
> +    [KEY_V]             = 0x2f,
> +    [KEY_B]             = 0x30,
> +    [KEY_N]             = 0x31,
> +    [KEY_M]             = 0x32,
> +    [KEY_COMMA]         = 0x33,
> +    [KEY_DOT]           = 0x34,
> +    [KEY_SLASH]         = 0x35,
> +    [KEY_RIGHTSHIFT]    = 0x36,
> +    [KEY_SPACE]         = 0x39,
> +
> +    [KEY_F1]            = 0x3b,
> +    [KEY_F2]            = 0x3c,
> +    [KEY_F3]            = 0x3d,
> +    [KEY_F4]            = 0x3e,
> +    [KEY_F5]            = 0x3f,
> +    [KEY_F6]            = 0x40,
> +    [KEY_F7]            = 0x41,
> +    [KEY_F8]            = 0x42,
> +    [KEY_F9]            = 0x43,
> +    [KEY_F10]           = 0x44,
> +    [KEY_F11]           = 0x57,
> +    [KEY_F12]           = 0x58,
> +
> +    [KEY_SYSRQ]         = 0xb7,
> +    [KEY_SCROLLLOCK]    = 0x46,
> +#if 0
> +    [KEY_PAUSE]         = FIXME,
> +#endif
> +    [KEY_CAPSLOCK]      = 0x3a,
> +    [KEY_102ND]         = 0x56,
> +
> +    [KEY_LEFTCTRL]      = 0x1d,
> +    [KEY_LEFTMETA]      = 0xdb,
> +    [KEY_LEFTALT]       = 0x38,
> +    [KEY_RIGHTALT]      = 0xb8,
> +    [KEY_RIGHTMETA]     = 0xdc,
> +    [KEY_RIGHTCTRL]     = 0x9d,
> +    [KEY_COMPOSE]       = 0xdd,
> +
> +    [KEY_INSERT]        = 0xd2,
> +    [KEY_DELETE]        = 0xd3,
> +    [KEY_HOME]          = 0xc7,
> +    [KEY_END]           = 0xcf,
> +    [KEY_PAGEUP]        = 0xc9,
> +    [KEY_PAGEDOWN]      = 0xd1,
> +
> +    [KEY_UP]            = 0xc8,
> +    [KEY_LEFT]          = 0xcb,
> +    [KEY_RIGHT]         = 0xcd,
> +    [KEY_DOWN]          = 0xd0,
> +
> +    [KEY_NUMLOCK]       = 0x45,
> +    [KEY_KPSLASH]       = 0xb5,
> +    [KEY_KPASTERISK]    = 0x37,
> +    [KEY_KP7]           = 0x47,
> +    [KEY_KP8]           = 0x48,
> +    [KEY_KP9]           = 0x49,
> +    [KEY_KPMINUS]       = 0x4a,
> +    [KEY_KP4]           = 0x4b,
> +    [KEY_KP5]           = 0x4c,
> +    [KEY_KP6]           = 0x4d,
> +    [KEY_KPPLUS]        = 0x4e,
> +    [KEY_KP1]           = 0x4f,
> +    [KEY_KP2]           = 0x50,
> +    [KEY_KP3]           = 0x51,
> +    [KEY_KP0]           = 0x52,
> +    [KEY_KPDOT]         = 0x53,
> +    [KEY_KPENTER]       = 0x9c,
> +};
> +
> +static const struct keysym_map {
> +    int  normal, shifted;
> +} keysym_map_en_us[KEY_CNT] = {
> +    [KEY_A] = { .normal = 'a', .shifted = 'A' },
> +    [KEY_B] = { .normal = 'b', .shifted = 'B' },
> +    [KEY_C] = { .normal = 'c', .shifted = 'C' },
> +    [KEY_D] = { .normal = 'd', .shifted = 'D' },
> +    [KEY_E] = { .normal = 'e', .shifted = 'E' },
> +    [KEY_F] = { .normal = 'f', .shifted = 'F' },
> +    [KEY_G] = { .normal = 'g', .shifted = 'G' },
> +    [KEY_H] = { .normal = 'h', .shifted = 'H' },
> +    [KEY_I] = { .normal = 'i', .shifted = 'I' },
> +    [KEY_J] = { .normal = 'j', .shifted = 'J' },
> +    [KEY_K] = { .normal = 'k', .shifted = 'K' },
> +    [KEY_L] = { .normal = 'l', .shifted = 'L' },
> +    [KEY_M] = { .normal = 'm', .shifted = 'M' },
> +    [KEY_N] = { .normal = 'n', .shifted = 'N' },
> +    [KEY_O] = { .normal = 'o', .shifted = 'O' },
> +    [KEY_P] = { .normal = 'p', .shifted = 'P' },
> +    [KEY_Q] = { .normal = 'q', .shifted = 'Q' },
> +    [KEY_R] = { .normal = 'r', .shifted = 'R' },
> +    [KEY_S] = { .normal = 's', .shifted = 'S' },
> +    [KEY_T] = { .normal = 't', .shifted = 'T' },
> +    [KEY_U] = { .normal = 'u', .shifted = 'U' },
> +    [KEY_V] = { .normal = 'v', .shifted = 'V' },
> +    [KEY_W] = { .normal = 'w', .shifted = 'W' },
> +    [KEY_X] = { .normal = 'x', .shifted = 'X' },
> +    [KEY_Y] = { .normal = 'y', .shifted = 'Y' },
> +    [KEY_Z] = { .normal = 'z', .shifted = 'Z' },
> +
> +    [KEY_1] = { .normal = '1', .shifted = '!' },
> +    [KEY_2] = { .normal = '2', .shifted = '@' },
> +    [KEY_3] = { .normal = '3', .shifted = '#' },
> +    [KEY_4] = { .normal = '4', .shifted = '$' },
> +    [KEY_5] = { .normal = '5', .shifted = '%' },
> +    [KEY_6] = { .normal = '6', .shifted = '^' },
> +    [KEY_7] = { .normal = '7', .shifted = '&' },
> +    [KEY_8] = { .normal = '8', .shifted = '*' },
> +    [KEY_9] = { .normal = '9', .shifted = '(' },
> +    [KEY_0] = { .normal = '0', .shifted = ')' },
> +
> +    [KEY_MINUS]       = { .normal = '-',  .shifted = '_'  },
> +    [KEY_EQUAL]       = { .normal = '=',  .shifted = '+'  },
> +    [KEY_TAB]         = { .normal = '\t'  },
> +    [KEY_LEFTBRACE]   = { .normal = '[',  .shifted = '{'  },
> +    [KEY_RIGHTBRACE]  = { .normal = ']',  .shifted = '}'  },
> +    [KEY_ENTER]       = { .normal = '\n', },
> +    [KEY_SEMICOLON]   = { .normal = ';',  .shifted = ':'  },
> +    [KEY_APOSTROPHE]  = { .normal = '"',  .shifted = '\'' },
> +    [KEY_BACKSLASH]   = { .normal = '\\', .shifted = '|'  },
> +    [KEY_COMMA]       = { .normal = ',',  .shifted = '<'  },
> +    [KEY_DOT]         = { .normal = '.',  .shifted = '>'  },
> +    [KEY_SLASH]       = { .normal = '/',  .shifted = '?'  },
> +    [KEY_SPACE]       = { .normal = ' '   },
> +
> +    [KEY_BACKSPACE]   = { .normal = QEMU_KEY_BACKSPACE  },
> +    [KEY_UP]          = { .normal = QEMU_KEY_UP         },
> +    [KEY_DOWN]        = { .normal = QEMU_KEY_DOWN       },
> +    [KEY_LEFT]        = { .normal = QEMU_KEY_LEFT       },
> +    [KEY_RIGHT]       = { .normal = QEMU_KEY_RIGHT      },
> +};
> +
> +static void start_mediumraw(FBDevState *s)
> +{
> +    struct termios tattr;
> +
> +    if (s->tty_mediumraw) {
> +        return;
> +    }
> +    trace_fbdev_kbd_raw(1);
> +
> +    /* save state */
> +    tcgetattr(s->tty, &s->tty_attributes);
> +    ioctl(s->tty, KDGKBMODE, &s->tty_mode);
> +    s->tty_flags = fcntl(s->tty, F_GETFL, NULL);
> +
> +    /* setup */
> +    tattr = s->tty_attributes;
> +    tattr.c_cflag &= ~(IXON|IXOFF);
> +    tattr.c_lflag &= ~(ICANON|ECHO|ISIG);
> +    tattr.c_iflag = 0;
> +    tattr.c_cc[VMIN] = 1;
> +    tattr.c_cc[VTIME] = 0;
> +    tcsetattr(s->tty, TCSAFLUSH, &tattr);
> +    ioctl(s->tty, KDSKBMODE, K_MEDIUMRAW);
> +    fcntl(s->tty, F_SETFL, s->tty_flags | O_NONBLOCK);
> +
> +    s->tty_mediumraw = true;
> +}
> +
> +static void stop_mediumraw(FBDevState *s)
> +{
> +    if (!s->tty_mediumraw) {
> +        return;
> +    }
> +    trace_fbdev_kbd_raw(0);
> +
> +    /* restore state */
> +    tcsetattr(s->tty, TCSANOW, &s->tty_attributes);
> +    ioctl(s->tty, KDSKBMODE, s->tty_mode);
> +    fcntl(s->tty, F_SETFL, s->tty_flags);
> +
> +    s->tty_mediumraw = false;
> +}
> +
> +static void send_scancode(int keycode, int up)
> +{
> +    int scancode = scancode_map[keycode];
> +
> +    if (!scancode) {
> +        fprintf(stderr, "%s: unmapped key: 0x%x %s\n",
> +                __func__, keycode, keynames[keycode]);
> +        return;
> +    }
> +    if (scancode & SCANCODE_GREY) {
> +        kbd_put_keycode(SCANCODE_EMUL0);
> +    }
> +    if (up) {
> +        kbd_put_keycode(scancode | SCANCODE_UP);
> +    } else {
> +        kbd_put_keycode(scancode & SCANCODE_KEYCODEMASK);
> +    }
> +}
> +
> +static void send_keysym(int keycode, int shift)
> +{
> +    const struct keysym_map *keysym_map = keysym_map_en_us;
> +    int keysym;
> +
> +    if (shift && keysym_map[keycode].shifted) {
> +        keysym = keysym_map[keycode].shifted;
> +    } else if (keysym_map[keycode].normal) {
> +        keysym = keysym_map[keycode].normal;
> +    } else {
> +        fprintf(stderr, "%s: unmapped key: 0x%x %s\n",
> +                __func__, keycode, keynames[keycode]);
> +        return;
> +    }
> +    kbd_put_keysym(keysym);
> +}
> +
> +static void reset_keys(FBDevState *s)
> +{
> +    int keycode;
> +
> +    for (keycode = 0; keycode < KEY_MAX; keycode++) {
> +        if (s->key_down[keycode]) {
> +            if (qemu_console_is_graphic(NULL)) {
> +                send_scancode(keycode, 1);
> +            }
> +            s->key_down[keycode] = false;
> +        }
> +    }
> +}
> +
> +static void read_mediumraw(void *opaque)
> +{
> +    FBDevState *s = opaque;
> +    uint8_t buf[32];
> +    int i, rc, up, keycode;
> +    bool ctrl, alt, shift;
> +
> +    rc = read(s->tty, buf, sizeof(buf));
> +    switch (rc) {
> +    case -1:
> +        perror("read tty");
> +        goto err;
> +    case 0:
> +        fprintf(stderr, "%s: eof\n", __func__);
> +        goto err;
> +    default:
> +        for (i = 0; i < rc; i++) {
> +            up      = buf[i] & 0x80;
> +            keycode = buf[i] & 0x7f;
> +            if (keycode == 0) {
> +                keycode  = (buf[i+1] & 0x7f) << 7;
> +                keycode |= buf[i+2] & 0x7f;
> +                i += 2;
> +            }
> +            if (keycode > KEY_MAX) {
> +                continue;
> +            }
> +
> +            if (up) {
> +                if (!s->key_down[keycode]) {
> +                    continue;
> +                }
> +                s->key_down[keycode] = false;
> +            } else {
> +                s->key_down[keycode] = true;
> +            }
> +
> +            trace_fbdev_kbd_event(keycode, keynames[keycode], !up);
> +
> +            alt   = s->key_down[KEY_LEFTALT]   || s->key_down[KEY_RIGHTALT];
> +            ctrl  = s->key_down[KEY_LEFTCTRL]  || s->key_down[KEY_RIGHTCTRL];
> +            shift = s->key_down[KEY_LEFTSHIFT] || s->key_down[KEY_RIGHTSHIFT];
> +
> +            if (ctrl && alt && !up) {
> +                if (keycode == KEY_ESC) {
> +                    fprintf(stderr, "=== fbdev emergency escape "
> +                            "(ctrl-alt-esc) ===\n");
> +                    exit(1);
> +                }
> +                if (keycode == KEY_S) {
> +                    s->use_scale = !s->use_scale;
> +                    s->resize_screen++;
> +                    s->redraw_screen++;
> +                    continue;
> +                }
> +                if (keycode >= KEY_F1 && keycode <= KEY_F10) {
> +                    fbdev_activate_vt(s->tty, keycode+1-KEY_F1, false);
> +                    s->key_down[keycode] = false;
> +                    continue;
> +                }
> +                if (keycode >= KEY_1 && keycode <= KEY_9) {
> +                    console_select(keycode-KEY_1);
> +                    reset_keys(s);
> +                    continue;
> +                }
> +            }
> +
> +            if (qemu_console_is_graphic(NULL)) {
> +                /* send scancode to guest kbd emulation */
> +                send_scancode(keycode, up);
> +            } else if (!up) {
> +                /* send keysym to text console (aka '-chardev vc') */
> +                send_keysym(keycode, shift);
> +            }
> +        }
> +    }
> +    return;
> +
> +err:
> +    exit(1);
> +}
> +
> +/* -------------------------------------------------------------------- */
> +
> +static void fbdev_cls(FBDevState *s)
> +{
> +    memset(s->fb_mem + s->fb_mem_offset, 0,
> +           s->fb_fix.line_length * s->fb_var.yres);
> +}
> +
> +static int fbdev_activate_vt(int tty, int vtno, bool wait)
> +{
> +    trace_fbdev_vt_activate(vtno, wait);
> +
> +    if (ioctl(tty, VT_ACTIVATE, vtno) < 0) {
> +        perror("ioctl VT_ACTIVATE");
> +        return -1;
> +    }
> +
> +    if (wait) {
> +        if (ioctl(tty, VT_WAITACTIVE, vtno) < 0) {
> +            perror("ioctl VT_WAITACTIVE");
> +            return -1;
> +        }
> +        trace_fbdev_vt_activated();
> +    }
> +
> +    return 0;
> +}
> +
> +static void fbdev_cleanup(FBDevState *s)
> +{
> +    trace_fbdev_cleanup();
> +
> +    /* release pixman stuff */
> +    pixman_region_fini(&s->dirty);
> +    if (s->framebuffer) {
> +        pixman_image_unref(s->framebuffer);
> +        s->framebuffer = NULL;
> +    }
> +    if (s->sref) {
> +        pixman_image_unref(s->sref);
> +        s->sref = NULL;
> +    }
> +    if (s->swork) {
> +        pixman_image_unref(s->swork);
> +        s->swork = NULL;
> +    }
> +
> +    /* restore console */
> +    if (s->fb_mem != NULL) {
> +        munmap(s->fb_mem, s->fb_fix.smem_len+s->fb_mem_offset);
> +        s->fb_mem = NULL;
> +    }
> +    if (s->fb != -1) {
> +        if (ioctl(s->fb, FBIOPUT_VSCREENINFO, &s->fb_ovar) < 0) {
> +            perror("ioctl FBIOPUT_VSCREENINFO");
> +        }
> +        close(s->fb);
> +        s->fb = -1;
> +    }
> +
> +    if (s->tty != -1) {
> +        stop_mediumraw(s);
> +        if (ioctl(s->tty, KDSETMODE, s->kd_omode) < 0) {
> +            perror("ioctl KDSETMODE");
> +        }
> +        if (ioctl(s->tty, VT_SETMODE, &s->vt_omode) < 0) {
> +            perror("ioctl VT_SETMODE");
> +        }
> +        if (s->orig_vtno) {
> +            fbdev_activate_vt(s->tty, s->orig_vtno, true);
> +        }
> +        qemu_set_fd_handler(s->tty, NULL, NULL, NULL);
> +        close(s->tty);
> +        s->tty = -1;
> +    }
> +
> +    g_free(s->device);
> +    s->device = NULL;
> +}
> +
> +static int fbdev_init(FBDevState *s, const char *device, Error **err)
> +{
> +    struct vt_stat vts;
> +    unsigned long page_mask;
> +    char ttyname[32];
> +
> +    /* open framebuffer */
> +    if (device == NULL) {
> +        device = getenv("FRAMEBUFFER");
> +    }
> +    if (device == NULL) {
> +        device = "/dev/fb0";
> +    }

Maybe this is a matter of taste, but I think that having this logic at
this layer makes the API harder to use. What about moving this to the
call in vl.c and making the device name required in QMP?

> +    s->fb = open(device, O_RDWR);
> +    if (s->fb == -1) {
> +        error_setg(err, "open %s: %s\n", device, strerror(errno));

We have error_setg_errno() :)

> +        return -1;
> +    }
> +
> +    /* open virtual console */
> +    s->tty = 0;
> +    if (ioctl(s->tty, VT_GETSTATE, &vts) < 0) {
> +        fprintf(stderr, "Not started from virtual terminal, "
> +                "trying to open one.\n");
> +
> +        snprintf(ttyname, sizeof(ttyname), "/dev/tty0");
> +        s->tty = open(ttyname, O_RDWR);
> +        if (s->tty == -1) {
> +            error_setg(err, "open %s: %s\n", ttyname, strerror(errno));
> +            goto err_early;
> +        }
> +        if (ioctl(s->tty, VT_OPENQRY, &s->vtno) < 0) {
> +            error_setg(err, "ioctl VT_OPENQRY: %s\n", strerror(errno));
> +            goto err_early;
> +        }
> +        if (ioctl(s->tty, VT_GETSTATE, &vts) < 0) {
> +            error_setg(err, "ioctl VT_GETSTATE: %s\n", strerror(errno));
> +            goto err_early;
> +        }
> +        close(s->tty);
> +
> +        snprintf(ttyname, sizeof(ttyname), "/dev/tty%d", s->vtno);
> +        s->tty = open(ttyname, O_RDWR);
> +        if (s->tty == -1) {
> +            error_setg(err, "open %s: %s\n", ttyname, strerror(errno));
> +            goto err_early;
> +        }
> +        s->orig_vtno = vts.v_active;
> +        fprintf(stderr, "Switching to vt %d (current %d).\n",
> +                s->vtno, s->orig_vtno);
> +    } else {
> +        s->orig_vtno = 0;
> +        s->vtno = vts.v_active;
> +        fprintf(stderr, "Started at vt %d, using it.\n", s->vtno);
> +    }
> +    fbdev_activate_vt(s->tty, s->vtno, true);
> +
> +    /* get current settings (which we have to restore) */
> +    if (ioctl(s->fb, FBIOGET_VSCREENINFO, &s->fb_ovar) < 0) {
> +        error_setg(err, "ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
> +        goto err_early;
> +    }
> +    if (ioctl(s->tty, KDGETMODE, &s->kd_omode) < 0) {
> +        error_setg(err, "ioctl KDGETMODE: %s\n", strerror(errno));
> +        goto err_early;
> +    }
> +    if (ioctl(s->tty, VT_GETMODE, &s->vt_omode) < 0) {
> +        error_setg(err, "ioctl VT_GETMODE: %s\n", strerror(errno));
> +        goto err_early;
> +    }
> +
> +    /* checks & initialisation */
> +    if (ioctl(s->fb, FBIOGET_FSCREENINFO, &s->fb_fix) < 0) {
> +        error_setg(err, "ioctl : %s\n", strerror(errno));
> +        perror("ioctl FBIOGET_FSCREENINFO");
> +        goto err;
> +    }
> +    if (ioctl(s->fb, FBIOGET_VSCREENINFO, &s->fb_var) < 0) {
> +        error_setg(err, "ioctl FBIOGET_VSCREENINFO: %s\n", strerror(errno));
> +        goto err;
> +    }
> +    if (s->fb_fix.type != FB_TYPE_PACKED_PIXELS) {
> +        error_setg(err, "can handle only packed pixel frame buffers\n");
> +        goto err;
> +    }
> +    switch (s->fb_var.bits_per_pixel) {
> +    case 32:
> +        break;
> +    default:
> +        error_setg(err, "can't handle %d bpp frame buffers\n",
> +                   s->fb_var.bits_per_pixel);
> +        goto err;
> +    }
> +
> +    page_mask = getpagesize()-1;
> +    fb_switch_state = FB_ACTIVE;
> +    s->fb_mem_offset = (unsigned long)(s->fb_fix.smem_start) & page_mask;
> +    s->fb_mem = mmap(NULL, s->fb_fix.smem_len+s->fb_mem_offset,
> +                     PROT_READ|PROT_WRITE, MAP_SHARED, s->fb, 0);
> +    if (s->fb_mem == MAP_FAILED) {
> +        error_setg(err, "mmap: %s\n", strerror(errno));
> +        goto err;
> +    }
> +    /* move viewport to upper left corner */
> +    if (s->fb_var.xoffset != 0 || s->fb_var.yoffset != 0) {
> +        s->fb_var.xoffset = 0;
> +        s->fb_var.yoffset = 0;
> +        if (ioctl(s->fb, FBIOPAN_DISPLAY, &s->fb_var) < 0) {
> +            error_setg(err, "ioctl FBIOPAN_DISPLAY: %s\n", strerror(errno));
> +            goto err;
> +        }
> +    }
> +    if (ioctl(s->tty, KDSETMODE, KD_GRAPHICS) < 0) {
> +        error_setg(err, "ioctl KDSETMODE: %s\n", strerror(errno));
> +        goto err;
> +    }
> +    /* some fb drivers need this again after switching to graphics ... */
> +    fbdev_activate_vt(s->tty, s->vtno, true);
> +
> +    fbdev_cls(s);
> +
> +    start_mediumraw(s);
> +    qemu_set_fd_handler(s->tty, read_mediumraw, NULL, s);
> +
> +    s->framebuffer = pixman_from_framebuffer(s);
> +    pixman_region_init(&s->dirty);
> +    s->device = g_strdup(device);
> +    return 0;
> +
> +err_early:
> +    if (s->tty > 0) {
> +        close(s->tty);
> +    }
> +    close(s->fb);
> +    return -1;
> +
> +err:
> +    fbdev_cleanup(s);
> +    return -1;
> +}
> +
> +static void
> +fbdev_catch_fatal_signal(int signr)
> +{
> +    fprintf(stderr, "%s: %s, restoring linux console state ...\n",
> +            __func__, strsignal(signr));
> +    fbdev_cleanup(fb);
> +    signal(SIGABRT, SIG_DFL);
> +    fprintf(stderr, "%s: ... done, going abort() now.\n", __func__);
> +    abort();
> +}
> +
> +static void fbdev_catch_exit_signals(void)
> +{
> +    static const int signals[] = {
> +        SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGBUS
> +    };
> +    struct sigaction act, old;
> +    int i;
> +
> +    memset(&act, 0, sizeof(act));
> +    act.sa_handler = fbdev_catch_fatal_signal;
> +    act.sa_flags = SA_RESETHAND;
> +    sigemptyset(&act.sa_mask);
> +    for (i = 0; i < ARRAY_SIZE(signals); i++) {
> +        sigaction(signals[i], &act, &old);
> +    }
> +}
> +
> +/* -------------------------------------------------------------------- */
> +/* console switching                                                    */
> +
> +static void fbdev_switch_signal(int signal)
> +{
> +    if (signal == SIG_REL) {
> +        /* release */
> +        trace_fbdev_vt_release_request();
> +        fb_switch_state = FB_REL_REQ;
> +    }
> +    if (signal == SIG_ACQ) {
> +        /* acquisition */
> +        trace_fbdev_vt_aquire_request();
> +        fb_switch_state = FB_ACQ_REQ;
> +    }
> +}
> +
> +static void fbdev_switch_release(FBDevState *s)
> +{
> +    stop_mediumraw(s);
> +    ioctl(s->tty, KDSETMODE, s->kd_omode);
> +    ioctl(s->tty, VT_RELDISP, 1);
> +    fb_switch_state = FB_INACTIVE;
> +    trace_fbdev_vt_released();
> +}
> +
> +static void fbdev_switch_acquire(FBDevState *s)
> +{
> +    ioctl(s->tty, VT_RELDISP, VT_ACKACQ);
> +    start_mediumraw(s);
> +    reset_keys(s);
> +    ioctl(s->tty, KDSETMODE, KD_GRAPHICS);
> +    fb_switch_state = FB_ACTIVE;
> +    trace_fbdev_vt_aquired();
> +}
> +
> +static int fbdev_switch_init(FBDevState *s)
> +{
> +    struct sigaction act, old;
> +
> +    memset(&act, 0, sizeof(act));
> +    act.sa_handler  = fbdev_switch_signal;
> +    sigemptyset(&act.sa_mask);
> +    sigaction(SIG_REL, &act, &old);
> +    sigaction(SIG_ACQ, &act, &old);
> +
> +    if (ioctl(s->tty, VT_GETMODE, &s->vt_mode) < 0) {
> +        perror("ioctl VT_GETMODE");
> +        exit(1);
> +    }
> +    s->vt_mode.mode   = VT_PROCESS;
> +    s->vt_mode.waitv  = 0;
> +    s->vt_mode.relsig = SIG_REL;
> +    s->vt_mode.acqsig = SIG_ACQ;
> +
> +    if (ioctl(s->tty, VT_SETMODE, &s->vt_mode) < 0) {
> +        perror("ioctl VT_SETMODE");
> +        exit(1);
> +    }
> +    return 0;
> +}
> +
> +/* -------------------------------------------------------------------- */
> +/* rendering                                                            */
> +
> +static void fbdev_render(FBDevState *s)
> +{
> +    assert(s->surface);
> +
> +    pixman_image_set_clip_region(s->swork, &s->dirty);
> +    pixman_image_composite(PIXMAN_OP_SRC, s->swork, NULL, s->framebuffer,
> +                           0, 0, 0, 0, 0, 0, s->fb_var.xres, s->fb_var.yres);
> +    pixman_region_fini(&s->dirty);
> +    pixman_region_init(&s->dirty);
> +}
> +
> +static void fbdev_unrender_ptr(FBDevState *s)
> +{
> +    if (!s->pw && !s->ph) {
> +        return;
> +    }
> +    pixman_region_union_rect(&s->dirty, &s->dirty,
> +                             s->px, s->py, s->pw, s->ph);
> +    s->ph = s->pw = 0;
> +}
> +
> +static void fbdev_render_ptr(FBDevState *s)
> +{
> +    pixman_region16_t region;
> +    pixman_transform_t transform;
> +
> +    if (!s->mon || !s->ptr_image) {
> +        return;
> +    }
> +    if (s->mx < 0 || s->mx >= s->cw || s->my < 0 || s->my >= s->ch) {
> +        return;
> +    }
> +
> +    s->px = s->mx - s->ptr_cursor->hot_x;
> +    s->py = s->my - s->ptr_cursor->hot_y;
> +    s->pw = s->ptr_cursor->width;
> +    s->ph = s->ptr_cursor->height;
> +
> +    pixman_transform_init_identity(&transform);
> +    pixman_transform_translate(&transform, NULL,
> +                               pixman_int_to_fixed(-s->cx),
> +                               pixman_int_to_fixed(-s->cy));
> +    if (s->use_scale) {
> +        pixman_transform_scale(&transform, NULL,
> +                               pixman_double_to_fixed(1/s->scale),
> +                               pixman_double_to_fixed(1/s->scale));
> +    }
> +    pixman_transform_translate(&transform, NULL,
> +                               pixman_int_to_fixed(-s->px),
> +                               pixman_int_to_fixed(-s->py));
> +    pixman_image_set_transform(s->ptr_image, &transform);
> +
> +    pixman_region_init_rect(&region, 0, 0, s->pw, s->ph);
> +    pixman_image_set_clip_region(s->ptr_image, &region);
> +
> +    pixman_image_composite(PIXMAN_OP_OVER, s->ptr_image, NULL, s->framebuffer,
> +                           0, 0, 0, 0, 0, 0, s->fb_var.xres, s->fb_var.yres);
> +
> +    pixman_region_fini(&region);
> +    s->ptr_refresh = 0;
> +}
> +
> +/* -------------------------------------------------------------------- */
> +/* qemu interfaces                                                      */
> +
> +static void fbdev_update(DisplayChangeListener *dcl,
> +                         int x, int y, int w, int h)
> +{
> +    FBDevState *s = container_of(dcl, FBDevState, dcl);
> +
> +    if (fb_switch_state != FB_ACTIVE) {
> +        return;
> +    }
> +
> +    if (s->resize_screen) {
> +        double xs, ys;
> +
> +        trace_fbdev_dpy_resize(surface_width(s->surface),
> +                               surface_height(s->surface));
> +        s->resize_screen = 0;
> +        s->cx = 0; s->cy = 0;
> +        s->cw = surface_width(s->surface);
> +        s->ch = surface_height(s->surface);
> +
> +        if (s->use_scale) {
> +            xs = (double)s->fb_var.xres / s->cw;
> +            ys = (double)s->fb_var.yres / s->ch;
> +            if (xs > ys) {
> +                s->scale = ys;
> +                s->cx = (s->fb_var.xres -
> +                         surface_width(s->surface)*s->scale) / 2;
> +            } else {
> +                s->scale = xs;
> +                s->cy = (s->fb_var.yres -
> +                         surface_height(s->surface)*s->scale) / 2;
> +            }
> +        } else {
> +            s->scale = 1;
> +            if (surface_width(s->surface) < s->fb_var.xres) {
> +                s->cx = (s->fb_var.xres - surface_width(s->surface)) / 2;
> +            }
> +            if (surface_height(s->surface) < s->fb_var.yres) {
> +                s->cy = (s->fb_var.yres - surface_height(s->surface)) / 2;
> +            }
> +        }
> +        if (s->sref) {
> +            pixman_image_unref(s->sref);
> +        }
> +        s->sref = pixman_image_ref(s->surface->image);
> +
> +        if (s->swork) {
> +            pixman_image_unref(s->swork);
> +        }
> +        s->swork = pixman_image_clone(s->sref);
> +
> +        pixman_transform_init_identity(&s->transform);
> +        pixman_transform_translate(&s->transform, NULL,
> +                                   pixman_int_to_fixed(-s->cx),
> +                                   pixman_int_to_fixed(-s->cy));
> +        if (s->use_scale) {
> +            pixman_transform_scale(&s->transform, NULL,
> +                                   pixman_double_to_fixed(1/s->scale),
> +                                   pixman_double_to_fixed(1/s->scale));
> +        }
> +        pixman_image_set_transform(s->swork, &s->transform);
> +
> +        pixman_image_set_filter(s->swork, s->pfilter, NULL, 0);
> +    }
> +
> +    if (s->redraw_screen) {
> +        trace_fbdev_dpy_redraw();
> +        s->redraw_screen = 0;
> +        fbdev_cls(s);
> +        x = 0; y = 0;
> +        w = surface_width(s->surface);
> +        h = surface_height(s->surface);
> +    }
> +
> +    pixman_region_union_rect(&s->dirty, &s->dirty, x, y, w, h);
> +    if (s->ptr_image && s->mon && s->pw && s->ph) {
> +        s->ptr_refresh++;
> +    }
> +}
> +
> +static void fbdev_switch(DisplayChangeListener *dcl,
> +                         DisplaySurface *new_surface)
> +{
> +    FBDevState *s = container_of(dcl, FBDevState, dcl);
> +
> +    s->surface = new_surface;
> +    s->resize_screen++;
> +    s->redraw_screen++;
> +}
> +
> +static void fbdev_refresh(DisplayChangeListener *dcl)
> +{
> +    FBDevState *s = container_of(dcl, FBDevState, dcl);
> +
> +    switch (fb_switch_state) {
> +    case FB_REL_REQ:
> +        fbdev_switch_release(s);
> +        /* fall though */
> +    case FB_INACTIVE:
> +        return;
> +    case FB_ACQ_REQ:
> +        fbdev_switch_acquire(s);
> +        s->redraw_screen++;
> +        /* fall though */
> +    case FB_ACTIVE:
> +        break;
> +    }
> +
> +    graphic_hw_update(NULL);
> +    if (s->redraw_screen) {
> +        fbdev_update(dcl, 0, 0, 0, 0);
> +    }
> +
> +    if (s->ptr_refresh) {
> +        fbdev_unrender_ptr(s);
> +    }
> +    if (pixman_region_not_empty(&s->dirty)) {
> +        fbdev_render(s);
> +    }
> +    if (s->ptr_refresh) {
> +        fbdev_render_ptr(s);
> +    }
> +}
> +
> +static void fbdev_mouse_set(DisplayChangeListener *dcl, int x, int y, int on)
> +{
> +    FBDevState *s = container_of(dcl, FBDevState, dcl);
> +
> +    s->ptr_refresh++;
> +    s->mx = x;
> +    s->my = y;
> +    s->mon = on;
> +}
> +
> +static void fbdev_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
> +{
> +    FBDevState *s = container_of(dcl, FBDevState, dcl);
> +
> +    s->ptr_refresh++;
> +
> +    if (s->ptr_cursor) {
> +        cursor_put(s->ptr_cursor);
> +        s->ptr_cursor = NULL;
> +    }
> +    if (s->ptr_image) {
> +        pixman_image_unref(s->ptr_image);
> +        s->ptr_image = NULL;
> +    }
> +
> +    if (!cursor) {
> +        return;
> +    }
> +
> +    s->ptr_cursor = cursor;
> +    cursor_get(s->ptr_cursor);
> +    s->ptr_image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
> +                                            cursor->width, cursor->height,
> +                                            cursor->data,
> +                                            cursor->width * 4);
> +    pixman_image_set_filter(s->ptr_image, s->pfilter, NULL, 0);
> +}
> +
> +static const DisplayChangeListenerOps fbdev_ops = {
> +    .dpy_name          = "fbdev",
> +    .dpy_gfx_update    = fbdev_update,
> +    .dpy_gfx_switch    = fbdev_switch,
> +    .dpy_refresh       = fbdev_refresh,
> +    .dpy_mouse_set     = fbdev_mouse_set,
> +    .dpy_cursor_define = fbdev_cursor_define,
> +};
> +
> +static void fbdev_exit_notifier(Notifier *notifier, void *data)
> +{
> +    FBDevState *s = container_of(notifier, FBDevState, exit_notifier);
> +    fbdev_cleanup(s);
> +}
> +
> +int fbdev_display_init(const char *device, bool scale, Error **err)
> +{
> +    FBDevState *s;
> +
> +    if (fb != NULL) {
> +        return 0;
> +    }
> +
> +    s = g_new0(FBDevState, 1);
> +    s->tty = -1;
> +    s->fb = -1;
> +    s->mice = -1;
> +    s->pfilter = PIXMAN_FILTER_GOOD;
> +
> +    if (fbdev_init(s, device, err) != 0) {
> +        g_free(s);
> +        return -1;
> +    }
> +
> +    s->exit_notifier.notify = fbdev_exit_notifier;
> +    qemu_add_exit_notifier(&s->exit_notifier);
> +    fbdev_switch_init(s);
> +    fbdev_catch_exit_signals();
> +    init_mouse(s);
> +    s->use_scale = scale;
> +
> +    s->dcl.ops = &fbdev_ops;
> +    register_displaychangelistener(&s->dcl);
> +
> +    trace_fbdev_enabled();
> +    fb = s;
> +    return 0;
> +}
> +
> +void fbdev_display_uninit(void)
> +{
> +    FBDevState *s = fb;
> +
> +    if (s == NULL) {
> +        return;
> +    }
> +
> +    unregister_displaychangelistener(&s->dcl);
> +    qemu_remove_exit_notifier(&s->exit_notifier);
> +    fbdev_cleanup(s);
> +    uninit_mouse(s);
> +    g_free(s);
> +    fb = NULL;
> +}
> diff --git a/ui/linux-keynames.h b/ui/linux-keynames.h
> new file mode 100644
> index 0000000..058af28
> --- /dev/null
> +++ b/ui/linux-keynames.h
> @@ -0,0 +1,388 @@
> +/*
> + *   awk '/#define KEY_/ { printf("    [%s] = \"%s\",\n",$2,$2); }' \
> + *       /usr/include/linux/input.h
> + */
> +    [KEY_RESERVED] = "KEY_RESERVED",
> +    [KEY_ESC] = "KEY_ESC",
> +    [KEY_1] = "KEY_1",
> +    [KEY_2] = "KEY_2",
> +    [KEY_3] = "KEY_3",
> +    [KEY_4] = "KEY_4",
> +    [KEY_5] = "KEY_5",
> +    [KEY_6] = "KEY_6",
> +    [KEY_7] = "KEY_7",
> +    [KEY_8] = "KEY_8",
> +    [KEY_9] = "KEY_9",
> +    [KEY_0] = "KEY_0",
> +    [KEY_MINUS] = "KEY_MINUS",
> +    [KEY_EQUAL] = "KEY_EQUAL",
> +    [KEY_BACKSPACE] = "KEY_BACKSPACE",
> +    [KEY_TAB] = "KEY_TAB",
> +    [KEY_Q] = "KEY_Q",
> +    [KEY_W] = "KEY_W",
> +    [KEY_E] = "KEY_E",
> +    [KEY_R] = "KEY_R",
> +    [KEY_T] = "KEY_T",
> +    [KEY_Y] = "KEY_Y",
> +    [KEY_U] = "KEY_U",
> +    [KEY_I] = "KEY_I",
> +    [KEY_O] = "KEY_O",
> +    [KEY_P] = "KEY_P",
> +    [KEY_LEFTBRACE] = "KEY_LEFTBRACE",
> +    [KEY_RIGHTBRACE] = "KEY_RIGHTBRACE",
> +    [KEY_ENTER] = "KEY_ENTER",
> +    [KEY_LEFTCTRL] = "KEY_LEFTCTRL",
> +    [KEY_A] = "KEY_A",
> +    [KEY_S] = "KEY_S",
> +    [KEY_D] = "KEY_D",
> +    [KEY_F] = "KEY_F",
> +    [KEY_G] = "KEY_G",
> +    [KEY_H] = "KEY_H",
> +    [KEY_J] = "KEY_J",
> +    [KEY_K] = "KEY_K",
> +    [KEY_L] = "KEY_L",
> +    [KEY_SEMICOLON] = "KEY_SEMICOLON",
> +    [KEY_APOSTROPHE] = "KEY_APOSTROPHE",
> +    [KEY_GRAVE] = "KEY_GRAVE",
> +    [KEY_LEFTSHIFT] = "KEY_LEFTSHIFT",
> +    [KEY_BACKSLASH] = "KEY_BACKSLASH",
> +    [KEY_Z] = "KEY_Z",
> +    [KEY_X] = "KEY_X",
> +    [KEY_C] = "KEY_C",
> +    [KEY_V] = "KEY_V",
> +    [KEY_B] = "KEY_B",
> +    [KEY_N] = "KEY_N",
> +    [KEY_M] = "KEY_M",
> +    [KEY_COMMA] = "KEY_COMMA",
> +    [KEY_DOT] = "KEY_DOT",
> +    [KEY_SLASH] = "KEY_SLASH",
> +    [KEY_RIGHTSHIFT] = "KEY_RIGHTSHIFT",
> +    [KEY_KPASTERISK] = "KEY_KPASTERISK",
> +    [KEY_LEFTALT] = "KEY_LEFTALT",
> +    [KEY_SPACE] = "KEY_SPACE",
> +    [KEY_CAPSLOCK] = "KEY_CAPSLOCK",
> +    [KEY_F1] = "KEY_F1",
> +    [KEY_F2] = "KEY_F2",
> +    [KEY_F3] = "KEY_F3",
> +    [KEY_F4] = "KEY_F4",
> +    [KEY_F5] = "KEY_F5",
> +    [KEY_F6] = "KEY_F6",
> +    [KEY_F7] = "KEY_F7",
> +    [KEY_F8] = "KEY_F8",
> +    [KEY_F9] = "KEY_F9",
> +    [KEY_F10] = "KEY_F10",
> +    [KEY_NUMLOCK] = "KEY_NUMLOCK",
> +    [KEY_SCROLLLOCK] = "KEY_SCROLLLOCK",
> +    [KEY_KP7] = "KEY_KP7",
> +    [KEY_KP8] = "KEY_KP8",
> +    [KEY_KP9] = "KEY_KP9",
> +    [KEY_KPMINUS] = "KEY_KPMINUS",
> +    [KEY_KP4] = "KEY_KP4",
> +    [KEY_KP5] = "KEY_KP5",
> +    [KEY_KP6] = "KEY_KP6",
> +    [KEY_KPPLUS] = "KEY_KPPLUS",
> +    [KEY_KP1] = "KEY_KP1",
> +    [KEY_KP2] = "KEY_KP2",
> +    [KEY_KP3] = "KEY_KP3",
> +    [KEY_KP0] = "KEY_KP0",
> +    [KEY_KPDOT] = "KEY_KPDOT",
> +    [KEY_ZENKAKUHANKAKU] = "KEY_ZENKAKUHANKAKU",
> +    [KEY_102ND] = "KEY_102ND",
> +    [KEY_F11] = "KEY_F11",
> +    [KEY_F12] = "KEY_F12",
> +    [KEY_RO] = "KEY_RO",
> +    [KEY_KATAKANA] = "KEY_KATAKANA",
> +    [KEY_HIRAGANA] = "KEY_HIRAGANA",
> +    [KEY_HENKAN] = "KEY_HENKAN",
> +    [KEY_KATAKANAHIRAGANA] = "KEY_KATAKANAHIRAGANA",
> +    [KEY_MUHENKAN] = "KEY_MUHENKAN",
> +    [KEY_KPJPCOMMA] = "KEY_KPJPCOMMA",
> +    [KEY_KPENTER] = "KEY_KPENTER",
> +    [KEY_RIGHTCTRL] = "KEY_RIGHTCTRL",
> +    [KEY_KPSLASH] = "KEY_KPSLASH",
> +    [KEY_SYSRQ] = "KEY_SYSRQ",
> +    [KEY_RIGHTALT] = "KEY_RIGHTALT",
> +    [KEY_LINEFEED] = "KEY_LINEFEED",
> +    [KEY_HOME] = "KEY_HOME",
> +    [KEY_UP] = "KEY_UP",
> +    [KEY_PAGEUP] = "KEY_PAGEUP",
> +    [KEY_LEFT] = "KEY_LEFT",
> +    [KEY_RIGHT] = "KEY_RIGHT",
> +    [KEY_END] = "KEY_END",
> +    [KEY_DOWN] = "KEY_DOWN",
> +    [KEY_PAGEDOWN] = "KEY_PAGEDOWN",
> +    [KEY_INSERT] = "KEY_INSERT",
> +    [KEY_DELETE] = "KEY_DELETE",
> +    [KEY_MACRO] = "KEY_MACRO",
> +    [KEY_MUTE] = "KEY_MUTE",
> +    [KEY_VOLUMEDOWN] = "KEY_VOLUMEDOWN",
> +    [KEY_VOLUMEUP] = "KEY_VOLUMEUP",
> +    [KEY_POWER] = "KEY_POWER",
> +    [KEY_KPEQUAL] = "KEY_KPEQUAL",
> +    [KEY_KPPLUSMINUS] = "KEY_KPPLUSMINUS",
> +    [KEY_PAUSE] = "KEY_PAUSE",
> +    [KEY_SCALE] = "KEY_SCALE",
> +    [KEY_KPCOMMA] = "KEY_KPCOMMA",
> +    [KEY_HANGEUL] = "KEY_HANGEUL",
> +    [KEY_HANGUEL] = "KEY_HANGUEL",
> +    [KEY_HANJA] = "KEY_HANJA",
> +    [KEY_YEN] = "KEY_YEN",
> +    [KEY_LEFTMETA] = "KEY_LEFTMETA",
> +    [KEY_RIGHTMETA] = "KEY_RIGHTMETA",
> +    [KEY_COMPOSE] = "KEY_COMPOSE",
> +    [KEY_STOP] = "KEY_STOP",
> +    [KEY_AGAIN] = "KEY_AGAIN",
> +    [KEY_PROPS] = "KEY_PROPS",
> +    [KEY_UNDO] = "KEY_UNDO",
> +    [KEY_FRONT] = "KEY_FRONT",
> +    [KEY_COPY] = "KEY_COPY",
> +    [KEY_OPEN] = "KEY_OPEN",
> +    [KEY_PASTE] = "KEY_PASTE",
> +    [KEY_FIND] = "KEY_FIND",
> +    [KEY_CUT] = "KEY_CUT",
> +    [KEY_HELP] = "KEY_HELP",
> +    [KEY_MENU] = "KEY_MENU",
> +    [KEY_CALC] = "KEY_CALC",
> +    [KEY_SETUP] = "KEY_SETUP",
> +    [KEY_SLEEP] = "KEY_SLEEP",
> +    [KEY_WAKEUP] = "KEY_WAKEUP",
> +    [KEY_FILE] = "KEY_FILE",
> +    [KEY_SENDFILE] = "KEY_SENDFILE",
> +    [KEY_DELETEFILE] = "KEY_DELETEFILE",
> +    [KEY_XFER] = "KEY_XFER",
> +    [KEY_PROG1] = "KEY_PROG1",
> +    [KEY_PROG2] = "KEY_PROG2",
> +    [KEY_WWW] = "KEY_WWW",
> +    [KEY_MSDOS] = "KEY_MSDOS",
> +    [KEY_COFFEE] = "KEY_COFFEE",
> +    [KEY_SCREENLOCK] = "KEY_SCREENLOCK",
> +    [KEY_DIRECTION] = "KEY_DIRECTION",
> +    [KEY_CYCLEWINDOWS] = "KEY_CYCLEWINDOWS",
> +    [KEY_MAIL] = "KEY_MAIL",
> +    [KEY_BOOKMARKS] = "KEY_BOOKMARKS",
> +    [KEY_COMPUTER] = "KEY_COMPUTER",
> +    [KEY_BACK] = "KEY_BACK",
> +    [KEY_FORWARD] = "KEY_FORWARD",
> +    [KEY_CLOSECD] = "KEY_CLOSECD",
> +    [KEY_EJECTCD] = "KEY_EJECTCD",
> +    [KEY_EJECTCLOSECD] = "KEY_EJECTCLOSECD",
> +    [KEY_NEXTSONG] = "KEY_NEXTSONG",
> +    [KEY_PLAYPAUSE] = "KEY_PLAYPAUSE",
> +    [KEY_PREVIOUSSONG] = "KEY_PREVIOUSSONG",
> +    [KEY_STOPCD] = "KEY_STOPCD",
> +    [KEY_RECORD] = "KEY_RECORD",
> +    [KEY_REWIND] = "KEY_REWIND",
> +    [KEY_PHONE] = "KEY_PHONE",
> +    [KEY_ISO] = "KEY_ISO",
> +    [KEY_CONFIG] = "KEY_CONFIG",
> +    [KEY_HOMEPAGE] = "KEY_HOMEPAGE",
> +    [KEY_REFRESH] = "KEY_REFRESH",
> +    [KEY_EXIT] = "KEY_EXIT",
> +    [KEY_MOVE] = "KEY_MOVE",
> +    [KEY_EDIT] = "KEY_EDIT",
> +    [KEY_SCROLLUP] = "KEY_SCROLLUP",
> +    [KEY_SCROLLDOWN] = "KEY_SCROLLDOWN",
> +    [KEY_KPLEFTPAREN] = "KEY_KPLEFTPAREN",
> +    [KEY_KPRIGHTPAREN] = "KEY_KPRIGHTPAREN",
> +    [KEY_NEW] = "KEY_NEW",
> +    [KEY_REDO] = "KEY_REDO",
> +    [KEY_F13] = "KEY_F13",
> +    [KEY_F14] = "KEY_F14",
> +    [KEY_F15] = "KEY_F15",
> +    [KEY_F16] = "KEY_F16",
> +    [KEY_F17] = "KEY_F17",
> +    [KEY_F18] = "KEY_F18",
> +    [KEY_F19] = "KEY_F19",
> +    [KEY_F20] = "KEY_F20",
> +    [KEY_F21] = "KEY_F21",
> +    [KEY_F22] = "KEY_F22",
> +    [KEY_F23] = "KEY_F23",
> +    [KEY_F24] = "KEY_F24",
> +    [KEY_PLAYCD] = "KEY_PLAYCD",
> +    [KEY_PAUSECD] = "KEY_PAUSECD",
> +    [KEY_PROG3] = "KEY_PROG3",
> +    [KEY_PROG4] = "KEY_PROG4",
> +    [KEY_DASHBOARD] = "KEY_DASHBOARD",
> +    [KEY_SUSPEND] = "KEY_SUSPEND",
> +    [KEY_CLOSE] = "KEY_CLOSE",
> +    [KEY_PLAY] = "KEY_PLAY",
> +    [KEY_FASTFORWARD] = "KEY_FASTFORWARD",
> +    [KEY_BASSBOOST] = "KEY_BASSBOOST",
> +    [KEY_PRINT] = "KEY_PRINT",
> +    [KEY_HP] = "KEY_HP",
> +    [KEY_CAMERA] = "KEY_CAMERA",
> +    [KEY_SOUND] = "KEY_SOUND",
> +    [KEY_QUESTION] = "KEY_QUESTION",
> +    [KEY_EMAIL] = "KEY_EMAIL",
> +    [KEY_CHAT] = "KEY_CHAT",
> +    [KEY_SEARCH] = "KEY_SEARCH",
> +    [KEY_CONNECT] = "KEY_CONNECT",
> +    [KEY_FINANCE] = "KEY_FINANCE",
> +    [KEY_SPORT] = "KEY_SPORT",
> +    [KEY_SHOP] = "KEY_SHOP",
> +    [KEY_ALTERASE] = "KEY_ALTERASE",
> +    [KEY_CANCEL] = "KEY_CANCEL",
> +    [KEY_BRIGHTNESSDOWN] = "KEY_BRIGHTNESSDOWN",
> +    [KEY_BRIGHTNESSUP] = "KEY_BRIGHTNESSUP",
> +    [KEY_MEDIA] = "KEY_MEDIA",
> +    [KEY_SWITCHVIDEOMODE] = "KEY_SWITCHVIDEOMODE",
> +    [KEY_KBDILLUMTOGGLE] = "KEY_KBDILLUMTOGGLE",
> +    [KEY_KBDILLUMDOWN] = "KEY_KBDILLUMDOWN",
> +    [KEY_KBDILLUMUP] = "KEY_KBDILLUMUP",
> +    [KEY_SEND] = "KEY_SEND",
> +    [KEY_REPLY] = "KEY_REPLY",
> +    [KEY_FORWARDMAIL] = "KEY_FORWARDMAIL",
> +    [KEY_SAVE] = "KEY_SAVE",
> +    [KEY_DOCUMENTS] = "KEY_DOCUMENTS",
> +    [KEY_BATTERY] = "KEY_BATTERY",
> +    [KEY_BLUETOOTH] = "KEY_BLUETOOTH",
> +    [KEY_WLAN] = "KEY_WLAN",
> +    [KEY_UWB] = "KEY_UWB",
> +    [KEY_UNKNOWN] = "KEY_UNKNOWN",
> +    [KEY_VIDEO_NEXT] = "KEY_VIDEO_NEXT",
> +    [KEY_VIDEO_PREV] = "KEY_VIDEO_PREV",
> +    [KEY_BRIGHTNESS_CYCLE] = "KEY_BRIGHTNESS_CYCLE",
> +    [KEY_BRIGHTNESS_ZERO] = "KEY_BRIGHTNESS_ZERO",
> +    [KEY_DISPLAY_OFF] = "KEY_DISPLAY_OFF",
> +    [KEY_WIMAX] = "KEY_WIMAX",
> +    [KEY_OK] = "KEY_OK",
> +    [KEY_SELECT] = "KEY_SELECT",
> +    [KEY_GOTO] = "KEY_GOTO",
> +    [KEY_CLEAR] = "KEY_CLEAR",
> +    [KEY_POWER2] = "KEY_POWER2",
> +    [KEY_OPTION] = "KEY_OPTION",
> +    [KEY_INFO] = "KEY_INFO",
> +    [KEY_TIME] = "KEY_TIME",
> +    [KEY_VENDOR] = "KEY_VENDOR",
> +    [KEY_ARCHIVE] = "KEY_ARCHIVE",
> +    [KEY_PROGRAM] = "KEY_PROGRAM",
> +    [KEY_CHANNEL] = "KEY_CHANNEL",
> +    [KEY_FAVORITES] = "KEY_FAVORITES",
> +    [KEY_EPG] = "KEY_EPG",
> +    [KEY_PVR] = "KEY_PVR",
> +    [KEY_MHP] = "KEY_MHP",
> +    [KEY_LANGUAGE] = "KEY_LANGUAGE",
> +    [KEY_TITLE] = "KEY_TITLE",
> +    [KEY_SUBTITLE] = "KEY_SUBTITLE",
> +    [KEY_ANGLE] = "KEY_ANGLE",
> +    [KEY_ZOOM] = "KEY_ZOOM",
> +    [KEY_MODE] = "KEY_MODE",
> +    [KEY_KEYBOARD] = "KEY_KEYBOARD",
> +    [KEY_SCREEN] = "KEY_SCREEN",
> +    [KEY_PC] = "KEY_PC",
> +    [KEY_TV] = "KEY_TV",
> +    [KEY_TV2] = "KEY_TV2",
> +    [KEY_VCR] = "KEY_VCR",
> +    [KEY_VCR2] = "KEY_VCR2",
> +    [KEY_SAT] = "KEY_SAT",
> +    [KEY_SAT2] = "KEY_SAT2",
> +    [KEY_CD] = "KEY_CD",
> +    [KEY_TAPE] = "KEY_TAPE",
> +    [KEY_RADIO] = "KEY_RADIO",
> +    [KEY_TUNER] = "KEY_TUNER",
> +    [KEY_PLAYER] = "KEY_PLAYER",
> +    [KEY_TEXT] = "KEY_TEXT",
> +    [KEY_DVD] = "KEY_DVD",
> +    [KEY_AUX] = "KEY_AUX",
> +    [KEY_MP3] = "KEY_MP3",
> +    [KEY_AUDIO] = "KEY_AUDIO",
> +    [KEY_VIDEO] = "KEY_VIDEO",
> +    [KEY_DIRECTORY] = "KEY_DIRECTORY",
> +    [KEY_LIST] = "KEY_LIST",
> +    [KEY_MEMO] = "KEY_MEMO",
> +    [KEY_CALENDAR] = "KEY_CALENDAR",
> +    [KEY_RED] = "KEY_RED",
> +    [KEY_GREEN] = "KEY_GREEN",
> +    [KEY_YELLOW] = "KEY_YELLOW",
> +    [KEY_BLUE] = "KEY_BLUE",
> +    [KEY_CHANNELUP] = "KEY_CHANNELUP",
> +    [KEY_CHANNELDOWN] = "KEY_CHANNELDOWN",
> +    [KEY_FIRST] = "KEY_FIRST",
> +    [KEY_LAST] = "KEY_LAST",
> +    [KEY_AB] = "KEY_AB",
> +    [KEY_NEXT] = "KEY_NEXT",
> +    [KEY_RESTART] = "KEY_RESTART",
> +    [KEY_SLOW] = "KEY_SLOW",
> +    [KEY_SHUFFLE] = "KEY_SHUFFLE",
> +    [KEY_BREAK] = "KEY_BREAK",
> +    [KEY_PREVIOUS] = "KEY_PREVIOUS",
> +    [KEY_DIGITS] = "KEY_DIGITS",
> +    [KEY_TEEN] = "KEY_TEEN",
> +    [KEY_TWEN] = "KEY_TWEN",
> +    [KEY_VIDEOPHONE] = "KEY_VIDEOPHONE",
> +    [KEY_GAMES] = "KEY_GAMES",
> +    [KEY_ZOOMIN] = "KEY_ZOOMIN",
> +    [KEY_ZOOMOUT] = "KEY_ZOOMOUT",
> +    [KEY_ZOOMRESET] = "KEY_ZOOMRESET",
> +    [KEY_WORDPROCESSOR] = "KEY_WORDPROCESSOR",
> +    [KEY_EDITOR] = "KEY_EDITOR",
> +    [KEY_SPREADSHEET] = "KEY_SPREADSHEET",
> +    [KEY_GRAPHICSEDITOR] = "KEY_GRAPHICSEDITOR",
> +    [KEY_PRESENTATION] = "KEY_PRESENTATION",
> +    [KEY_DATABASE] = "KEY_DATABASE",
> +    [KEY_NEWS] = "KEY_NEWS",
> +    [KEY_VOICEMAIL] = "KEY_VOICEMAIL",
> +    [KEY_ADDRESSBOOK] = "KEY_ADDRESSBOOK",
> +    [KEY_MESSENGER] = "KEY_MESSENGER",
> +    [KEY_DISPLAYTOGGLE] = "KEY_DISPLAYTOGGLE",
> +    [KEY_SPELLCHECK] = "KEY_SPELLCHECK",
> +    [KEY_LOGOFF] = "KEY_LOGOFF",
> +    [KEY_DOLLAR] = "KEY_DOLLAR",
> +    [KEY_EURO] = "KEY_EURO",
> +    [KEY_FRAMEBACK] = "KEY_FRAMEBACK",
> +    [KEY_FRAMEFORWARD] = "KEY_FRAMEFORWARD",
> +    [KEY_CONTEXT_MENU] = "KEY_CONTEXT_MENU",
> +    [KEY_MEDIA_REPEAT] = "KEY_MEDIA_REPEAT",
> +    [KEY_10CHANNELSUP] = "KEY_10CHANNELSUP",
> +    [KEY_10CHANNELSDOWN] = "KEY_10CHANNELSDOWN",
> +    [KEY_DEL_EOL] = "KEY_DEL_EOL",
> +    [KEY_DEL_EOS] = "KEY_DEL_EOS",
> +    [KEY_INS_LINE] = "KEY_INS_LINE",
> +    [KEY_DEL_LINE] = "KEY_DEL_LINE",
> +    [KEY_FN] = "KEY_FN",
> +    [KEY_FN_ESC] = "KEY_FN_ESC",
> +    [KEY_FN_F1] = "KEY_FN_F1",
> +    [KEY_FN_F2] = "KEY_FN_F2",
> +    [KEY_FN_F3] = "KEY_FN_F3",
> +    [KEY_FN_F4] = "KEY_FN_F4",
> +    [KEY_FN_F5] = "KEY_FN_F5",
> +    [KEY_FN_F6] = "KEY_FN_F6",
> +    [KEY_FN_F7] = "KEY_FN_F7",
> +    [KEY_FN_F8] = "KEY_FN_F8",
> +    [KEY_FN_F9] = "KEY_FN_F9",
> +    [KEY_FN_F10] = "KEY_FN_F10",
> +    [KEY_FN_F11] = "KEY_FN_F11",
> +    [KEY_FN_F12] = "KEY_FN_F12",
> +    [KEY_FN_1] = "KEY_FN_1",
> +    [KEY_FN_2] = "KEY_FN_2",
> +    [KEY_FN_D] = "KEY_FN_D",
> +    [KEY_FN_E] = "KEY_FN_E",
> +    [KEY_FN_F] = "KEY_FN_F",
> +    [KEY_FN_S] = "KEY_FN_S",
> +    [KEY_FN_B] = "KEY_FN_B",
> +    [KEY_BRL_DOT1] = "KEY_BRL_DOT1",
> +    [KEY_BRL_DOT2] = "KEY_BRL_DOT2",
> +    [KEY_BRL_DOT3] = "KEY_BRL_DOT3",
> +    [KEY_BRL_DOT4] = "KEY_BRL_DOT4",
> +    [KEY_BRL_DOT5] = "KEY_BRL_DOT5",
> +    [KEY_BRL_DOT6] = "KEY_BRL_DOT6",
> +    [KEY_BRL_DOT7] = "KEY_BRL_DOT7",
> +    [KEY_BRL_DOT8] = "KEY_BRL_DOT8",
> +    [KEY_BRL_DOT9] = "KEY_BRL_DOT9",
> +    [KEY_BRL_DOT10] = "KEY_BRL_DOT10",
> +    [KEY_NUMERIC_0] = "KEY_NUMERIC_0",
> +    [KEY_NUMERIC_1] = "KEY_NUMERIC_1",
> +    [KEY_NUMERIC_2] = "KEY_NUMERIC_2",
> +    [KEY_NUMERIC_3] = "KEY_NUMERIC_3",
> +    [KEY_NUMERIC_4] = "KEY_NUMERIC_4",
> +    [KEY_NUMERIC_5] = "KEY_NUMERIC_5",
> +    [KEY_NUMERIC_6] = "KEY_NUMERIC_6",
> +    [KEY_NUMERIC_7] = "KEY_NUMERIC_7",
> +    [KEY_NUMERIC_8] = "KEY_NUMERIC_8",
> +    [KEY_NUMERIC_9] = "KEY_NUMERIC_9",
> +    [KEY_NUMERIC_STAR] = "KEY_NUMERIC_STAR",
> +    [KEY_NUMERIC_POUND] = "KEY_NUMERIC_POUND",
> +    [KEY_RFKILL] = "KEY_RFKILL",
> +    [KEY_MIN_INTERESTING] = "KEY_MIN_INTERESTING",
> +    [KEY_MAX] = "KEY_MAX",
> +    [KEY_CNT] = "KEY_CNT",
> diff --git a/vl.c b/vl.c
> index 0a8f056..bf337f9 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2240,6 +2240,13 @@ static DisplayType select_display(const char *p)
>          fprintf(stderr, "GTK support is disabled\n");
>          exit(1);
>  #endif
> +    } else if (strstart(p, "fbdev", &opts)) {
> +#ifdef CONFIG_FBDEV
> +        display = DT_FBDEV;
> +#else
> +        fprintf(stderr, "fbdev support is disabled\n");
> +        exit(1);
> +#endif
>      } else if (strstart(p, "none", &opts)) {
>          display = DT_NONE;
>      } else {
> @@ -4300,6 +4307,20 @@ int main(int argc, char **argv, char **envp)
>          curses_display_init(ds, full_screen);
>          break;
>  #endif
> +#if defined(CONFIG_FBDEV)
> +    case DT_FBDEV:
> +    {
> +        Error *errp = NULL;
> +        if (fbdev_display_init(NULL, false, &errp) != 0) {
> +            if (error_is_set(&errp)) {
> +                fprintf(stderr, "%s\n", error_get_pretty(errp));
> +                error_free(errp);
> +            }
> +            exit(1);
> +        }
> +        break;
> +    }
> +#endif
>  #if defined(CONFIG_SDL)
>      case DT_SDL:
>          sdl_display_init(ds, full_screen, no_frame);

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-26 11:38 ` [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query Gerd Hoffmann
@ 2013-06-26 15:56   ` Luiz Capitulino
  2013-06-27 10:05     ` Gerd Hoffmann
  2013-06-27 21:55     ` Eric Blake
  0 siblings, 2 replies; 11+ messages in thread
From: Luiz Capitulino @ 2013-06-26 15:56 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Anthony Liguori, qemu-devel, Markus Armbruster

On Wed, 26 Jun 2013 13:38:04 +0200
Gerd Hoffmann <kraxel@redhat.com> wrote:

> This patch adds a fbdev monitor command to enable/disable
> the fbdev display at runtime to both qmp and hmp.
> 
> qmp: framebuffer-display enable=on|off scale=on|off device=/dev/fb<n>
> hmp: framebuffer-display on|off
> 
> There is also a query-framebuffer command for qmp.
> 
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  hmp-commands.hx      |   14 ++++++++++++++
>  hmp.c                |    9 +++++++++
>  hmp.h                |    1 +
>  include/ui/console.h |    1 +
>  qapi-schema.json     |   43 +++++++++++++++++++++++++++++++++++++++++++
>  qmp-commands.hx      |   12 ++++++++++++
>  qmp.c                |   31 +++++++++++++++++++++++++++++++
>  ui/fbdev.c           |   20 ++++++++++++++++++++
>  8 files changed, 131 insertions(+)
> 
> diff --git a/hmp-commands.hx b/hmp-commands.hx
> index 915b0d1..283106d 100644
> --- a/hmp-commands.hx
> +++ b/hmp-commands.hx
> @@ -1563,7 +1563,21 @@ STEXI
>  @findex qemu-io
>  
>  Executes a qemu-io command on the given block device.
> +ETEXI
> +
> +    {
> +        .name       = "framebuffer-display",
> +        .args_type  = "enable:b",
> +        .params     = "on|off",
> +        .help       = "enable/disable linux console framebuffer display",
> +        .mhandler.cmd = hmp_framebuffer_display,
> +    },
> +
> +STEXI
> +@item framebuffer-display on | off
> +@findex framebuffer-display
>  
> +enable/disable linux console framebuffer display.
>  ETEXI
>  
>      {
> diff --git a/hmp.c b/hmp.c
> index 494a9aa..55f195f 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -1464,3 +1464,12 @@ void hmp_qemu_io(Monitor *mon, const QDict *qdict)
>  
>      hmp_handle_error(mon, &err);
>  }
> +
> +void hmp_framebuffer_display(Monitor *mon, const QDict *qdict)
> +{
> +    int enable = qdict_get_bool(qdict, "enable");
> +    Error *errp = NULL;
> +
> +    qmp_framebuffer_display(enable, false, false, false, NULL, &errp);
> +    hmp_handle_error(mon, &errp);
> +}
> diff --git a/hmp.h b/hmp.h
> index 56d2e92..c3a48e4 100644
> --- a/hmp.h
> +++ b/hmp.h
> @@ -86,5 +86,6 @@ void hmp_nbd_server_stop(Monitor *mon, const QDict *qdict);
>  void hmp_chardev_add(Monitor *mon, const QDict *qdict);
>  void hmp_chardev_remove(Monitor *mon, const QDict *qdict);
>  void hmp_qemu_io(Monitor *mon, const QDict *qdict);
> +void hmp_framebuffer_display(Monitor *mon, const QDict *qdict);
>  
>  #endif
> diff --git a/include/ui/console.h b/include/ui/console.h
> index 71b538a..5a9207d 100644
> --- a/include/ui/console.h
> +++ b/include/ui/console.h
> @@ -311,6 +311,7 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame);
>  /* fbdev.c */
>  int fbdev_display_init(const char *device, bool scale, Error **err);
>  void fbdev_display_uninit(void);
> +FramebufferInfo *framebuffer_info(void);
>  
>  /* cocoa.m */
>  void cocoa_display_init(DisplayState *ds, int full_screen);
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 6cc07c2..715dc1f 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -3608,3 +3608,46 @@
>              '*cpuid-input-ecx': 'int',
>              'cpuid-register': 'X86CPURegister32',
>              'features': 'int' } }
> +
> +##
> +# @framebuffer-display:

Let me bike-shed: we're trying to make command's names verbs. So, we
could call this framebuffer-display-set or maybe have two commands,
framebuffer-display-enable and framebuffer-display-disable. I prefer
the latter.

> +#
> +# Enable/disable linux console framebuffer display.
> +#
> +# @enable: whenever the framebuffer display should be enabled or disabled.
> +#
> +# @scale: #optional enables display scaling, default: off
> +#
> +# @device: #optional specifies framebuffer device, default: /dev/fb0

Actually, it will try to get the device name from an env variable first,
which sounds too automatic for a building block API like QMP. You can do
it for HMP, but QMP I guess it would be more appropriate to make the
device name mandatory.

> +#
> +# Returns: Nothing.
> +#
> +# Since: 1.6
> +#
> +##
> +{ 'command': 'framebuffer-display', 'data': {'enable'  : 'bool',
> +                                             '*scale'  : 'bool',
> +                                             '*device' : 'str' } }
> +
> +##
> +# @FramebufferInfo:
> +#

Missing docs.

> +# Since 1.6
> +##
> +{ 'type': 'FramebufferInfo',
> +  'data': { 'enabled': 'bool',
> +            '*scale' : 'bool',
> +            '*device': 'str',

Why is device optional?

> +            '*vtno'  : 'int' } }
> +
> +##
> +# @query-framebuffer:
> +#
> +# Query linux console framebuffer state.
> +#
> +# Returns: FramebufferInfo.
> +#
> +# Since: 1.6
> +#
> +##
> +{ 'command': 'query-framebuffer', 'returns': 'FramebufferInfo' }
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index 8cea5e5..e0661f0 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -2997,3 +2997,15 @@ Example:
>  <- { "return": {} }
>  
>  EQMP
> +
> +    {
> +        .name       = "framebuffer-display",
> +        .args_type  = "enable:b,scale:b?,device:s?",
> +        .mhandler.cmd_new = qmp_marshal_input_framebuffer_display,
> +    },
> +
> +    {
> +        .name       = "query-framebuffer",
> +        .args_type  = "",
> +        .mhandler.cmd_new = qmp_marshal_input_query_framebuffer,
> +    },
> diff --git a/qmp.c b/qmp.c
> index 4c149b3..b6d826c 100644
> --- a/qmp.c
> +++ b/qmp.c
> @@ -404,6 +404,37 @@ void qmp_change(const char *device, const char *target,
>      }
>  }
>  
> +void qmp_framebuffer_display(bool enable,
> +                             bool has_scale, bool scale,
> +                             bool has_device, const char *device,
> +                             Error **errp)
> +{
> +#if defined(CONFIG_FBDEV)
> +    if (enable) {
> +        if (fbdev_display_init(has_device ? device : NULL,
> +                               has_scale  ? scale  : false,
> +                               errp) != 0) {
> +            if (!error_is_set(errp)) {
> +                error_setg(errp, "fbdev initialization failed");

You should use error_propagate() in order to propagate the error
information filled by fbdev_display_init(). Also, I'd move
the generic error_setg() above to fbdev_display_init() and make it
return void.

> +            }
> +        }
> +    } else {
> +        fbdev_display_uninit();
> +    }
> +#else
> +    error_setg(errp, "fbdev support disabled at compile time");
> +#endif
> +}
> +
> +FramebufferInfo *qmp_query_framebuffer(Error **errp)
> +{
> +#if defined(CONFIG_FBDEV)
> +    return framebuffer_info();
> +#else
> +    error_setg(errp, "fbdev support disabled at compile time");
> +#endif
> +}
> +
>  static void qom_list_types_tramp(ObjectClass *klass, void *data)
>  {
>      ObjectTypeInfoList *e, **pret = data;
> diff --git a/ui/fbdev.c b/ui/fbdev.c
> index 91e11e5..326bba1 100644
> --- a/ui/fbdev.c
> +++ b/ui/fbdev.c
> @@ -1162,3 +1162,23 @@ void fbdev_display_uninit(void)
>      g_free(s);
>      fb = NULL;
>  }
> +
> +FramebufferInfo *framebuffer_info(void)
> +{
> +    FramebufferInfo *info = g_new0(FramebufferInfo, 1);
> +    FBDevState *s = fb;
> +
> +    if (s == NULL) {
> +        info->enabled = false;
> +        return info;
> +    }
> +
> +    info->enabled    = true;
> +    info->has_device = true;
> +    info->device     = g_strdup(s->device);
> +    info->has_scale  = true;
> +    info->scale      = s->use_scale;
> +    info->has_vtno   = true;
> +    info->vtno       = s->vtno;
> +    return info;
> +}

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver.
  2013-06-26 15:48 ` [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Luiz Capitulino
@ 2013-06-27  9:58   ` Gerd Hoffmann
  2013-06-27 13:23     ` Luiz Capitulino
  0 siblings, 1 reply; 11+ messages in thread
From: Gerd Hoffmann @ 2013-06-27  9:58 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: Anthony Liguori, qemu-devel

  Hi,

>> +    /* open framebuffer */
>> +    if (device == NULL) {
>> +        device = getenv("FRAMEBUFFER");
>> +    }
>> +    if (device == NULL) {
>> +        device = "/dev/fb0";
>> +    }
> 
> Maybe this is a matter of taste, but I think that having this logic at
> this layer makes the API harder to use. What about moving this to the
> call in vl.c and making the device name required in QMP?

I'll just drop the getenv.  That will make "/dev/fb0" the default value
no matter what.  Ok?

>> +    s->fb = open(device, O_RDWR);
>> +    if (s->fb == -1) {
>> +        error_setg(err, "open %s: %s\n", device, strerror(errno));
> 
> We have error_setg_errno() :)

Also error_setg_file_open() ;)

cheers,
  Gerd

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-26 15:56   ` Luiz Capitulino
@ 2013-06-27 10:05     ` Gerd Hoffmann
  2013-06-27 13:29       ` Luiz Capitulino
  2013-06-27 21:55     ` Eric Blake
  1 sibling, 1 reply; 11+ messages in thread
From: Gerd Hoffmann @ 2013-06-27 10:05 UTC (permalink / raw)
  To: Luiz Capitulino; +Cc: Anthony Liguori, qemu-devel, Markus Armbruster

  Hi,

>> --- a/qapi-schema.json
>> +++ b/qapi-schema.json
>> @@ -3608,3 +3608,46 @@
>>              '*cpuid-input-ecx': 'int',
>>              'cpuid-register': 'X86CPURegister32',
>>              'features': 'int' } }
>> +
>> +##
>> +# @framebuffer-display:
> 
> Let me bike-shed: we're trying to make command's names verbs. So, we
> could call this framebuffer-display-set or maybe have two commands,
> framebuffer-display-enable and framebuffer-display-disable. I prefer
> the latter.

Can do that.

>> +#
>> +# Enable/disable linux console framebuffer display.
>> +#
>> +# @enable: whenever the framebuffer display should be enabled or disabled.
>> +#
>> +# @scale: #optional enables display scaling, default: off
>> +#
>> +# @device: #optional specifies framebuffer device, default: /dev/fb0
> 
> Actually, it will try to get the device name from an env variable first,
> which sounds too automatic for a building block API like QMP.

The env variable is icky indeed.  But a fixed default (which will be the
one you need in 99% of the cases) is fine IMO.

>> +# Since 1.6
>> +##
>> +{ 'type': 'FramebufferInfo',
>> +  'data': { 'enabled': 'bool',
>> +            '*scale' : 'bool',
>> +            '*device': 'str',
> 
> Why is device optional?

When the framebuffer is'nt active you'll get just
{ "enabled" : "false" }, thats why the other ones are optional.  They
all are filled in case the framebuffer is active.

>> +void qmp_framebuffer_display(bool enable,
>> +                             bool has_scale, bool scale,
>> +                             bool has_device, const char *device,
>> +                             Error **errp)
>> +{
>> +#if defined(CONFIG_FBDEV)
>> +    if (enable) {
>> +        if (fbdev_display_init(has_device ? device : NULL,
>> +                               has_scale  ? scale  : false,
>> +                               errp) != 0) {
>> +            if (!error_is_set(errp)) {
>> +                error_setg(errp, "fbdev initialization failed");
> 
> You should use error_propagate() in order to propagate the error
> information filled by fbdev_display_init().

Why?  What is wrong with simply passing down errp?

> Also, I'd move
> the generic error_setg() above to fbdev_display_init() and make it
> return void.

Makes sense indeed.

cheers,
  Gerd

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver.
  2013-06-27  9:58   ` Gerd Hoffmann
@ 2013-06-27 13:23     ` Luiz Capitulino
  0 siblings, 0 replies; 11+ messages in thread
From: Luiz Capitulino @ 2013-06-27 13:23 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Anthony Liguori, qemu-devel

On Thu, 27 Jun 2013 11:58:57 +0200
Gerd Hoffmann <kraxel@redhat.com> wrote:

>   Hi,
> 
> >> +    /* open framebuffer */
> >> +    if (device == NULL) {
> >> +        device = getenv("FRAMEBUFFER");
> >> +    }
> >> +    if (device == NULL) {
> >> +        device = "/dev/fb0";
> >> +    }
> > 
> > Maybe this is a matter of taste, but I think that having this logic at
> > this layer makes the API harder to use. What about moving this to the
> > call in vl.c and making the device name required in QMP?
> 
> I'll just drop the getenv.  That will make "/dev/fb0" the default value
> no matter what.  Ok?

I still prefer having no defaults in QMP (it's ok elsewhere, like HMP),
but I won't nack the patch of that.

> >> +    s->fb = open(device, O_RDWR);
> >> +    if (s->fb == -1) {
> >> +        error_setg(err, "open %s: %s\n", device, strerror(errno));
> > 
> > We have error_setg_errno() :)
> 
> Also error_setg_file_open() ;)

Oh, I really forgot about it...

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-27 10:05     ` Gerd Hoffmann
@ 2013-06-27 13:29       ` Luiz Capitulino
  2013-06-27 22:03         ` Eric Blake
  0 siblings, 1 reply; 11+ messages in thread
From: Luiz Capitulino @ 2013-06-27 13:29 UTC (permalink / raw)
  To: Gerd Hoffmann; +Cc: Anthony Liguori, qemu-devel, Markus Armbruster

On Thu, 27 Jun 2013 12:05:33 +0200
Gerd Hoffmann <kraxel@redhat.com> wrote:

>   Hi,
> 
> >> --- a/qapi-schema.json
> >> +++ b/qapi-schema.json
> >> @@ -3608,3 +3608,46 @@
> >>              '*cpuid-input-ecx': 'int',
> >>              'cpuid-register': 'X86CPURegister32',
> >>              'features': 'int' } }
> >> +
> >> +##
> >> +# @framebuffer-display:
> > 
> > Let me bike-shed: we're trying to make command's names verbs. So, we
> > could call this framebuffer-display-set or maybe have two commands,
> > framebuffer-display-enable and framebuffer-display-disable. I prefer
> > the latter.
> 
> Can do that.
> 
> >> +#
> >> +# Enable/disable linux console framebuffer display.
> >> +#
> >> +# @enable: whenever the framebuffer display should be enabled or disabled.
> >> +#
> >> +# @scale: #optional enables display scaling, default: off
> >> +#
> >> +# @device: #optional specifies framebuffer device, default: /dev/fb0
> > 
> > Actually, it will try to get the device name from an env variable first,
> > which sounds too automatic for a building block API like QMP.
> 
> The env variable is icky indeed.  But a fixed default (which will be the
> one you need in 99% of the cases) is fine IMO.

I prefer having no defaults, but I'm not strong about this.

> 
> >> +# Since 1.6
> >> +##
> >> +{ 'type': 'FramebufferInfo',
> >> +  'data': { 'enabled': 'bool',
> >> +            '*scale' : 'bool',
> >> +            '*device': 'str',
> > 
> > Why is device optional?
> 
> When the framebuffer is'nt active you'll get just
> { "enabled" : "false" }, thats why the other ones are optional.  They
> all are filled in case the framebuffer is active.
> 
> >> +void qmp_framebuffer_display(bool enable,
> >> +                             bool has_scale, bool scale,
> >> +                             bool has_device, const char *device,
> >> +                             Error **errp)
> >> +{
> >> +#if defined(CONFIG_FBDEV)
> >> +    if (enable) {
> >> +        if (fbdev_display_init(has_device ? device : NULL,
> >> +                               has_scale  ? scale  : false,
> >> +                               errp) != 0) {
> >> +            if (!error_is_set(errp)) {
> >> +                error_setg(errp, "fbdev initialization failed");
> > 
> > You should use error_propagate() in order to propagate the error
> > information filled by fbdev_display_init().
> 
> Why?  What is wrong with simply passing down errp?

Because errp can be NULL, then you'll be unable to detect errors if
fbdev_display_init() is changed to return void. But you're right you
won't need to propagate errors if you drop the if () test, as you
won't be checking for errors anymore.

> 
> > Also, I'd move
> > the generic error_setg() above to fbdev_display_init() and make it
> > return void.
> 
> Makes sense indeed.
> 
> cheers,
>   Gerd
> 

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-26 15:56   ` Luiz Capitulino
  2013-06-27 10:05     ` Gerd Hoffmann
@ 2013-06-27 21:55     ` Eric Blake
  1 sibling, 0 replies; 11+ messages in thread
From: Eric Blake @ 2013-06-27 21:55 UTC (permalink / raw)
  To: Luiz Capitulino
  Cc: Markus Armbruster, Anthony Liguori, Gerd Hoffmann, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1247 bytes --]

On 06/26/2013 09:56 AM, Luiz Capitulino wrote:
> On Wed, 26 Jun 2013 13:38:04 +0200
> Gerd Hoffmann <kraxel@redhat.com> wrote:
> 
>> This patch adds a fbdev monitor command to enable/disable
>> the fbdev display at runtime to both qmp and hmp.
>>

>> +##
>> +# @framebuffer-display:
> 
> Let me bike-shed: we're trying to make command's names verbs. So, we
> could call this framebuffer-display-set or maybe have two commands,
> framebuffer-display-enable and framebuffer-display-disable. I prefer
> the latter.

Having 2 commands also avoids the semantic quandary of what to do for
"enable":false,"device":"/path/to/non-default" - the device parameter
only makes sense when enabling the framebuffer display.

>> +
>> +##
>> +# @FramebufferInfo:
>> +#
> 
> Missing docs.
> 
>> +# Since 1.6
>> +##
>> +{ 'type': 'FramebufferInfo',
>> +  'data': { 'enabled': 'bool',
>> +            '*scale' : 'bool',
>> +            '*device': 'str',
> 
> Why is device optional?
> 
>> +            '*vtno'  : 'int' } }

Also, 'vtno' isn't a word; is it worth spelling it out a bit more by
naming it 'vt-number'?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 621 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-27 13:29       ` Luiz Capitulino
@ 2013-06-27 22:03         ` Eric Blake
  2013-06-28 14:26           ` Luiz Capitulino
  0 siblings, 1 reply; 11+ messages in thread
From: Eric Blake @ 2013-06-27 22:03 UTC (permalink / raw)
  To: Luiz Capitulino
  Cc: Markus Armbruster, Anthony Liguori, Gerd Hoffmann, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 2218 bytes --]

On 06/27/2013 07:29 AM, Luiz Capitulino wrote:
> On Thu, 27 Jun 2013 12:05:33 +0200
> Gerd Hoffmann <kraxel@redhat.com> wrote:
> 

>>>> +# @device: #optional specifies framebuffer device, default: /dev/fb0
>>>
>>> Actually, it will try to get the device name from an env variable first,
>>> which sounds too automatic for a building block API like QMP.
>>
>> The env variable is icky indeed.  But a fixed default (which will be the
>> one you need in 99% of the cases) is fine IMO.
> 
> I prefer having no defaults, but I'm not strong about this.

I also think that letting HMP leave the parameter optional, with
first-level fallback to the environment variable and secondary fallback
to the hardcoded /dev/fv0, while making the parameter mandatory in QMP,
makes more sense.

> 
>>
>>>> +# Since 1.6
>>>> +##
>>>> +{ 'type': 'FramebufferInfo',
>>>> +  'data': { 'enabled': 'bool',
>>>> +            '*scale' : 'bool',
>>>> +            '*device': 'str',
>>>
>>> Why is device optional?
>>
>> When the framebuffer is'nt active you'll get just
>> { "enabled" : "false" }, thats why the other ones are optional.  They
>> all are filled in case the framebuffer is active.

Would it be any simpler to just have the query command fail when the
framebuffer is disabled, leave out the 'enabled' member from
FramebufferInfo, and have all the other members mandatory?

Also, would it ever make sense for a future enhancement to allow having
more than one framebuffer in use by a single domain?  Even if your
initial implementation supports at most one /dev/fb* device per guest,
it might be worth having the query-framebuffer command return an array
of all enabled framebuffers (a 0-length array when none is enabled),
rather than hard-coding it to a single FramebufferInfo return.
Likewise, make sure that framebuffer-display can deal with extension if
it makes sense (the suggestion to split into -enable and -disable
commands might be better stated as splitting into
framebuffer-display-add and framebuffer-display-delete, more like
hotplugging of other devices).

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 621 bytes --]

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query
  2013-06-27 22:03         ` Eric Blake
@ 2013-06-28 14:26           ` Luiz Capitulino
  0 siblings, 0 replies; 11+ messages in thread
From: Luiz Capitulino @ 2013-06-28 14:26 UTC (permalink / raw)
  To: Eric Blake; +Cc: Markus Armbruster, Anthony Liguori, Gerd Hoffmann, qemu-devel

On Thu, 27 Jun 2013 16:03:48 -0600
Eric Blake <eblake@redhat.com> wrote:

> On 06/27/2013 07:29 AM, Luiz Capitulino wrote:
> > On Thu, 27 Jun 2013 12:05:33 +0200
> > Gerd Hoffmann <kraxel@redhat.com> wrote:
> > 
> 
> >>>> +# @device: #optional specifies framebuffer device, default: /dev/fb0
> >>>
> >>> Actually, it will try to get the device name from an env variable first,
> >>> which sounds too automatic for a building block API like QMP.
> >>
> >> The env variable is icky indeed.  But a fixed default (which will be the
> >> one you need in 99% of the cases) is fine IMO.
> > 
> > I prefer having no defaults, but I'm not strong about this.
> 
> I also think that letting HMP leave the parameter optional, with
> first-level fallback to the environment variable and secondary fallback
> to the hardcoded /dev/fv0, while making the parameter mandatory in QMP,
> makes more sense.
> 
> > 
> >>
> >>>> +# Since 1.6
> >>>> +##
> >>>> +{ 'type': 'FramebufferInfo',
> >>>> +  'data': { 'enabled': 'bool',
> >>>> +            '*scale' : 'bool',
> >>>> +            '*device': 'str',
> >>>
> >>> Why is device optional?
> >>
> >> When the framebuffer is'nt active you'll get just
> >> { "enabled" : "false" }, thats why the other ones are optional.  They
> >> all are filled in case the framebuffer is active.
> 
> Would it be any simpler to just have the query command fail when the
> framebuffer is disabled, leave out the 'enabled' member from
> FramebufferInfo, and have all the other members mandatory?

I don't think this is correct. First because mngt would have to parse
the error message to learn if the query command really failed or
the framebuffer device is just disabled. Second because the command does
succeed on returning the framebuffer status even when it's disabled.

> Also, would it ever make sense for a future enhancement to allow having
> more than one framebuffer in use by a single domain?  Even if your
> initial implementation supports at most one /dev/fb* device per guest,
> it might be worth having the query-framebuffer command return an array
> of all enabled framebuffers (a 0-length array when none is enabled),

This is also an argument in favor of not having a default device name
in QMP.

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2013-06-28 14:26 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-06-26 11:38 [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Gerd Hoffmann
2013-06-26 11:38 ` [Qemu-devel] [PATCH 2/2] fbdev: add monitor commands to enable/disable/query Gerd Hoffmann
2013-06-26 15:56   ` Luiz Capitulino
2013-06-27 10:05     ` Gerd Hoffmann
2013-06-27 13:29       ` Luiz Capitulino
2013-06-27 22:03         ` Eric Blake
2013-06-28 14:26           ` Luiz Capitulino
2013-06-27 21:55     ` Eric Blake
2013-06-26 15:48 ` [Qemu-devel] [PATCH 1/2] fbdev: add linux framebuffer display driver Luiz Capitulino
2013-06-27  9:58   ` Gerd Hoffmann
2013-06-27 13:23     ` Luiz Capitulino

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.