diff mbox

[2/7] ui: add basic GTK gui (v2)

Message ID 1346872724-9156-3-git-send-email-aliguori@us.ibm.com
State New
Headers show

Commit Message

Anthony Liguori Sept. 5, 2012, 7:18 p.m. UTC
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>
---
v1 -> v2
 - add gtk-vnc license
 - fix key propagation
---
 Makefile         |    2 +
 configure        |   25 +++-
 console.h        |    4 +
 sysemu.h         |    1 +
 ui/Makefile.objs |    1 +
 ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 604 insertions(+), 1 deletions(-)
 create mode 100644 ui/gtk.c

Comments

Blue Swirl Sept. 5, 2012, 7:53 p.m. UTC | #1
On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> 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>
> ---
> v1 -> v2
>  - add gtk-vnc license
>  - fix key propagation
> ---
>  Makefile         |    2 +
>  configure        |   25 +++-
>  console.h        |    4 +
>  sysemu.h         |    1 +
>  ui/Makefile.objs |    1 +
>  ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 604 insertions(+), 1 deletions(-)
>  create mode 100644 ui/gtk.c
>
> diff --git a/Makefile b/Makefile
> index 1cd5bc8..9523e05 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -122,6 +122,8 @@ ui/cocoa.o: ui/cocoa.m
>
>  ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
> index d97fd81..586dbd3 100755
> --- a/configure
> +++ b/configure
> @@ -278,7 +278,7 @@ sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
>  # default flags for all hosts
>  QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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"

Is this intentional? I hope not.

>  QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
>  QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
>  if test "$debug_info" = "yes"; then
> @@ -1635,6 +1635,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
> @@ -3113,6 +3130,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"
> @@ -3390,6 +3408,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 eb428f9..2bd4814 100644
> --- a/console.h
> +++ b/console.h
> @@ -401,4 +401,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 65552ac..09a8523 100644
> --- a/sysemu.h
> +++ b/sysemu.h
> @@ -93,6 +93,7 @@ typedef enum DisplayType
>      DT_DEFAULT,
>      DT_CURSES,
>      DT_SDL,
> +    DT_GTK,
>      DT_NOGRAPHIC,
>      DT_NONE,
>  } DisplayType;
> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
> index adc07be..89f8d64 100644
> --- a/ui/Makefile.objs
> +++ b/ui/Makefile.objs
> @@ -12,3 +12,4 @@ common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
>  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
> diff --git a/ui/gtk.c b/ui/gtk.c
> new file mode 100644
> index 0000000..e724956
> --- /dev/null
> +++ b/ui/gtk.c
> @@ -0,0 +1,572 @@
> +/*
> + * 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.
> + *
> + * Portions from gtk-vnc:
> + *
> + * GTK VNC Widget
> + *
> + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.0 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

Please use the recommended web version like other files.

> + */
> +
> +#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__)

dprintf is actually defined by stdio.h. Either rename or #undef first.

> +#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 TRUE;
> +}
> +
> +/** 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;
> +}
> --
> 1.7.5.4
>
>
Eric Blake Sept. 5, 2012, 8 p.m. UTC | #2
On 09/05/2012 01:53 PM, Blue Swirl wrote:
> On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>> 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.
>>

>> +#ifdef DEBUG_GTK
>> +#define dprintf(fmt, ...) printf(fmt, ## __VA_ARGS__)
> 
> dprintf is actually defined by stdio.h. Either rename or #undef first.

Worse than that, dprintf() is mandated by POSIX 2008, and you are using
a reserved name with different meaning, which can give undefined
behavior.  Your best bet is to use a name other than dprintf.
Stefan Weil Sept. 5, 2012, 8:04 p.m. UTC | #3
Am 05.09.2012 21:18, 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>
> ---
> v1 ->  v2
>   - add gtk-vnc license
>   - fix key propagation
> ---
>   Makefile         |    2 +
>   configure        |   25 +++-
>   console.h        |    4 +
>   sysemu.h         |    1 +
>   ui/Makefile.objs |    1 +
>   ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   6 files changed, 604 insertions(+), 1 deletions(-)
>   create mode 100644 ui/gtk.c
>

Hi Anthony,

I tested the previous version of your series and noticed that the
menu accelerators (especially for file/quit) which are still part of
this new patch are a bad idea.

Just run a system emulation, start some GUI program in the emulation
and press Ctrl-Q: you won't terminate the GUI program, but terminate
QEMU!

Cheers,

Stefan W.
Anthony Liguori Sept. 5, 2012, 8:38 p.m. UTC | #4
Blue Swirl <blauwirbel@gmail.com> writes:

> On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>> 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>
>> ---
>> v1 -> v2
>>  - add gtk-vnc license
>>  - fix key propagation
>> ---
>>  Makefile         |    2 +
>>  configure        |   25 +++-
>>  console.h        |    4 +
>>  sysemu.h         |    1 +
>>  ui/Makefile.objs |    1 +
>>  ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  6 files changed, 604 insertions(+), 1 deletions(-)
>>  create mode 100644 ui/gtk.c
>>
>> diff --git a/Makefile b/Makefile
>> index 1cd5bc8..9523e05 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -122,6 +122,8 @@ ui/cocoa.o: ui/cocoa.m
>>
>>  ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
>> index d97fd81..586dbd3 100755
>> --- a/configure
>> +++ b/configure
>> @@ -278,7 +278,7 @@ sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
>>  # default flags for all hosts
>>  QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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"
>
> Is this intentional? I hope not.

I forgot to split this out into a separate patch but yes, this is
intentional.  GTK won't build with -Wstrict-prototypes.

Regards,

Anthony Liguori

>
>>  QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
>>  QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
>>  if test "$debug_info" = "yes"; then
>> @@ -1635,6 +1635,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
>> @@ -3113,6 +3130,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"
>> @@ -3390,6 +3408,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 eb428f9..2bd4814 100644
>> --- a/console.h
>> +++ b/console.h
>> @@ -401,4 +401,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 65552ac..09a8523 100644
>> --- a/sysemu.h
>> +++ b/sysemu.h
>> @@ -93,6 +93,7 @@ typedef enum DisplayType
>>      DT_DEFAULT,
>>      DT_CURSES,
>>      DT_SDL,
>> +    DT_GTK,
>>      DT_NOGRAPHIC,
>>      DT_NONE,
>>  } DisplayType;
>> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
>> index adc07be..89f8d64 100644
>> --- a/ui/Makefile.objs
>> +++ b/ui/Makefile.objs
>> @@ -12,3 +12,4 @@ common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
>>  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
>> diff --git a/ui/gtk.c b/ui/gtk.c
>> new file mode 100644
>> index 0000000..e724956
>> --- /dev/null
>> +++ b/ui/gtk.c
>> @@ -0,0 +1,572 @@
>> +/*
>> + * 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.
>> + *
>> + * Portions from gtk-vnc:
>> + *
>> + * GTK VNC Widget
>> + *
>> + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
>> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
>> + *
>> + * This library is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.0 of the License, or (at your option) any later version.
>> + *
>> + * This library is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with this library; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
>
> Please use the recommended web version like other files.
>
>> + */
>> +
>> +#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__)
>
> dprintf is actually defined by stdio.h. Either rename or #undef first.
>
>> +#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 TRUE;
>> +}
>> +
>> +/** 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;
>> +}
>> --
>> 1.7.5.4
>>
>>
Anthony Liguori Sept. 5, 2012, 8:39 p.m. UTC | #5
Stefan Weil <sw@weilnetz.de> writes:

> Am 05.09.2012 21:18, 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>
>> ---
>> v1 ->  v2
>>   - add gtk-vnc license
>>   - fix key propagation
>> ---
>>   Makefile         |    2 +
>>   configure        |   25 +++-
>>   console.h        |    4 +
>>   sysemu.h         |    1 +
>>   ui/Makefile.objs |    1 +
>>   ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   6 files changed, 604 insertions(+), 1 deletions(-)
>>   create mode 100644 ui/gtk.c
>>
>
> Hi Anthony,
>
> I tested the previous version of your series and noticed that the
> menu accelerators (especially for file/quit) which are still part of
> this new patch are a bad idea.
>
> Just run a system emulation, start some GUI program in the emulation
> and press Ctrl-Q: you won't terminate the GUI program, but terminate
> QEMU!

How is that different from ctrl-alt-f or any of the other accelerators
we use?

If the argument is that Ctrl-Q is too common, that's one thing, but
we can't avoid using accelerators.

Regards,

Anthony Liguori

>
> Cheers,
>
> Stefan W.
Jan Kiszka Sept. 5, 2012, 8:45 p.m. UTC | #6
On 2012-09-05 22:04, Stefan Weil wrote:
> Am 05.09.2012 21:18, 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>
>> ---
>> v1 ->  v2
>>   - add gtk-vnc license
>>   - fix key propagation
>> ---
>>   Makefile         |    2 +
>>   configure        |   25 +++-
>>   console.h        |    4 +
>>   sysemu.h         |    1 +
>>   ui/Makefile.objs |    1 +
>>   ui/gtk.c         |  572
>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>   6 files changed, 604 insertions(+), 1 deletions(-)
>>   create mode 100644 ui/gtk.c
>>
> 
> Hi Anthony,
> 
> I tested the previous version of your series and noticed that the
> menu accelerators (especially for file/quit) which are still part of
> this new patch are a bad idea.
> 
> Just run a system emulation, start some GUI program in the emulation
> and press Ctrl-Q: you won't terminate the GUI program, but terminate
> QEMU!

Yep, standard accelerators must go, definitely while the input is grabbed.

Jan
Jan Kiszka Sept. 5, 2012, 8:54 p.m. UTC | #7
On 2012-09-05 21:18, Anthony Liguori wrote:
> 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>
> ---
> v1 -> v2
>  - add gtk-vnc license
>  - fix key propagation

...

> +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");

Minor nit, but "VGA" is the wrong name for QEMU in general. Maybe "Display"?

Jan
Blue Swirl Sept. 5, 2012, 9 p.m. UTC | #8
On Wed, Sep 5, 2012 at 8:38 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> Blue Swirl <blauwirbel@gmail.com> writes:
>
>> On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>>> 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>
>>> ---
>>> v1 -> v2
>>>  - add gtk-vnc license
>>>  - fix key propagation
>>> ---
>>>  Makefile         |    2 +
>>>  configure        |   25 +++-
>>>  console.h        |    4 +
>>>  sysemu.h         |    1 +
>>>  ui/Makefile.objs |    1 +
>>>  ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>  6 files changed, 604 insertions(+), 1 deletions(-)
>>>  create mode 100644 ui/gtk.c
>>>
>>> diff --git a/Makefile b/Makefile
>>> index 1cd5bc8..9523e05 100644
>>> --- a/Makefile
>>> +++ b/Makefile
>>> @@ -122,6 +122,8 @@ ui/cocoa.o: ui/cocoa.m
>>>
>>>  ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
>>> index d97fd81..586dbd3 100755
>>> --- a/configure
>>> +++ b/configure
>>> @@ -278,7 +278,7 @@ sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
>>>  # default flags for all hosts
>>>  QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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"
>>
>> Is this intentional? I hope not.
>
> I forgot to split this out into a separate patch but yes, this is
> intentional.  GTK won't build with -Wstrict-prototypes.

I think we should avoid this at all costs, for example compile
ui/gtk.c with different set of flags or use
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#pragma GCC diagnostic error "-Wstrict-prototypes"
when the compiler supports them.

>
> Regards,
>
> Anthony Liguori
>
>>
>>>  QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
>>>  QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
>>>  if test "$debug_info" = "yes"; then
>>> @@ -1635,6 +1635,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
>>> @@ -3113,6 +3130,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"
>>> @@ -3390,6 +3408,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 eb428f9..2bd4814 100644
>>> --- a/console.h
>>> +++ b/console.h
>>> @@ -401,4 +401,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 65552ac..09a8523 100644
>>> --- a/sysemu.h
>>> +++ b/sysemu.h
>>> @@ -93,6 +93,7 @@ typedef enum DisplayType
>>>      DT_DEFAULT,
>>>      DT_CURSES,
>>>      DT_SDL,
>>> +    DT_GTK,
>>>      DT_NOGRAPHIC,
>>>      DT_NONE,
>>>  } DisplayType;
>>> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
>>> index adc07be..89f8d64 100644
>>> --- a/ui/Makefile.objs
>>> +++ b/ui/Makefile.objs
>>> @@ -12,3 +12,4 @@ common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
>>>  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
>>> diff --git a/ui/gtk.c b/ui/gtk.c
>>> new file mode 100644
>>> index 0000000..e724956
>>> --- /dev/null
>>> +++ b/ui/gtk.c
>>> @@ -0,0 +1,572 @@
>>> +/*
>>> + * 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.
>>> + *
>>> + * Portions from gtk-vnc:
>>> + *
>>> + * GTK VNC Widget
>>> + *
>>> + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
>>> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
>>> + *
>>> + * This library is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU Lesser General Public
>>> + * License as published by the Free Software Foundation; either
>>> + * version 2.0 of the License, or (at your option) any later version.
>>> + *
>>> + * This library is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + * Lesser General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU Lesser General Public
>>> + * License along with this library; if not, write to the Free Software
>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
>>
>> Please use the recommended web version like other files.
>>
>>> + */
>>> +
>>> +#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__)
>>
>> dprintf is actually defined by stdio.h. Either rename or #undef first.
>>
>>> +#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 TRUE;
>>> +}
>>> +
>>> +/** 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;
>>> +}
>>> --
>>> 1.7.5.4
>>>
>>>
>
Anthony Liguori Sept. 5, 2012, 10:55 p.m. UTC | #9
Jan Kiszka <jan.kiszka@web.de> writes:

> On 2012-09-05 22:04, Stefan Weil wrote:
>> Am 05.09.2012 21:18, 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>
>>> ---
>>> v1 ->  v2
>>>   - add gtk-vnc license
>>>   - fix key propagation
>>> ---
>>>   Makefile         |    2 +
>>>   configure        |   25 +++-
>>>   console.h        |    4 +
>>>   sysemu.h         |    1 +
>>>   ui/Makefile.objs |    1 +
>>>   ui/gtk.c         |  572
>>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>   6 files changed, 604 insertions(+), 1 deletions(-)
>>>   create mode 100644 ui/gtk.c
>>>
>> 
>> Hi Anthony,
>> 
>> I tested the previous version of your series and noticed that the
>> menu accelerators (especially for file/quit) which are still part of
>> this new patch are a bad idea.
>> 
>> Just run a system emulation, start some GUI program in the emulation
>> and press Ctrl-Q: you won't terminate the GUI program, but terminate
>> QEMU!
>
> Yep, standard accelerators must go, definitely while the input is
> grabbed.

I finally figured out how to do this in GTK so I can limit the number of
accelerators that are active while keyboard grab is active.

It'll be a part of v4.

Regards,

Anthony Liguori

>
> Jan
Anthony Liguori Sept. 5, 2012, 11:07 p.m. UTC | #10
Blue Swirl <blauwirbel@gmail.com> writes:

> On Wed, Sep 5, 2012 at 8:38 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>> Blue Swirl <blauwirbel@gmail.com> writes:
>>
>>> On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>>>> 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>
>>>> ---
>>>> v1 -> v2
>>>>  - add gtk-vnc license
>>>>  - fix key propagation
>>>> ---
>>>>  Makefile         |    2 +
>>>>  configure        |   25 +++-
>>>>  console.h        |    4 +
>>>>  sysemu.h         |    1 +
>>>>  ui/Makefile.objs |    1 +
>>>>  ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>>  6 files changed, 604 insertions(+), 1 deletions(-)
>>>>  create mode 100644 ui/gtk.c
>>>>
>>>> diff --git a/Makefile b/Makefile
>>>> index 1cd5bc8..9523e05 100644
>>>> --- a/Makefile
>>>> +++ b/Makefile
>>>> @@ -122,6 +122,8 @@ ui/cocoa.o: ui/cocoa.m
>>>>
>>>>  ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
>>>> index d97fd81..586dbd3 100755
>>>> --- a/configure
>>>> +++ b/configure
>>>> @@ -278,7 +278,7 @@ sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
>>>>  # default flags for all hosts
>>>>  QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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"
>>>
>>> Is this intentional? I hope not.
>>
>> I forgot to split this out into a separate patch but yes, this is
>> intentional.  GTK won't build with -Wstrict-prototypes.
>
> I think we should avoid this at all costs, for example compile
> ui/gtk.c with different set of flags or use
> #pragma GCC diagnostic ignored "-Wstrict-prototypes"
> #pragma GCC diagnostic error "-Wstrict-prototypes"
> when the compiler supports them.

The problem is that anything that includes gtk.h would need this.

I'm not sure I agree this warning has much real value in QEMU.  No one
submits patches using K&R declarations and I think most people are
trained to explicitly use (void).

The problem in GTK is gtkitemfactory.h.  Specifically:

/* We use () here to mean unspecified arguments. This is deprecated
 * as of C99, but we can't change it without breaking compatibility.
 * (Note that if we are included from a C++ program () will mean
 * (void) so an explicit cast will be needed.)
 */
typedef	void	(*GtkItemFactoryCallback)  ();

While deprecated in C99, it's still valid C and it's being used
appropriately.

Note that we require this same construct with getucontext but since it
comes from a system header, it's excluded from warnings like this.

Regards,

Anthony Liguori

>
>>
>> Regards,
>>
>> Anthony Liguori
>>
>>>
>>>>  QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
>>>>  QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
>>>>  if test "$debug_info" = "yes"; then
>>>> @@ -1635,6 +1635,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
>>>> @@ -3113,6 +3130,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"
>>>> @@ -3390,6 +3408,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 eb428f9..2bd4814 100644
>>>> --- a/console.h
>>>> +++ b/console.h
>>>> @@ -401,4 +401,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 65552ac..09a8523 100644
>>>> --- a/sysemu.h
>>>> +++ b/sysemu.h
>>>> @@ -93,6 +93,7 @@ typedef enum DisplayType
>>>>      DT_DEFAULT,
>>>>      DT_CURSES,
>>>>      DT_SDL,
>>>> +    DT_GTK,
>>>>      DT_NOGRAPHIC,
>>>>      DT_NONE,
>>>>  } DisplayType;
>>>> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
>>>> index adc07be..89f8d64 100644
>>>> --- a/ui/Makefile.objs
>>>> +++ b/ui/Makefile.objs
>>>> @@ -12,3 +12,4 @@ common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
>>>>  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
>>>> diff --git a/ui/gtk.c b/ui/gtk.c
>>>> new file mode 100644
>>>> index 0000000..e724956
>>>> --- /dev/null
>>>> +++ b/ui/gtk.c
>>>> @@ -0,0 +1,572 @@
>>>> +/*
>>>> + * 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.
>>>> + *
>>>> + * Portions from gtk-vnc:
>>>> + *
>>>> + * GTK VNC Widget
>>>> + *
>>>> + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
>>>> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
>>>> + *
>>>> + * This library is free software; you can redistribute it and/or
>>>> + * modify it under the terms of the GNU Lesser General Public
>>>> + * License as published by the Free Software Foundation; either
>>>> + * version 2.0 of the License, or (at your option) any later version.
>>>> + *
>>>> + * This library is distributed in the hope that it will be useful,
>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>> + * Lesser General Public License for more details.
>>>> + *
>>>> + * You should have received a copy of the GNU Lesser General Public
>>>> + * License along with this library; if not, write to the Free Software
>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
>>>
>>> Please use the recommended web version like other files.
>>>
>>>> + */
>>>> +
>>>> +#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__)
>>>
>>> dprintf is actually defined by stdio.h. Either rename or #undef first.
>>>
>>>> +#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 TRUE;
>>>> +}
>>>> +
>>>> +/** 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;
>>>> +}
>>>> --
>>>> 1.7.5.4
>>>>
>>>>
>>
Blue Swirl Sept. 8, 2012, 7 a.m. UTC | #11
On Wed, Sep 5, 2012 at 11:07 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
> Blue Swirl <blauwirbel@gmail.com> writes:
>
>> On Wed, Sep 5, 2012 at 8:38 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>>> Blue Swirl <blauwirbel@gmail.com> writes:
>>>
>>>> On Wed, Sep 5, 2012 at 7:18 PM, Anthony Liguori <aliguori@us.ibm.com> wrote:
>>>>> 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>
>>>>> ---
>>>>> v1 -> v2
>>>>>  - add gtk-vnc license
>>>>>  - fix key propagation
>>>>> ---
>>>>>  Makefile         |    2 +
>>>>>  configure        |   25 +++-
>>>>>  console.h        |    4 +
>>>>>  sysemu.h         |    1 +
>>>>>  ui/Makefile.objs |    1 +
>>>>>  ui/gtk.c         |  572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>>>  6 files changed, 604 insertions(+), 1 deletions(-)
>>>>>  create mode 100644 ui/gtk.c
>>>>>
>>>>> diff --git a/Makefile b/Makefile
>>>>> index 1cd5bc8..9523e05 100644
>>>>> --- a/Makefile
>>>>> +++ b/Makefile
>>>>> @@ -122,6 +122,8 @@ ui/cocoa.o: ui/cocoa.m
>>>>>
>>>>>  ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
>>>>> index d97fd81..586dbd3 100755
>>>>> --- a/configure
>>>>> +++ b/configure
>>>>> @@ -278,7 +278,7 @@ sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
>>>>>  # default flags for all hosts
>>>>>  QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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"
>>>>
>>>> Is this intentional? I hope not.
>>>
>>> I forgot to split this out into a separate patch but yes, this is
>>> intentional.  GTK won't build with -Wstrict-prototypes.
>>
>> I think we should avoid this at all costs, for example compile
>> ui/gtk.c with different set of flags or use
>> #pragma GCC diagnostic ignored "-Wstrict-prototypes"
>> #pragma GCC diagnostic error "-Wstrict-prototypes"
>> when the compiler supports them.
>
> The problem is that anything that includes gtk.h would need this.

But that's only ui/gkt.c.

A nice way to handle this could be to add a check to configure for GTK
compatibility with -Wstrict-prototypes. Then the flag is retained if
either GTK is not used or a fixed GTK appears.

>
> I'm not sure I agree this warning has much real value in QEMU.  No one
> submits patches using K&R declarations and I think most people are
> trained to explicitly use (void).

Right, I had actually confused this with -Wmissing-prototypes, this
flag is not so important. Looking at
a5f1b965dae70f7d41721edaacb109d80721b966, there could be also function
pointer declarations which the flag use may detect but otherwise I
agree.

>
> The problem in GTK is gtkitemfactory.h.  Specifically:
>
> /* We use () here to mean unspecified arguments. This is deprecated
>  * as of C99, but we can't change it without breaking compatibility.
>  * (Note that if we are included from a C++ program () will mean
>  * (void) so an explicit cast will be needed.)
>  */
> typedef void    (*GtkItemFactoryCallback)  ();
>
> While deprecated in C99, it's still valid C and it's being used
> appropriately.
>
> Note that we require this same construct with getucontext but since it
> comes from a system header, it's excluded from warnings like this.

Perhaps the real problem is that GTK is not considered a system header
from compiler point of view. We could use -isystem or #pragma GCC
system_header to fix it:
http://gcc.gnu.org/onlinedocs/cpp/System-Headers.html

The pragmas are not supported by all GCC versions though.

>
> Regards,
>
> Anthony Liguori
>
>>
>>>
>>> Regards,
>>>
>>> Anthony Liguori
>>>
>>>>
>>>>>  QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS"
>>>>>  QEMU_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
>>>>>  if test "$debug_info" = "yes"; then
>>>>> @@ -1635,6 +1635,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
>>>>> @@ -3113,6 +3130,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"
>>>>> @@ -3390,6 +3408,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 eb428f9..2bd4814 100644
>>>>> --- a/console.h
>>>>> +++ b/console.h
>>>>> @@ -401,4 +401,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 65552ac..09a8523 100644
>>>>> --- a/sysemu.h
>>>>> +++ b/sysemu.h
>>>>> @@ -93,6 +93,7 @@ typedef enum DisplayType
>>>>>      DT_DEFAULT,
>>>>>      DT_CURSES,
>>>>>      DT_SDL,
>>>>> +    DT_GTK,
>>>>>      DT_NOGRAPHIC,
>>>>>      DT_NONE,
>>>>>  } DisplayType;
>>>>> diff --git a/ui/Makefile.objs b/ui/Makefile.objs
>>>>> index adc07be..89f8d64 100644
>>>>> --- a/ui/Makefile.objs
>>>>> +++ b/ui/Makefile.objs
>>>>> @@ -12,3 +12,4 @@ common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
>>>>>  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
>>>>> diff --git a/ui/gtk.c b/ui/gtk.c
>>>>> new file mode 100644
>>>>> index 0000000..e724956
>>>>> --- /dev/null
>>>>> +++ b/ui/gtk.c
>>>>> @@ -0,0 +1,572 @@
>>>>> +/*
>>>>> + * 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.
>>>>> + *
>>>>> + * Portions from gtk-vnc:
>>>>> + *
>>>>> + * GTK VNC Widget
>>>>> + *
>>>>> + * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
>>>>> + * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
>>>>> + *
>>>>> + * This library is free software; you can redistribute it and/or
>>>>> + * modify it under the terms of the GNU Lesser General Public
>>>>> + * License as published by the Free Software Foundation; either
>>>>> + * version 2.0 of the License, or (at your option) any later version.
>>>>> + *
>>>>> + * This library is distributed in the hope that it will be useful,
>>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>>> + * Lesser General Public License for more details.
>>>>> + *
>>>>> + * You should have received a copy of the GNU Lesser General Public
>>>>> + * License along with this library; if not, write to the Free Software
>>>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
>>>>
>>>> Please use the recommended web version like other files.
>>>>
>>>>> + */
>>>>> +
>>>>> +#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__)
>>>>
>>>> dprintf is actually defined by stdio.h. Either rename or #undef first.
>>>>
>>>>> +#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 TRUE;
>>>>> +}
>>>>> +
>>>>> +/** 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;
>>>>> +}
>>>>> --
>>>>> 1.7.5.4
>>>>>
>>>>>
>>>
>
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 1cd5bc8..9523e05 100644
--- a/Makefile
+++ b/Makefile
@@ -122,6 +122,8 @@  ui/cocoa.o: ui/cocoa.m
 
 ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o hw/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/configure b/configure
index d97fd81..586dbd3 100755
--- a/configure
+++ b/configure
@@ -278,7 +278,7 @@  sdl_config="${SDL_CONFIG-${cross_prefix}sdl-config}"
 # default flags for all hosts
 QEMU_CFLAGS="-fno-strict-aliasing $QEMU_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_INCLUDES="-I. -I\$(SRC_PATH) -I\$(SRC_PATH)/fpu"
 if test "$debug_info" = "yes"; then
@@ -1635,6 +1635,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
@@ -3113,6 +3130,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"
@@ -3390,6 +3408,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 eb428f9..2bd4814 100644
--- a/console.h
+++ b/console.h
@@ -401,4 +401,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 65552ac..09a8523 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -93,6 +93,7 @@  typedef enum DisplayType
     DT_DEFAULT,
     DT_CURSES,
     DT_SDL,
+    DT_GTK,
     DT_NOGRAPHIC,
     DT_NONE,
 } DisplayType;
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
index adc07be..89f8d64 100644
--- a/ui/Makefile.objs
+++ b/ui/Makefile.objs
@@ -12,3 +12,4 @@  common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
 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
diff --git a/ui/gtk.c b/ui/gtk.c
new file mode 100644
index 0000000..e724956
--- /dev/null
+++ b/ui/gtk.c
@@ -0,0 +1,572 @@ 
+/*
+ * 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.
+ *
+ * Portions from gtk-vnc:
+ *
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#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 TRUE;
+}
+
+/** 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;
+}