Patchwork [2/6] ui: add basic GTK gui

login
register
mail settings
Submitter Anthony Liguori
Date Feb. 19, 2012, 11:45 p.m.
Message ID <1329695104-15174-3-git-send-email-aliguori@us.ibm.com>
Download mbox | patch
Permalink /patch/142084/
State New
Headers show

Comments

Anthony Liguori - Feb. 19, 2012, 11:45 p.m.
This is minimalistic and just contains the basic widget infrastructure.  The GUI
consists of a menu and a GtkNotebook.  To start with, the notebook has its tabs
hidden which provides a UI that looks very similar to SDL with the exception of
the menu bar.

The menu bar allows a user to toggle the visibility of the tabs.  Cairo is used
for rendering.

I used gtk-vnc as a reference.  gtk-vnc solves the same basic problems as QEMU
since it was originally written as a remote display for QEMU.  So for the most
part, the approach to rendering and keyboard handling should be pretty solid for
GTK.

Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
---
 Makefile      |    2 +
 Makefile.objs |    1 +
 configure     |   25 +++-
 console.h     |    4 +
 sysemu.h      |    1 +
 ui/gtk.c      |  551 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 583 insertions(+), 1 deletions(-)
 create mode 100644 ui/gtk.c
Stefan Weil - Feb. 20, 2012, 8:45 p.m.
Am 20.02.2012 00:45, schrieb Anthony Liguori:
> This is minimalistic and just contains the basic widget 
> infrastructure. The GUI
> consists of a menu and a GtkNotebook. To start with, the notebook has 
> its tabs
> hidden which provides a UI that looks very similar to SDL with the 
> exception of
> the menu bar.
>
> The menu bar allows a user to toggle the visibility of the tabs. Cairo 
> is used
> for rendering.
>
> I used gtk-vnc as a reference. gtk-vnc solves the same basic problems 
> as QEMU
> since it was originally written as a remote display for QEMU. So for 
> the most
> part, the approach to rendering and keyboard handling should be pretty 
> solid for
> GTK.
>
> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
> ---
> Makefile | 2 +
> Makefile.objs | 1 +
> configure | 25 +++-
> console.h | 4 +
> sysemu.h | 1 +
> ui/gtk.c | 551 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 6 files changed, 583 insertions(+), 1 deletions(-)
> create mode 100644 ui/gtk.c
>

This patch compiles, but does not provide the minimalistic interface
because the hooks in vl.c are missing. Parts of patch 6 might be
moved to patch 2.

Patch 2 could also be improved by removing the dependency on VTE.
For some platforms (w32), providing VTE is really a big challenge.
I did not find a precompiled version and still did not succeed
in compiling it because of a large and still incomplete dependency
chain...

What about offering compilation without VTE also in the "final"
version of the GTK gui (--enable-vte, --disable-vte)?

These includes in ui/gtk.c were not needed:

+#include <vte/vte.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <pty.h>

If the version using VTE needs them, qemu_socket.h should be
used (needed for w32 which does neither have sys/socket.h
nor pty.h). Maybe most of these includes are not needed at all
(relict from VNC?).

With a patched vl.c and ui/gtk.c, the new GUI starts on w32
but does not work stable.
After some minutes, the GUI freezes (no more updates, menu no
longer working).

The configuration support still does not work as expected:

configure should support --enable-gtk, --disable-gtk with the
usual semantic (no GTK UI if --disable-gtk was given, fail if
--enable-gtk was given and GTK is not found, provide help message).

The GTK user interface is a good starting point for a more
powerful and user friendly interface.

Nevertheless SDL should not be removed for the next few years.
I would not mind if buggy features like zooming were removed
from SDL again, but having a GUI which also works with framebuffers
(without X) and which has much less requirements than GTK+-2.0
(and perhaps also a reduced risk of security problems)
is a good feature.

Cheers,
Stefan Weil

PS: git complains about whitespace at eol in some of the patches,
and checkpatch.pl is also very noisy :-)
Anthony Liguori - Feb. 21, 2012, 12:20 a.m.
On 02/20/2012 02:45 PM, Stefan Weil wrote:
> Am 20.02.2012 00:45, schrieb Anthony Liguori:
>> This is minimalistic and just contains the basic widget infrastructure. The GUI
>> consists of a menu and a GtkNotebook. To start with, the notebook has its tabs
>> hidden which provides a UI that looks very similar to SDL with the exception of
>> the menu bar.
>>
>> The menu bar allows a user to toggle the visibility of the tabs. Cairo is used
>> for rendering.
>>
>> I used gtk-vnc as a reference. gtk-vnc solves the same basic problems as QEMU
>> since it was originally written as a remote display for QEMU. So for the most
>> part, the approach to rendering and keyboard handling should be pretty solid for
>> GTK.
>>
>> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
>> ---
>> Makefile | 2 +
>> Makefile.objs | 1 +
>> configure | 25 +++-
>> console.h | 4 +
>> sysemu.h | 1 +
>> ui/gtk.c | 551 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 6 files changed, 583 insertions(+), 1 deletions(-)
>> create mode 100644 ui/gtk.c
>>
>
> This patch compiles, but does not provide the minimalistic interface
> because the hooks in vl.c are missing. Parts of patch 6 might be
> moved to patch 2.

Yes, that's intentional in order to avoid bad bisecting.  I could make a 
-display gtk flag though and just not make it the default until the last patch. 
  I'll update accordingly.

> Patch 2 could also be improved by removing the dependency on VTE.
> For some platforms (w32), providing VTE is really a big challenge.
> I did not find a precompiled version and still did not succeed
> in compiling it because of a large and still incomplete dependency
> chain...

I'm not a big fan of this.  Would hosting dependencies on qemu.org help address 
this problem?

In order to do this, we would have to have mirror all of the dependencies onto 
git.qemu.org and create a super repository with submodules and a build script to 
cross compile all of the dependencies.  I don't think it's a huge effort.

> What about offering compilation without VTE also in the "final"
> version of the GTK gui (--enable-vte, --disable-vte)?

That's a pretty significant reduction in functionality.  I'd rather try to 
provide the dependencies directly.

>
> These includes in ui/gtk.c were not needed:
>
> +#include <vte/vte.h>
> +#include <sys/types.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> +#include <sys/wait.h>
> +#include <pty.h>
>
> If the version using VTE needs them, qemu_socket.h should be
> used (needed for w32 which does neither have sys/socket.h
> nor pty.h). Maybe most of these includes are not needed at all
> (relict from VNC?).

They are needed but not in this patch.  I missed this bit as I was extracting 
patches.

>
> With a patched vl.c and ui/gtk.c, the new GUI starts on w32
> but does not work stable.
> After some minutes, the GUI freezes (no more updates, menu no
> longer working).

My guess is that this has to do with the way the Win32 main loops work.   I'm 
not sure how to fix this since IIUC, the problem is that we basically have two 
loops for Win32, one that calls select() on fds and another that uses 
WaitForEvent or whatever the win32 function is.

> The configuration support still does not work as expected:
>
> configure should support --enable-gtk, --disable-gtk with the
> usual semantic (no GTK UI if --disable-gtk was given, fail if
> --enable-gtk was given and GTK is not found, provide help message).

Oh, I'll look into that.  I expect that it should work.

> The GTK user interface is a good starting point for a more
> powerful and user friendly interface.
>
> Nevertheless SDL should not be removed for the next few years.
> I would not mind if buggy features like zooming were removed
> from SDL again, but having a GUI which also works with framebuffers
> (without X) and which has much less requirements than GTK+-2.0
> (and perhaps also a reduced risk of security problems)
> is a good feature.

As I mentioned in another thread, I'd be very happy to keep the SDL interface if 
we removed the ugly bits (like the virtual console emulation).

>
> Cheers,
> Stefan Weil
>
> PS: git complains about whitespace at eol in some of the patches,
> and checkpatch.pl is also very noisy :-)

Yes, I'm aware.  I'll address it in the next round.

Thanks for the feedback.

Regards,

Anthony Liguori

>
>

Patch

diff --git a/Makefile b/Makefile
index e66e885..d90025c 100644
--- a/Makefile
+++ b/Makefile
@@ -120,6 +120,8 @@  ui/cocoa.o: ui/cocoa.m
 
 ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o baum.o: QEMU_CFLAGS += $(SDL_CFLAGS)
 
+ui/gtk.o: QEMU_CFLAGS += $(GTK_CFLAGS) $(VTE_CFLAGS)
+
 ui/vnc.o: QEMU_CFLAGS += $(VNC_TLS_CFLAGS)
 
 bt-host.o: QEMU_CFLAGS += $(BLUEZ_CFLAGS)
diff --git a/Makefile.objs b/Makefile.objs
index 391e524..09add6e 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -147,6 +147,7 @@  ui-obj-y += keymaps.o
 ui-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
 ui-obj-$(CONFIG_COCOA) += cocoa.o
 ui-obj-$(CONFIG_CURSES) += curses.o
+ui-obj-$(CONFIG_GTK) += gtk.o
 vnc-obj-y += vnc.o d3des.o
 vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o
 vnc-obj-y += vnc-enc-tight.o vnc-palette.o
diff --git a/configure b/configure
index 037f7f7..7b4a040 100755
--- a/configure
+++ b/configure
@@ -246,7 +246,7 @@  sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
 QEMU_CFLAGS="-fno-strict-aliasing $QEMU_CFLAGS"
 CFLAGS="-g $CFLAGS"
 QEMU_CFLAGS="-Wall -Wundef -Wwrite-strings -Wmissing-prototypes $QEMU_CFLAGS"
-QEMU_CFLAGS="-Wstrict-prototypes -Wredundant-decls $QEMU_CFLAGS"
+QEMU_CFLAGS="-Wredundant-decls $QEMU_CFLAGS"
 QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
 QEMU_CFLAGS="-D_FORTIFY_SOURCE=2 $QEMU_CFLAGS"
 QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
@@ -1487,6 +1487,23 @@  if test "$sparse" != "no" ; then
 fi
 
 ##########################################
+# GTK probe
+
+if test "$gtk" != "no"; then
+    if $pkg_config gtk+-2.0 --modversion >/dev/null 2>/dev/null && \
+       $pkg_config vte --modversion >/dev/null 2>/dev/null; then
+	gtk_cflags=`$pkg_config --cflags gtk+-2.0 2>/dev/null`
+	gtk_libs=`$pkg_config --libs gtk+-2.0 2>/dev/null`
+	vte_cflags=`$pkg_config --cflags vte 2>/dev/null`
+	vte_libs=`$pkg_config --libs vte 2>/dev/null`
+	libs_softmmu="$gtk_libs $vte_libs $libs_softmmu"
+	gtk="yes"
+    else
+	gtk="no"
+    fi
+fi
+
+##########################################
 # SDL probe
 
 # Look for sdl configuration program (pkg-config or sdl-config).  Try
@@ -2864,6 +2881,7 @@  if test "$darwin" = "yes" ; then
     echo "Cocoa support     $cocoa"
 fi
 echo "SDL support       $sdl"
+echo "GTK support       $gtk"
 echo "curses support    $curses"
 echo "curl support      $curl"
 echo "mingw32 support   $mingw32"
@@ -3147,6 +3165,11 @@  if test "$bluez" = "yes" ; then
   echo "BLUEZ_CFLAGS=$bluez_cflags" >> $config_host_mak
 fi
 echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
+if test "$gtk" = "yes" ; then
+  echo "CONFIG_GTK=y" >> $config_host_mak
+  echo "GTK_CFLAGS=$gtk_cflags" >> $config_host_mak
+  echo "VTE_CFLAGS=$vte_cflags" >> $config_host_mak
+fi
 if test "$xen" = "yes" ; then
   echo "CONFIG_XEN_BACKEND=y" >> $config_host_mak
   echo "CONFIG_XEN_CTRL_INTERFACE_VERSION=$xen_ctrl_version" >> $config_host_mak
diff --git a/console.h b/console.h
index 7225ae6..c322260 100644
--- a/console.h
+++ b/console.h
@@ -398,4 +398,8 @@  static inline int vnc_display_pw_expire(DisplayState *ds, time_t expires)
 /* curses.c */
 void curses_display_init(DisplayState *ds, int full_screen);
 
+/* gtk.c */
+void early_gtk_display_init(void);
+void gtk_display_init(DisplayState *ds);
+
 #endif
diff --git a/sysemu.h b/sysemu.h
index 9d5ce33..aa0e3e4 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -81,6 +81,7 @@  typedef enum DisplayType
     DT_DEFAULT,
     DT_CURSES,
     DT_SDL,
+    DT_GTK,
     DT_NOGRAPHIC,
     DT_NONE,
 } DisplayType;
diff --git a/ui/gtk.c b/ui/gtk.c
new file mode 100644
index 0000000..502705b
--- /dev/null
+++ b/ui/gtk.c
@@ -0,0 +1,551 @@ 
+/*
+ * GTK UI
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Anthony Liguori   <aliguori@us.ibm.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 <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <vte/vte.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <pty.h>
+#include <math.h>
+
+#include "qemu-common.h"
+#include "console.h"
+#include "sysemu.h"
+#include "qmp-commands.h"
+#include "x_keymap.h"
+#include "keymaps.h"
+
+//#define DEBUG_GTK
+
+#ifdef DEBUG_GTK
+#define dprintf(fmt, ...) printf(fmt, ## __VA_ARGS__)
+#else
+#define dprintf(fmt, ...) do { } while (0)
+#endif
+
+typedef struct VirtualConsole
+{
+    GtkWidget *menu_item;
+    GtkWidget *terminal;
+    GtkWidget *scrolled_window;
+    CharDriverState *chr;
+    int fd;
+} VirtualConsole;
+
+typedef struct GtkDisplayState
+{
+    GtkWidget *window;
+
+    GtkWidget *menu_bar;
+
+    GtkWidget *file_menu_item;
+    GtkWidget *file_menu;
+    GtkWidget *quit_item;
+
+    GtkWidget *view_menu_item;
+    GtkWidget *view_menu;
+    GtkWidget *vga_item;
+
+    GtkWidget *show_tabs_item;
+
+    GtkWidget *vbox;
+    GtkWidget *notebook;
+    GtkWidget *drawing_area;
+    cairo_surface_t *surface;
+    DisplayChangeListener dcl;
+    DisplayState *ds;
+    int button_mask;
+    int last_x;
+    int last_y;
+
+    double scale_x;
+    double scale_y;
+
+    GdkCursor *null_cursor;
+    Notifier mouse_mode_notifier;
+} GtkDisplayState;
+
+static GtkDisplayState *global_state;
+
+/** Utility Functions **/
+
+static void gd_update_cursor(GtkDisplayState *s, gboolean override)
+{
+    GdkWindow *window;
+    bool on_vga;
+
+    window = gtk_widget_get_window(GTK_WIDGET(s->drawing_area));
+
+    on_vga = (gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0);
+
+    if ((override || on_vga) && kbd_mouse_is_absolute()) {
+	gdk_window_set_cursor(window, s->null_cursor);
+    } else {
+	gdk_window_set_cursor(window, NULL);
+    }
+}
+
+static void gd_update_caption(GtkDisplayState *s)
+{
+    const char *status = "";
+    gchar *title;
+
+    if (!runstate_is_running()) {
+        status = " [Stopped]";
+    }
+
+    if (qemu_name) {
+        title = g_strdup_printf("QEMU (%s)%s", qemu_name, status);
+    } else {
+        title = g_strdup_printf("QEMU%s", status);
+    }
+        
+    gtk_window_set_title(GTK_WINDOW(s->window), title);
+
+    g_free(title);
+}
+
+/** DisplayState Callbacks **/
+
+static void gd_update(DisplayState *ds, int x, int y, int w, int h)
+{
+    GtkDisplayState *s = ds->opaque;
+    int x1, x2, y1, y2;
+
+    dprintf("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h);
+
+    x1 = floor(x * s->scale_x);
+    y1 = floor(y * s->scale_y);
+
+    x2 = ceil(x * s->scale_x + w * s->scale_x);
+    y2 = ceil(y * s->scale_y + h * s->scale_y);
+
+    gtk_widget_queue_draw_area(s->drawing_area, x1, y1, (x2 - x1), (y2 - y1));
+}
+
+static void gd_refresh(DisplayState *ds)
+{
+    vga_hw_update();
+}
+
+static void gd_resize(DisplayState *ds)
+{
+    GtkDisplayState *s = ds->opaque;
+    cairo_format_t kind;
+    int stride;
+
+    dprintf("resize(width=%d, height=%d)\n",
+            ds->surface->width, ds->surface->height);
+
+    if (s->surface) {
+        cairo_surface_destroy(s->surface);
+    }
+
+    switch (ds->surface->pf.bits_per_pixel) {
+    case 8:
+        kind = CAIRO_FORMAT_A8;
+        break;
+    case 16:
+        kind = CAIRO_FORMAT_RGB16_565;
+        break;
+    case 32:
+        kind = CAIRO_FORMAT_RGB24;
+        break;
+    default:
+        g_assert_not_reached();
+        break;
+    }
+
+    stride = cairo_format_stride_for_width(kind, ds->surface->width);
+    g_assert_cmpint(ds->surface->linesize, ==, stride);
+
+    s->surface = cairo_image_surface_create_for_data(ds->surface->data,
+                                                     kind,
+                                                     ds->surface->width,
+                                                     ds->surface->height,
+                                                     ds->surface->linesize);
+
+    gtk_widget_set_size_request(s->drawing_area,
+                                ds->surface->width * s->scale_x,
+                                ds->surface->height * s->scale_y);
+}
+
+/** QEMU Events **/
+
+static void gd_change_runstate(void *opaque, int running, RunState state)
+{
+    GtkDisplayState *s = opaque;
+
+    gd_update_caption(s);
+}
+
+static void gd_mouse_mode_change(Notifier *notify, void *data)
+{
+    gd_update_cursor(container_of(notify, GtkDisplayState, mouse_mode_notifier),
+                     FALSE);
+}
+
+/** GTK Events **/
+
+static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
+                                void *opaque)
+{
+    if (!no_quit) {
+        qmp_quit(NULL);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    int ww, wh;
+    int fbw, fbh;
+
+    fbw = s->ds->surface->width;
+    fbh = s->ds->surface->height;
+
+    gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
+
+    cairo_rectangle(cr, 0, 0, ww, wh);
+
+    if (ww != fbw || wh != fbh) {
+        s->scale_x = (double)ww / fbw;
+        s->scale_y = (double)wh / fbh;
+        cairo_scale(cr, s->scale_x, s->scale_y);
+    } else {
+        s->scale_x = 1.0;
+        s->scale_y = 1.0;
+    }
+
+    cairo_set_source_surface(cr, s->surface, 0, 0);
+    cairo_paint(cr);
+
+    return TRUE;
+}
+
+static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
+                                void *opaque)
+{
+    cairo_t *cr;
+    gboolean ret;
+
+    cr = gdk_cairo_create(gtk_widget_get_window(widget));
+    cairo_rectangle(cr,
+                    expose->area.x,
+                    expose->area.y,
+                    expose->area.width,
+                    expose->area.height);
+    cairo_clip(cr);
+
+    ret = gd_draw_event(widget, cr, opaque);
+
+    cairo_destroy(cr);
+
+    return ret;
+}
+
+static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
+                                void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    int dx, dy;
+    int x, y;
+
+    x = motion->x / s->scale_x;
+    y = motion->y / s->scale_y;
+
+    if (kbd_mouse_is_absolute()) {
+        dx = x * 0x7FFF / (s->ds->surface->width - 1);
+        dy = y * 0x7FFF / (s->ds->surface->height - 1);
+    } else if (s->last_x == -1 || s->last_y == -1) {
+        dx = 0;
+        dy = 0;
+    } else {
+        dx = x - s->last_x;
+        dy = y - s->last_y;
+    }
+
+    s->last_x = x;
+    s->last_y = y;
+
+    if (kbd_mouse_is_absolute()) {
+        kbd_mouse_event(dx, dy, 0, s->button_mask);
+    }
+
+    return TRUE;
+}
+
+static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
+                                void *opaque)
+{
+    GtkDisplayState *s = opaque;
+    int dx, dy;
+    int n;
+
+    if (button->button == 1) {
+        n = 0x01;
+    } else if (button->button == 2) {
+        n = 0x04;
+    } else if (button->button == 3) {
+        n = 0x02;
+    } else {
+        n = 0x00;
+    }
+
+    if (button->type == GDK_BUTTON_PRESS) {
+        s->button_mask |= n;
+    } else if (button->type == GDK_BUTTON_RELEASE) {
+        s->button_mask &= ~n;
+    }
+
+    if (kbd_mouse_is_absolute()) {
+        dx = s->last_x * 0x7FFF / (s->ds->surface->width - 1);
+        dy = s->last_y * 0x7FFF / (s->ds->surface->height - 1);
+    } else {
+        dx = 0;
+        dy = 0;
+    }
+
+    kbd_mouse_event(dx, dy, 0, s->button_mask);
+        
+    return TRUE;
+}
+
+static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
+{
+    int gdk_keycode;
+    int qemu_keycode;
+
+    gdk_keycode = key->hardware_keycode;
+
+    if (gdk_keycode < 9) {
+        qemu_keycode = 0;
+    } else if (gdk_keycode < 97) {
+        qemu_keycode = gdk_keycode - 8;
+    } else if (gdk_keycode < 158) {
+        qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
+    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
+        qemu_keycode = 0x70;
+    } else if (gdk_keycode == 211) { /* backslash */
+        qemu_keycode = 0x73;
+    } else {
+        qemu_keycode = 0;
+    }
+
+    dprintf("translated GDK keycode %d to QEMU keycode %d (%s)\n",
+            gdk_keycode, qemu_keycode,
+            (key->type == GDK_KEY_PRESS) ? "down" : "up");
+
+    if (qemu_keycode & SCANCODE_GREY) {
+        kbd_put_keycode(SCANCODE_EMUL0);
+    }
+
+    if (key->type == GDK_KEY_PRESS) {
+        kbd_put_keycode(qemu_keycode & SCANCODE_KEYCODEMASK);
+    } else if (key->type == GDK_KEY_RELEASE) {
+        kbd_put_keycode(qemu_keycode | SCANCODE_UP);
+    } else {
+        g_assert_not_reached();
+    }
+
+    return FALSE;
+}
+
+/** Window Menu Actions **/
+
+static void gd_menu_quit(GtkMenuItem *item, void *opaque)
+{
+    qmp_quit(NULL);
+}
+
+static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
+{
+    GtkDisplayState *s = opaque;
+
+    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
+        gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
+    }
+}
+
+static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
+{
+    GtkDisplayState *s = opaque;
+
+    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
+        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
+    } else {
+        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+    }
+}
+
+static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
+                           gpointer data)
+{
+    GtkDisplayState *s = data;
+
+    if (!gtk_widget_get_realized(s->notebook)) {
+        return;
+    }
+
+    gd_update_cursor(s, TRUE);
+}
+
+void early_gtk_display_init(void)
+{
+}
+
+/** Window Creation **/
+
+static void gd_connect_signals(GtkDisplayState *s)
+{
+    g_signal_connect(s->show_tabs_item, "activate",
+                     G_CALLBACK(gd_menu_show_tabs), s);
+
+    g_signal_connect(s->window, "delete-event",
+                     G_CALLBACK(gd_window_close), s);
+
+    g_signal_connect(s->drawing_area, "expose-event",
+                     G_CALLBACK(gd_expose_event), s);
+    g_signal_connect(s->drawing_area, "motion-notify-event",
+                     G_CALLBACK(gd_motion_event), s);
+    g_signal_connect(s->drawing_area, "button-press-event",
+                     G_CALLBACK(gd_button_event), s);
+    g_signal_connect(s->drawing_area, "button-release-event",
+                     G_CALLBACK(gd_button_event), s);
+    g_signal_connect(s->drawing_area, "key-press-event",
+                     G_CALLBACK(gd_key_event), s);
+    g_signal_connect(s->drawing_area, "key-release-event",
+                     G_CALLBACK(gd_key_event), s);
+
+    g_signal_connect(s->quit_item, "activate",
+                     G_CALLBACK(gd_menu_quit), s);
+    g_signal_connect(s->vga_item, "activate",
+                     G_CALLBACK(gd_menu_switch_vc), s);
+    g_signal_connect(s->notebook, "switch-page",
+                     G_CALLBACK(gd_change_page), s);
+}
+
+static void gd_create_menus(GtkDisplayState *s)
+{
+    GtkStockItem item;
+    GtkAccelGroup *accel_group;
+    GSList *group = NULL;
+    GtkWidget *separator;
+
+    accel_group = gtk_accel_group_new();
+    s->file_menu = gtk_menu_new();
+    gtk_menu_set_accel_group(GTK_MENU(s->file_menu), accel_group);
+    s->file_menu_item = gtk_menu_item_new_with_mnemonic("_File");
+
+    s->quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
+    gtk_stock_lookup(GTK_STOCK_QUIT, &item);
+    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
+                                 "<QEMU>/File/Quit");
+    gtk_accel_map_add_entry("<QEMU>/File/Quit", item.keyval, item.modifier);
+
+    s->view_menu = gtk_menu_new();
+    gtk_menu_set_accel_group(GTK_MENU(s->view_menu), accel_group);
+    s->view_menu_item = gtk_menu_item_new_with_mnemonic("_View");
+
+    separator = gtk_separator_menu_item_new();
+    gtk_menu_append(GTK_MENU(s->view_menu), separator);
+
+    s->vga_item = gtk_radio_menu_item_new_with_mnemonic(group, "_VGA");
+    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(s->vga_item));
+    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->vga_item),
+                                 "<QEMU>/View/VGA");
+    gtk_accel_map_add_entry("<QEMU>/View/VGA", GDK_KEY_1, GDK_CONTROL_MASK | GDK_MOD1_MASK);
+    gtk_menu_append(GTK_MENU(s->view_menu), s->vga_item);
+
+    separator = gtk_separator_menu_item_new();
+    gtk_menu_append(GTK_MENU(s->view_menu), separator);
+
+    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic("Show _Tabs");
+    gtk_menu_append(GTK_MENU(s->view_menu), s->show_tabs_item);
+
+    g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
+    gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
+
+    gtk_menu_append(GTK_MENU(s->file_menu), s->quit_item);
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->file_menu_item), s->file_menu);
+    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->file_menu_item);
+
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
+    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
+}
+
+void gtk_display_init(DisplayState *ds)
+{
+    GtkDisplayState *s = g_malloc0(sizeof(*s));
+
+    gtk_init(NULL, NULL);
+
+    ds->opaque = s;
+    s->ds = ds;
+    s->dcl.dpy_update = gd_update;
+    s->dcl.dpy_resize = gd_resize;
+    s->dcl.dpy_refresh = gd_refresh;
+    register_displaychangelistener(ds, &s->dcl);
+
+    s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    s->vbox = gtk_vbox_new(FALSE, 0);
+    s->notebook = gtk_notebook_new();
+    s->drawing_area = gtk_drawing_area_new();
+    s->menu_bar = gtk_menu_bar_new();
+
+    s->scale_x = 1.0;
+    s->scale_y = 1.0;
+
+    s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
+
+    s->mouse_mode_notifier.notify = gd_mouse_mode_change;
+    qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
+    qemu_add_vm_change_state_handler(gd_change_runstate, s);
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), s->drawing_area, gtk_label_new("VGA"));
+
+    gd_create_menus(s);
+
+    gd_connect_signals(s);
+
+    gtk_widget_add_events(s->drawing_area,
+                          GDK_POINTER_MOTION_MASK |
+                          GDK_BUTTON_PRESS_MASK |
+                          GDK_BUTTON_RELEASE_MASK |
+                          GDK_BUTTON_MOTION_MASK |
+                          GDK_SCROLL_MASK |
+                          GDK_KEY_PRESS_MASK);
+    gtk_widget_set_double_buffered(s->drawing_area, FALSE);
+    gtk_widget_set_can_focus(s->drawing_area, TRUE);
+
+    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+    gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
+
+    gtk_window_set_resizable(GTK_WINDOW(s->window), FALSE);
+
+    gd_update_caption(s);
+
+    gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
+    gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
+
+    gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
+
+    gtk_widget_show_all(s->window);
+
+    global_state = s;
+}