qemu/ui/gtk.c
<<
>>
Prefs
   1/*
   2 * GTK UI
   3 *
   4 * Copyright IBM, Corp. 2012
   5 *
   6 * Authors:
   7 *  Anthony Liguori   <aliguori@us.ibm.com>
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  10 * See the COPYING file in the top-level directory.
  11 *
  12 * Portions from gtk-vnc:
  13 *
  14 * GTK VNC Widget
  15 *
  16 * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
  17 * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
  18 *
  19 * This library is free software; you can redistribute it and/or
  20 * modify it under the terms of the GNU Lesser General Public
  21 * License as published by the Free Software Foundation; either
  22 * version 2.0 of the License, or (at your option) any later version.
  23 *
  24 * This library is distributed in the hope that it will be useful,
  25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  27 * Lesser General Public License for more details.
  28 *
  29 * You should have received a copy of the GNU Lesser General Public
  30 * License along with this library; if not, write to the Free Software
  31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
  32 */
  33
  34#define GETTEXT_PACKAGE "qemu"
  35#define LOCALEDIR "po"
  36
  37#include "qemu/osdep.h"
  38#include "qemu-common.h"
  39#include "qemu/cutils.h"
  40
  41#include "ui/console.h"
  42#include "ui/gtk.h"
  43
  44#include <glib/gi18n.h>
  45#include <locale.h>
  46#if defined(CONFIG_VTE)
  47#include <vte/vte.h>
  48#endif
  49#include <math.h>
  50
  51#include "trace.h"
  52#include "ui/input.h"
  53#include "sysemu/sysemu.h"
  54#include "qmp-commands.h"
  55#include "x_keymap.h"
  56#include "keymaps.h"
  57#include "sysemu/char.h"
  58#include "qom/object.h"
  59
  60#define MAX_VCS 10
  61#define VC_WINDOW_X_MIN  320
  62#define VC_WINDOW_Y_MIN  240
  63#define VC_TERM_X_MIN     80
  64#define VC_TERM_Y_MIN     25
  65#define VC_SCALE_MIN    0.25
  66#define VC_SCALE_STEP   0.25
  67
  68#if !defined(CONFIG_VTE)
  69# define VTE_CHECK_VERSION(a, b, c) 0
  70#endif
  71
  72#if defined(CONFIG_VTE) && !GTK_CHECK_VERSION(3, 0, 0)
  73/*
  74 * The gtk2 vte terminal widget seriously messes up the window resize
  75 * for some reason.  You basically can't make the qemu window smaller
  76 * any more because the toplevel window geoemtry hints are overridden.
  77 *
  78 * Workaround that by hiding all vte widgets, except the one in the
  79 * current tab.
  80 *
  81 * Luckily everything works smooth in gtk3.
  82 */
  83# define VTE_RESIZE_HACK 1
  84#endif
  85
  86#if !GTK_CHECK_VERSION(2, 20, 0)
  87#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget)
  88#endif
  89
  90#ifndef GDK_IS_X11_DISPLAY
  91#define GDK_IS_X11_DISPLAY(dpy) (dpy == dpy)
  92#endif
  93#ifndef GDK_IS_WIN32_DISPLAY
  94#define GDK_IS_WIN32_DISPLAY(dpy) (dpy == dpy)
  95#endif
  96
  97#ifndef GDK_KEY_0
  98#define GDK_KEY_0 GDK_0
  99#define GDK_KEY_1 GDK_1
 100#define GDK_KEY_2 GDK_2
 101#define GDK_KEY_f GDK_f
 102#define GDK_KEY_g GDK_g
 103#define GDK_KEY_q GDK_q
 104#define GDK_KEY_plus GDK_plus
 105#define GDK_KEY_minus GDK_minus
 106#define GDK_KEY_Pause GDK_Pause
 107#endif
 108
 109/* Some older mingw versions lack this constant or have
 110 * it conditionally defined */
 111#ifdef _WIN32
 112# ifndef MAPVK_VK_TO_VSC
 113#  define MAPVK_VK_TO_VSC 0
 114# endif
 115#endif
 116
 117
 118#define HOTKEY_MODIFIERS        (GDK_CONTROL_MASK | GDK_MOD1_MASK)
 119
 120static const int modifier_keycode[] = {
 121    /* shift, control, alt keys, meta keys, both left & right */
 122    0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd,
 123};
 124
 125struct GtkDisplayState {
 126    GtkWidget *window;
 127
 128    GtkWidget *menu_bar;
 129
 130    GtkAccelGroup *accel_group;
 131
 132    GtkWidget *machine_menu_item;
 133    GtkWidget *machine_menu;
 134    GtkWidget *pause_item;
 135    GtkWidget *reset_item;
 136    GtkWidget *powerdown_item;
 137    GtkWidget *quit_item;
 138
 139    GtkWidget *view_menu_item;
 140    GtkWidget *view_menu;
 141    GtkWidget *full_screen_item;
 142    GtkWidget *copy_item;
 143    GtkWidget *zoom_in_item;
 144    GtkWidget *zoom_out_item;
 145    GtkWidget *zoom_fixed_item;
 146    GtkWidget *zoom_fit_item;
 147    GtkWidget *grab_item;
 148    GtkWidget *grab_on_hover_item;
 149
 150    int nb_vcs;
 151    VirtualConsole vc[MAX_VCS];
 152
 153    GtkWidget *show_tabs_item;
 154    GtkWidget *untabify_item;
 155
 156    GtkWidget *vbox;
 157    GtkWidget *notebook;
 158    int button_mask;
 159    gboolean last_set;
 160    int last_x;
 161    int last_y;
 162    int grab_x_root;
 163    int grab_y_root;
 164    VirtualConsole *kbd_owner;
 165    VirtualConsole *ptr_owner;
 166
 167    gboolean full_screen;
 168
 169    GdkCursor *null_cursor;
 170    Notifier mouse_mode_notifier;
 171    gboolean free_scale;
 172
 173    bool external_pause_update;
 174
 175    bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
 176    bool has_evdev;
 177    bool ignore_keys;
 178};
 179
 180static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
 181static void gd_ungrab_pointer(GtkDisplayState *s);
 182static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
 183static void gd_ungrab_keyboard(GtkDisplayState *s);
 184
 185/** Utility Functions **/
 186
 187static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
 188{
 189    VirtualConsole *vc;
 190    gint i;
 191
 192    for (i = 0; i < s->nb_vcs; i++) {
 193        vc = &s->vc[i];
 194        if (gtk_check_menu_item_get_active
 195            (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
 196            return vc;
 197        }
 198    }
 199    return NULL;
 200}
 201
 202static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
 203{
 204    VirtualConsole *vc;
 205    gint i, p;
 206
 207    for (i = 0; i < s->nb_vcs; i++) {
 208        vc = &s->vc[i];
 209        p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
 210        if (p == page) {
 211            return vc;
 212        }
 213    }
 214    return NULL;
 215}
 216
 217static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
 218{
 219    gint page;
 220
 221    page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
 222    return gd_vc_find_by_page(s, page);
 223}
 224
 225static bool gd_is_grab_active(GtkDisplayState *s)
 226{
 227    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
 228}
 229
 230static bool gd_grab_on_hover(GtkDisplayState *s)
 231{
 232    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
 233}
 234
 235static void gd_update_cursor(VirtualConsole *vc)
 236{
 237    GtkDisplayState *s = vc->s;
 238    GdkWindow *window;
 239
 240    if (vc->type != GD_VC_GFX ||
 241        !qemu_console_is_graphic(vc->gfx.dcl.con)) {
 242        return;
 243    }
 244
 245    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
 246        return;
 247    }
 248
 249    window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
 250    if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) {
 251        gdk_window_set_cursor(window, s->null_cursor);
 252    } else {
 253        gdk_window_set_cursor(window, NULL);
 254    }
 255}
 256
 257static void gd_update_caption(GtkDisplayState *s)
 258{
 259    const char *status = "";
 260    gchar *prefix;
 261    gchar *title;
 262    const char *grab = "";
 263    bool is_paused = !runstate_is_running();
 264    int i;
 265
 266    if (qemu_name) {
 267        prefix = g_strdup_printf("QEMU (%s)", qemu_name);
 268    } else {
 269        prefix = g_strdup_printf("QEMU");
 270    }
 271
 272    if (s->ptr_owner != NULL &&
 273        s->ptr_owner->window == NULL) {
 274        grab = _(" - Press Ctrl+Alt+G to release grab");
 275    }
 276
 277    if (is_paused) {
 278        status = _(" [Paused]");
 279    }
 280    s->external_pause_update = true;
 281    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item),
 282                                   is_paused);
 283    s->external_pause_update = false;
 284
 285    title = g_strdup_printf("%s%s%s", prefix, status, grab);
 286    gtk_window_set_title(GTK_WINDOW(s->window), title);
 287    g_free(title);
 288
 289    for (i = 0; i < s->nb_vcs; i++) {
 290        VirtualConsole *vc = &s->vc[i];
 291
 292        if (!vc->window) {
 293            continue;
 294        }
 295        title = g_strdup_printf("%s: %s%s%s", prefix, vc->label,
 296                                vc == s->kbd_owner ? " +kbd" : "",
 297                                vc == s->ptr_owner ? " +ptr" : "");
 298        gtk_window_set_title(GTK_WINDOW(vc->window), title);
 299        g_free(title);
 300    }
 301
 302    g_free(prefix);
 303}
 304
 305static void gd_update_geometry_hints(VirtualConsole *vc)
 306{
 307    GtkDisplayState *s = vc->s;
 308    GdkWindowHints mask = 0;
 309    GdkGeometry geo = {};
 310    GtkWidget *geo_widget = NULL;
 311    GtkWindow *geo_window;
 312
 313    if (vc->type == GD_VC_GFX) {
 314        if (!vc->gfx.ds) {
 315            return;
 316        }
 317        if (s->free_scale) {
 318            geo.min_width  = surface_width(vc->gfx.ds) * VC_SCALE_MIN;
 319            geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN;
 320            mask |= GDK_HINT_MIN_SIZE;
 321        } else {
 322            geo.min_width  = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
 323            geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
 324            mask |= GDK_HINT_MIN_SIZE;
 325        }
 326        geo_widget = vc->gfx.drawing_area;
 327        gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height);
 328
 329#if defined(CONFIG_VTE)
 330    } else if (vc->type == GD_VC_VTE) {
 331        VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
 332        GtkBorder padding = { 0 };
 333
 334#if VTE_CHECK_VERSION(0, 37, 0)
 335        gtk_style_context_get_padding(
 336                gtk_widget_get_style_context(vc->vte.terminal),
 337                gtk_widget_get_state_flags(vc->vte.terminal),
 338                &padding);
 339#else
 340        {
 341            GtkBorder *ib = NULL;
 342            gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL);
 343            if (ib) {
 344                padding = *ib;
 345                gtk_border_free(ib);
 346            }
 347        }
 348#endif
 349
 350        geo.width_inc  = vte_terminal_get_char_width(term);
 351        geo.height_inc = vte_terminal_get_char_height(term);
 352        mask |= GDK_HINT_RESIZE_INC;
 353        geo.base_width  = geo.width_inc;
 354        geo.base_height = geo.height_inc;
 355        mask |= GDK_HINT_BASE_SIZE;
 356        geo.min_width  = geo.width_inc * VC_TERM_X_MIN;
 357        geo.min_height = geo.height_inc * VC_TERM_Y_MIN;
 358        mask |= GDK_HINT_MIN_SIZE;
 359
 360        geo.base_width  += padding.left + padding.right;
 361        geo.base_height += padding.top + padding.bottom;
 362        geo.min_width   += padding.left + padding.right;
 363        geo.min_height  += padding.top + padding.bottom;
 364        geo_widget = vc->vte.terminal;
 365#endif
 366    }
 367
 368    geo_window = GTK_WINDOW(vc->window ? vc->window : s->window);
 369    gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask);
 370}
 371
 372void gd_update_windowsize(VirtualConsole *vc)
 373{
 374    GtkDisplayState *s = vc->s;
 375
 376    gd_update_geometry_hints(vc);
 377
 378    if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) {
 379        gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window),
 380                          VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN);
 381    }
 382}
 383
 384static void gd_update_full_redraw(VirtualConsole *vc)
 385{
 386    GtkWidget *area = vc->gfx.drawing_area;
 387    int ww, wh;
 388    gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh);
 389#if defined(CONFIG_GTK_GL)
 390    if (vc->gfx.gls) {
 391        gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
 392        return;
 393    }
 394#endif
 395    gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
 396}
 397
 398static void gtk_release_modifiers(GtkDisplayState *s)
 399{
 400    VirtualConsole *vc = gd_vc_find_current(s);
 401    int i, keycode;
 402
 403    if (vc->type != GD_VC_GFX ||
 404        !qemu_console_is_graphic(vc->gfx.dcl.con)) {
 405        return;
 406    }
 407    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
 408        keycode = modifier_keycode[i];
 409        if (!s->modifier_pressed[i]) {
 410            continue;
 411        }
 412        qemu_input_event_send_key_number(vc->gfx.dcl.con, keycode, false);
 413        s->modifier_pressed[i] = false;
 414    }
 415}
 416
 417static void gd_widget_reparent(GtkWidget *from, GtkWidget *to,
 418                               GtkWidget *widget)
 419{
 420    g_object_ref(G_OBJECT(widget));
 421    gtk_container_remove(GTK_CONTAINER(from), widget);
 422    gtk_container_add(GTK_CONTAINER(to), widget);
 423    g_object_unref(G_OBJECT(widget));
 424}
 425
 426/** DisplayState Callbacks **/
 427
 428static void gd_update(DisplayChangeListener *dcl,
 429                      int x, int y, int w, int h)
 430{
 431    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 432    GdkWindow *win;
 433    int x1, x2, y1, y2;
 434    int mx, my;
 435    int fbw, fbh;
 436    int ww, wh;
 437
 438    trace_gd_update(vc->label, x, y, w, h);
 439
 440    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
 441        return;
 442    }
 443
 444    if (vc->gfx.convert) {
 445        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
 446                               NULL, vc->gfx.convert,
 447                               x, y, 0, 0, x, y, w, h);
 448    }
 449
 450    x1 = floor(x * vc->gfx.scale_x);
 451    y1 = floor(y * vc->gfx.scale_y);
 452
 453    x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
 454    y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
 455
 456    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
 457    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
 458
 459    win = gtk_widget_get_window(vc->gfx.drawing_area);
 460    if (!win) {
 461        return;
 462    }
 463    gdk_drawable_get_size(win, &ww, &wh);
 464
 465    mx = my = 0;
 466    if (ww > fbw) {
 467        mx = (ww - fbw) / 2;
 468    }
 469    if (wh > fbh) {
 470        my = (wh - fbh) / 2;
 471    }
 472
 473    gtk_widget_queue_draw_area(vc->gfx.drawing_area,
 474                               mx + x1, my + y1, (x2 - x1), (y2 - y1));
 475}
 476
 477static void gd_refresh(DisplayChangeListener *dcl)
 478{
 479    graphic_hw_update(dcl->con);
 480}
 481
 482#if GTK_CHECK_VERSION(3, 0, 0)
 483static GdkDevice *gd_get_pointer(GdkDisplay *dpy)
 484{
 485#if GTK_CHECK_VERSION(3, 20, 0)
 486    return gdk_seat_get_pointer(gdk_display_get_default_seat(dpy));
 487#else
 488    return gdk_device_manager_get_client_pointer(
 489        gdk_display_get_device_manager(dpy));
 490#endif
 491}
 492
 493static void gd_mouse_set(DisplayChangeListener *dcl,
 494                         int x, int y, int visible)
 495{
 496    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 497    GdkDisplay *dpy;
 498    gint x_root, y_root;
 499
 500    if (qemu_input_is_absolute()) {
 501        return;
 502    }
 503
 504    dpy = gtk_widget_get_display(vc->gfx.drawing_area);
 505    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
 506                               x, y, &x_root, &y_root);
 507    gdk_device_warp(gd_get_pointer(dpy),
 508                    gtk_widget_get_screen(vc->gfx.drawing_area),
 509                    x_root, y_root);
 510    vc->s->last_x = x;
 511    vc->s->last_y = y;
 512}
 513#else
 514static void gd_mouse_set(DisplayChangeListener *dcl,
 515                         int x, int y, int visible)
 516{
 517    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 518    gint x_root, y_root;
 519
 520    if (qemu_input_is_absolute()) {
 521        return;
 522    }
 523
 524    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
 525                               x, y, &x_root, &y_root);
 526    gdk_display_warp_pointer(gtk_widget_get_display(vc->gfx.drawing_area),
 527                             gtk_widget_get_screen(vc->gfx.drawing_area),
 528                             x_root, y_root);
 529}
 530#endif
 531
 532static void gd_cursor_define(DisplayChangeListener *dcl,
 533                             QEMUCursor *c)
 534{
 535    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 536    GdkPixbuf *pixbuf;
 537    GdkCursor *cursor;
 538
 539    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
 540        return;
 541    }
 542
 543    pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
 544                                      GDK_COLORSPACE_RGB, true, 8,
 545                                      c->width, c->height, c->width * 4,
 546                                      NULL, NULL);
 547    cursor = gdk_cursor_new_from_pixbuf
 548        (gtk_widget_get_display(vc->gfx.drawing_area),
 549         pixbuf, c->hot_x, c->hot_y);
 550    gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor);
 551    g_object_unref(pixbuf);
 552#if !GTK_CHECK_VERSION(3, 0, 0)
 553    gdk_cursor_unref(cursor);
 554#else
 555    g_object_unref(cursor);
 556#endif
 557}
 558
 559static void gd_switch(DisplayChangeListener *dcl,
 560                      DisplaySurface *surface)
 561{
 562    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
 563    bool resized = true;
 564
 565    trace_gd_switch(vc->label,
 566                    surface ? surface_width(surface)  : 0,
 567                    surface ? surface_height(surface) : 0);
 568
 569    if (vc->gfx.surface) {
 570        cairo_surface_destroy(vc->gfx.surface);
 571        vc->gfx.surface = NULL;
 572    }
 573    if (vc->gfx.convert) {
 574        pixman_image_unref(vc->gfx.convert);
 575        vc->gfx.convert = NULL;
 576    }
 577
 578    if (vc->gfx.ds && surface &&
 579        surface_width(vc->gfx.ds) == surface_width(surface) &&
 580        surface_height(vc->gfx.ds) == surface_height(surface)) {
 581        resized = false;
 582    }
 583    vc->gfx.ds = surface;
 584
 585    if (!surface) {
 586        return;
 587    }
 588
 589    if (surface->format == PIXMAN_x8r8g8b8) {
 590        /*
 591         * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24
 592         *
 593         * No need to convert, use surface directly.  Should be the
 594         * common case as this is qemu_default_pixelformat(32) too.
 595         */
 596        vc->gfx.surface = cairo_image_surface_create_for_data
 597            (surface_data(surface),
 598             CAIRO_FORMAT_RGB24,
 599             surface_width(surface),
 600             surface_height(surface),
 601             surface_stride(surface));
 602    } else {
 603        /* Must convert surface, use pixman to do it. */
 604        vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
 605                                                   surface_width(surface),
 606                                                   surface_height(surface),
 607                                                   NULL, 0);
 608        vc->gfx.surface = cairo_image_surface_create_for_data
 609            ((void *)pixman_image_get_data(vc->gfx.convert),
 610             CAIRO_FORMAT_RGB24,
 611             pixman_image_get_width(vc->gfx.convert),
 612             pixman_image_get_height(vc->gfx.convert),
 613             pixman_image_get_stride(vc->gfx.convert));
 614        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
 615                               NULL, vc->gfx.convert,
 616                               0, 0, 0, 0, 0, 0,
 617                               pixman_image_get_width(vc->gfx.convert),
 618                               pixman_image_get_height(vc->gfx.convert));
 619    }
 620
 621    if (resized) {
 622        gd_update_windowsize(vc);
 623    } else {
 624        gd_update_full_redraw(vc);
 625    }
 626}
 627
 628static const DisplayChangeListenerOps dcl_ops = {
 629    .dpy_name             = "gtk",
 630    .dpy_gfx_update       = gd_update,
 631    .dpy_gfx_switch       = gd_switch,
 632    .dpy_gfx_check_format = qemu_pixman_check_format,
 633    .dpy_refresh          = gd_refresh,
 634    .dpy_mouse_set        = gd_mouse_set,
 635    .dpy_cursor_define    = gd_cursor_define,
 636};
 637
 638
 639#if defined(CONFIG_OPENGL)
 640
 641/** DisplayState Callbacks (opengl version) **/
 642
 643#if defined(CONFIG_GTK_GL)
 644
 645static const DisplayChangeListenerOps dcl_gl_area_ops = {
 646    .dpy_name             = "gtk-egl",
 647    .dpy_gfx_update       = gd_gl_area_update,
 648    .dpy_gfx_switch       = gd_gl_area_switch,
 649    .dpy_gfx_check_format = console_gl_check_format,
 650    .dpy_refresh          = gd_gl_area_refresh,
 651    .dpy_mouse_set        = gd_mouse_set,
 652    .dpy_cursor_define    = gd_cursor_define,
 653
 654    .dpy_gl_ctx_create       = gd_gl_area_create_context,
 655    .dpy_gl_ctx_destroy      = gd_gl_area_destroy_context,
 656    .dpy_gl_ctx_make_current = gd_gl_area_make_current,
 657    .dpy_gl_ctx_get_current  = gd_gl_area_get_current_context,
 658    .dpy_gl_scanout          = gd_gl_area_scanout,
 659    .dpy_gl_update           = gd_gl_area_scanout_flush,
 660};
 661
 662#else
 663
 664static const DisplayChangeListenerOps dcl_egl_ops = {
 665    .dpy_name             = "gtk-egl",
 666    .dpy_gfx_update       = gd_egl_update,
 667    .dpy_gfx_switch       = gd_egl_switch,
 668    .dpy_gfx_check_format = console_gl_check_format,
 669    .dpy_refresh          = gd_egl_refresh,
 670    .dpy_mouse_set        = gd_mouse_set,
 671    .dpy_cursor_define    = gd_cursor_define,
 672
 673    .dpy_gl_ctx_create       = gd_egl_create_context,
 674    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
 675    .dpy_gl_ctx_make_current = gd_egl_make_current,
 676    .dpy_gl_ctx_get_current  = qemu_egl_get_current_context,
 677    .dpy_gl_scanout          = gd_egl_scanout,
 678    .dpy_gl_update           = gd_egl_scanout_flush,
 679};
 680
 681#endif /* CONFIG_GTK_GL */
 682#endif /* CONFIG_OPENGL */
 683
 684/** QEMU Events **/
 685
 686static void gd_change_runstate(void *opaque, int running, RunState state)
 687{
 688    GtkDisplayState *s = opaque;
 689
 690    gd_update_caption(s);
 691}
 692
 693static void gd_mouse_mode_change(Notifier *notify, void *data)
 694{
 695    GtkDisplayState *s;
 696    int i;
 697
 698    s = container_of(notify, GtkDisplayState, mouse_mode_notifier);
 699    /* release the grab at switching to absolute mode */
 700    if (qemu_input_is_absolute() && gd_is_grab_active(s)) {
 701        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
 702                                       FALSE);
 703    }
 704    for (i = 0; i < s->nb_vcs; i++) {
 705        VirtualConsole *vc = &s->vc[i];
 706        gd_update_cursor(vc);
 707    }
 708}
 709
 710/** GTK Events **/
 711
 712static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
 713                                void *opaque)
 714{
 715    GtkDisplayState *s = opaque;
 716    int i;
 717
 718    if (!no_quit) {
 719        for (i = 0; i < s->nb_vcs; i++) {
 720            if (s->vc[i].type != GD_VC_GFX) {
 721                continue;
 722            }
 723            unregister_displaychangelistener(&s->vc[i].gfx.dcl);
 724        }
 725        qmp_quit(NULL);
 726        return FALSE;
 727    }
 728
 729    return TRUE;
 730}
 731
 732static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
 733{
 734    QemuUIInfo info;
 735
 736    memset(&info, 0, sizeof(info));
 737    info.width = width;
 738    info.height = height;
 739    dpy_set_ui_info(vc->gfx.dcl.con, &info);
 740}
 741
 742#if defined(CONFIG_GTK_GL)
 743
 744static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context,
 745                                void *opaque)
 746{
 747    VirtualConsole *vc = opaque;
 748
 749    if (vc->gfx.gls) {
 750        gd_gl_area_draw(vc);
 751    }
 752    return TRUE;
 753}
 754
 755static void gd_resize_event(GtkGLArea *area,
 756                            gint width, gint height, gpointer *opaque)
 757{
 758    VirtualConsole *vc = (void *)opaque;
 759
 760    gd_set_ui_info(vc, width, height);
 761}
 762
 763#endif
 764
 765static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
 766{
 767    VirtualConsole *vc = opaque;
 768    GtkDisplayState *s = vc->s;
 769    int mx, my;
 770    int ww, wh;
 771    int fbw, fbh;
 772
 773#if defined(CONFIG_OPENGL)
 774    if (vc->gfx.gls) {
 775#if defined(CONFIG_GTK_GL)
 776        /* invoke render callback please */
 777        return FALSE;
 778#else
 779        gd_egl_draw(vc);
 780        return TRUE;
 781#endif
 782    }
 783#endif
 784
 785    if (!gtk_widget_get_realized(widget)) {
 786        return FALSE;
 787    }
 788    if (!vc->gfx.ds) {
 789        return FALSE;
 790    }
 791
 792    fbw = surface_width(vc->gfx.ds);
 793    fbh = surface_height(vc->gfx.ds);
 794
 795    gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
 796
 797    if (s->full_screen) {
 798        vc->gfx.scale_x = (double)ww / fbw;
 799        vc->gfx.scale_y = (double)wh / fbh;
 800    } else if (s->free_scale) {
 801        double sx, sy;
 802
 803        sx = (double)ww / fbw;
 804        sy = (double)wh / fbh;
 805
 806        vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
 807    }
 808
 809    fbw *= vc->gfx.scale_x;
 810    fbh *= vc->gfx.scale_y;
 811
 812    mx = my = 0;
 813    if (ww > fbw) {
 814        mx = (ww - fbw) / 2;
 815    }
 816    if (wh > fbh) {
 817        my = (wh - fbh) / 2;
 818    }
 819
 820    cairo_rectangle(cr, 0, 0, ww, wh);
 821
 822    /* Optionally cut out the inner area where the pixmap
 823       will be drawn. This avoids 'flashing' since we're
 824       not double-buffering. Note we're using the undocumented
 825       behaviour of drawing the rectangle from right to left
 826       to cut out the whole */
 827    cairo_rectangle(cr, mx + fbw, my,
 828                    -1 * fbw, fbh);
 829    cairo_fill(cr);
 830
 831    cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
 832    cairo_set_source_surface(cr, vc->gfx.surface,
 833                             mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
 834    cairo_paint(cr);
 835
 836    return TRUE;
 837}
 838
 839#if !GTK_CHECK_VERSION(3, 0, 0)
 840static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
 841                                void *opaque)
 842{
 843    cairo_t *cr;
 844    gboolean ret;
 845
 846    cr = gdk_cairo_create(gtk_widget_get_window(widget));
 847    cairo_rectangle(cr,
 848                    expose->area.x,
 849                    expose->area.y,
 850                    expose->area.width,
 851                    expose->area.height);
 852    cairo_clip(cr);
 853
 854    ret = gd_draw_event(widget, cr, opaque);
 855
 856    cairo_destroy(cr);
 857
 858    return ret;
 859}
 860#endif
 861
 862static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
 863                                void *opaque)
 864{
 865    VirtualConsole *vc = opaque;
 866    GtkDisplayState *s = vc->s;
 867    int x, y;
 868    int mx, my;
 869    int fbh, fbw;
 870    int ww, wh;
 871
 872    if (!vc->gfx.ds) {
 873        return TRUE;
 874    }
 875
 876    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
 877    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
 878
 879    gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
 880                          &ww, &wh);
 881
 882    mx = my = 0;
 883    if (ww > fbw) {
 884        mx = (ww - fbw) / 2;
 885    }
 886    if (wh > fbh) {
 887        my = (wh - fbh) / 2;
 888    }
 889
 890    x = (motion->x - mx) / vc->gfx.scale_x;
 891    y = (motion->y - my) / vc->gfx.scale_y;
 892
 893    if (qemu_input_is_absolute()) {
 894        if (x < 0 || y < 0 ||
 895            x >= surface_width(vc->gfx.ds) ||
 896            y >= surface_height(vc->gfx.ds)) {
 897            return TRUE;
 898        }
 899        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
 900                             surface_width(vc->gfx.ds));
 901        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
 902                             surface_height(vc->gfx.ds));
 903        qemu_input_event_sync();
 904    } else if (s->last_set && s->ptr_owner == vc) {
 905        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
 906        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
 907        qemu_input_event_sync();
 908    }
 909    s->last_x = x;
 910    s->last_y = y;
 911    s->last_set = TRUE;
 912
 913    if (!qemu_input_is_absolute() && s->ptr_owner == vc) {
 914        GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
 915        int x = (int)motion->x_root;
 916        int y = (int)motion->y_root;
 917
 918        /* In relative mode check to see if client pointer hit
 919         * one of the screen edges, and if so move it back by
 920         * 200 pixels. This is important because the pointer
 921         * in the server doesn't correspond 1-for-1, and so
 922         * may still be only half way across the screen. Without
 923         * this warp, the server pointer would thus appear to hit
 924         * an invisible wall */
 925        if (x == 0) {
 926            x += 200;
 927        }
 928        if (y == 0) {
 929            y += 200;
 930        }
 931        if (x == (gdk_screen_get_width(screen) - 1)) {
 932            x -= 200;
 933        }
 934        if (y == (gdk_screen_get_height(screen) - 1)) {
 935            y -= 200;
 936        }
 937
 938        if (x != (int)motion->x_root || y != (int)motion->y_root) {
 939#if GTK_CHECK_VERSION(3, 0, 0)
 940            GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
 941            gdk_device_warp(dev, screen, x, y);
 942#else
 943            GdkDisplay *display = gtk_widget_get_display(widget);
 944            gdk_display_warp_pointer(display, screen, x, y);
 945#endif
 946            s->last_set = FALSE;
 947            return FALSE;
 948        }
 949    }
 950    return TRUE;
 951}
 952
 953static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
 954                                void *opaque)
 955{
 956    VirtualConsole *vc = opaque;
 957    GtkDisplayState *s = vc->s;
 958    InputButton btn;
 959
 960    /* implicitly grab the input at the first click in the relative mode */
 961    if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
 962        !qemu_input_is_absolute() && s->ptr_owner != vc) {
 963        if (!vc->window) {
 964            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
 965                                           TRUE);
 966        } else {
 967            gd_grab_pointer(vc, "relative-mode-click");
 968        }
 969        return TRUE;
 970    }
 971
 972    if (button->button == 1) {
 973        btn = INPUT_BUTTON_LEFT;
 974    } else if (button->button == 2) {
 975        btn = INPUT_BUTTON_MIDDLE;
 976    } else if (button->button == 3) {
 977        btn = INPUT_BUTTON_RIGHT;
 978    } else {
 979        return TRUE;
 980    }
 981
 982    qemu_input_queue_btn(vc->gfx.dcl.con, btn,
 983                         button->type == GDK_BUTTON_PRESS);
 984    qemu_input_event_sync();
 985    return TRUE;
 986}
 987
 988static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
 989                                void *opaque)
 990{
 991    VirtualConsole *vc = opaque;
 992    InputButton btn;
 993
 994    if (scroll->direction == GDK_SCROLL_UP) {
 995        btn = INPUT_BUTTON_WHEEL_UP;
 996    } else if (scroll->direction == GDK_SCROLL_DOWN) {
 997        btn = INPUT_BUTTON_WHEEL_DOWN;
 998    } else {
 999        return TRUE;
1000    }
1001
1002    qemu_input_queue_btn(vc->gfx.dcl.con, btn, true);
1003    qemu_input_event_sync();
1004    qemu_input_queue_btn(vc->gfx.dcl.con, btn, false);
1005    qemu_input_event_sync();
1006    return TRUE;
1007}
1008
1009static int gd_map_keycode(GtkDisplayState *s, GdkDisplay *dpy, int gdk_keycode)
1010{
1011    int qemu_keycode;
1012
1013#ifdef GDK_WINDOWING_WIN32
1014    if (GDK_IS_WIN32_DISPLAY(dpy)) {
1015        qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC);
1016        switch (qemu_keycode) {
1017        case 103:   /* alt gr */
1018            qemu_keycode = 56 | SCANCODE_GREY;
1019            break;
1020        }
1021        return qemu_keycode;
1022    }
1023#endif
1024
1025    if (gdk_keycode < 9) {
1026        qemu_keycode = 0;
1027    } else if (gdk_keycode < 97) {
1028        qemu_keycode = gdk_keycode - 8;
1029#ifdef GDK_WINDOWING_X11
1030    } else if (GDK_IS_X11_DISPLAY(dpy) && gdk_keycode < 158) {
1031        if (s->has_evdev) {
1032            qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
1033        } else {
1034            qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97);
1035        }
1036#endif
1037    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
1038        qemu_keycode = 0x70;
1039    } else if (gdk_keycode == 211) { /* backslash */
1040        qemu_keycode = 0x73;
1041    } else {
1042        qemu_keycode = 0;
1043    }
1044
1045    return qemu_keycode;
1046}
1047
1048static gboolean gd_text_key_down(GtkWidget *widget,
1049                                 GdkEventKey *key, void *opaque)
1050{
1051    VirtualConsole *vc = opaque;
1052    QemuConsole *con = vc->gfx.dcl.con;
1053
1054    if (key->length) {
1055        kbd_put_string_console(con, key->string, key->length);
1056    } else {
1057        int num = gd_map_keycode(vc->s, gtk_widget_get_display(widget),
1058                                 key->hardware_keycode);
1059        int qcode = qemu_input_key_number_to_qcode(num);
1060        kbd_put_qcode_console(con, qcode);
1061    }
1062    return TRUE;
1063}
1064
1065static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
1066{
1067    VirtualConsole *vc = opaque;
1068    GtkDisplayState *s = vc->s;
1069    int gdk_keycode = key->hardware_keycode;
1070    int qemu_keycode;
1071    int i;
1072
1073    if (s->ignore_keys) {
1074        s->ignore_keys = (key->type == GDK_KEY_PRESS);
1075        return TRUE;
1076    }
1077
1078    if (key->keyval == GDK_KEY_Pause) {
1079        qemu_input_event_send_key_qcode(vc->gfx.dcl.con, Q_KEY_CODE_PAUSE,
1080                                        key->type == GDK_KEY_PRESS);
1081        return TRUE;
1082    }
1083
1084    qemu_keycode = gd_map_keycode(s, gtk_widget_get_display(widget),
1085                                  gdk_keycode);
1086
1087    trace_gd_key_event(vc->label, gdk_keycode, qemu_keycode,
1088                       (key->type == GDK_KEY_PRESS) ? "down" : "up");
1089
1090    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
1091        if (qemu_keycode == modifier_keycode[i]) {
1092            s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS);
1093        }
1094    }
1095
1096    qemu_input_event_send_key_number(vc->gfx.dcl.con, qemu_keycode,
1097                                     key->type == GDK_KEY_PRESS);
1098
1099    return TRUE;
1100}
1101
1102static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque)
1103{
1104    if (event->type == GDK_MOTION_NOTIFY) {
1105        return gd_motion_event(widget, &event->motion, opaque);
1106    }
1107    return FALSE;
1108}
1109
1110/** Window Menu Actions **/
1111
1112static void gd_menu_pause(GtkMenuItem *item, void *opaque)
1113{
1114    GtkDisplayState *s = opaque;
1115
1116    if (s->external_pause_update) {
1117        return;
1118    }
1119    if (runstate_is_running()) {
1120        qmp_stop(NULL);
1121    } else {
1122        qmp_cont(NULL);
1123    }
1124}
1125
1126static void gd_menu_reset(GtkMenuItem *item, void *opaque)
1127{
1128    qmp_system_reset(NULL);
1129}
1130
1131static void gd_menu_powerdown(GtkMenuItem *item, void *opaque)
1132{
1133    qmp_system_powerdown(NULL);
1134}
1135
1136static void gd_menu_quit(GtkMenuItem *item, void *opaque)
1137{
1138    qmp_quit(NULL);
1139}
1140
1141static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
1142{
1143    GtkDisplayState *s = opaque;
1144    VirtualConsole *vc = gd_vc_find_by_menu(s);
1145    GtkNotebook *nb = GTK_NOTEBOOK(s->notebook);
1146    gint page;
1147
1148    gtk_release_modifiers(s);
1149    if (vc) {
1150        page = gtk_notebook_page_num(nb, vc->tab_item);
1151        gtk_notebook_set_current_page(nb, page);
1152        gtk_widget_grab_focus(vc->focus);
1153    }
1154    s->ignore_keys = false;
1155}
1156
1157static void gd_accel_switch_vc(void *opaque)
1158{
1159    VirtualConsole *vc = opaque;
1160
1161    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
1162#if !GTK_CHECK_VERSION(3, 0, 0)
1163    /* GTK2 sends the accel key to the target console - ignore this until */
1164    vc->s->ignore_keys = true;
1165#endif
1166}
1167
1168static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
1169{
1170    GtkDisplayState *s = opaque;
1171    VirtualConsole *vc = gd_vc_find_current(s);
1172
1173    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
1174        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
1175    } else {
1176        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1177    }
1178    gd_update_windowsize(vc);
1179}
1180
1181static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
1182                                    void *opaque)
1183{
1184    VirtualConsole *vc = opaque;
1185    GtkDisplayState *s = vc->s;
1186
1187    gtk_widget_set_sensitive(vc->menu_item, true);
1188    gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
1189    gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
1190                                    vc->tab_item, vc->label);
1191    gtk_widget_destroy(vc->window);
1192    vc->window = NULL;
1193    return TRUE;
1194}
1195
1196static gboolean gd_win_grab(void *opaque)
1197{
1198    VirtualConsole *vc = opaque;
1199
1200    fprintf(stderr, "%s: %s\n", __func__, vc->label);
1201    if (vc->s->ptr_owner) {
1202        gd_ungrab_pointer(vc->s);
1203    } else {
1204        gd_grab_pointer(vc, "user-request-detached-tab");
1205    }
1206    return TRUE;
1207}
1208
1209static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
1210{
1211    GtkDisplayState *s = opaque;
1212    VirtualConsole *vc = gd_vc_find_current(s);
1213
1214    if (vc->type == GD_VC_GFX &&
1215        qemu_console_is_graphic(vc->gfx.dcl.con)) {
1216        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1217                                       FALSE);
1218    }
1219    if (!vc->window) {
1220        gtk_widget_set_sensitive(vc->menu_item, false);
1221        vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1222        gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
1223
1224        g_signal_connect(vc->window, "delete-event",
1225                         G_CALLBACK(gd_tab_window_close), vc);
1226        gtk_widget_show_all(vc->window);
1227
1228        if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
1229            GtkAccelGroup *ag = gtk_accel_group_new();
1230            gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
1231
1232            GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
1233                                               vc, NULL);
1234            gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
1235        }
1236
1237        gd_update_geometry_hints(vc);
1238        gd_update_caption(s);
1239    }
1240}
1241
1242static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
1243{
1244    GtkDisplayState *s = opaque;
1245    VirtualConsole *vc = gd_vc_find_current(s);
1246
1247    if (!s->full_screen) {
1248        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1249        gtk_widget_hide(s->menu_bar);
1250        if (vc->type == GD_VC_GFX) {
1251            gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
1252        }
1253        gtk_window_fullscreen(GTK_WINDOW(s->window));
1254        s->full_screen = TRUE;
1255    } else {
1256        gtk_window_unfullscreen(GTK_WINDOW(s->window));
1257        gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
1258        gtk_widget_show(s->menu_bar);
1259        s->full_screen = FALSE;
1260        if (vc->type == GD_VC_GFX) {
1261            vc->gfx.scale_x = 1.0;
1262            vc->gfx.scale_y = 1.0;
1263            gd_update_windowsize(vc);
1264        }
1265    }
1266
1267    gd_update_cursor(vc);
1268}
1269
1270static void gd_accel_full_screen(void *opaque)
1271{
1272    GtkDisplayState *s = opaque;
1273    gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
1274}
1275
1276static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
1277{
1278    GtkDisplayState *s = opaque;
1279    VirtualConsole *vc = gd_vc_find_current(s);
1280
1281    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1282                                   FALSE);
1283
1284    vc->gfx.scale_x += VC_SCALE_STEP;
1285    vc->gfx.scale_y += VC_SCALE_STEP;
1286
1287    gd_update_windowsize(vc);
1288}
1289
1290static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
1291{
1292    GtkDisplayState *s = opaque;
1293    VirtualConsole *vc = gd_vc_find_current(s);
1294
1295    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1296                                   FALSE);
1297
1298    vc->gfx.scale_x -= VC_SCALE_STEP;
1299    vc->gfx.scale_y -= VC_SCALE_STEP;
1300
1301    vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN);
1302    vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN);
1303
1304    gd_update_windowsize(vc);
1305}
1306
1307static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
1308{
1309    GtkDisplayState *s = opaque;
1310    VirtualConsole *vc = gd_vc_find_current(s);
1311
1312    vc->gfx.scale_x = 1.0;
1313    vc->gfx.scale_y = 1.0;
1314
1315    gd_update_windowsize(vc);
1316}
1317
1318static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
1319{
1320    GtkDisplayState *s = opaque;
1321    VirtualConsole *vc = gd_vc_find_current(s);
1322
1323    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
1324        s->free_scale = TRUE;
1325    } else {
1326        s->free_scale = FALSE;
1327        vc->gfx.scale_x = 1.0;
1328        vc->gfx.scale_y = 1.0;
1329    }
1330
1331    gd_update_windowsize(vc);
1332    gd_update_full_redraw(vc);
1333}
1334
1335#if GTK_CHECK_VERSION(3, 20, 0)
1336static void gd_grab_update(VirtualConsole *vc, bool kbd, bool ptr)
1337{
1338    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1339    GdkSeat *seat = gdk_display_get_default_seat(display);
1340    GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area);
1341    GdkSeatCapabilities caps = 0;
1342    GdkCursor *cursor = NULL;
1343
1344    if (kbd) {
1345        caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
1346    }
1347    if (ptr) {
1348        caps |= GDK_SEAT_CAPABILITY_ALL_POINTING;
1349        cursor = vc->s->null_cursor;
1350    }
1351
1352    if (caps) {
1353        gdk_seat_grab(seat, window, caps, false, cursor,
1354                      NULL, NULL, NULL);
1355    } else {
1356        gdk_seat_ungrab(seat);
1357    }
1358}
1359#elif GTK_CHECK_VERSION(3, 0, 0)
1360static void gd_grab_devices(VirtualConsole *vc, bool grab,
1361                            GdkInputSource source, GdkEventMask mask,
1362                            GdkCursor *cursor)
1363{
1364    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1365    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1366    GList *devs = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER);
1367    GList *tmp = devs;
1368
1369    for (tmp = devs; tmp; tmp = tmp->next) {
1370        GdkDevice *dev = tmp->data;
1371        if (gdk_device_get_source(dev) != source) {
1372            continue;
1373        }
1374        if (grab) {
1375            GdkWindow *win = gtk_widget_get_window(vc->gfx.drawing_area);
1376            gdk_device_grab(dev, win, GDK_OWNERSHIP_NONE, FALSE,
1377                            mask, cursor, GDK_CURRENT_TIME);
1378        } else {
1379            gdk_device_ungrab(dev, GDK_CURRENT_TIME);
1380        }
1381    }
1382    g_list_free(devs);
1383}
1384#endif
1385
1386static void gd_grab_keyboard(VirtualConsole *vc, const char *reason)
1387{
1388    if (vc->s->kbd_owner) {
1389        if (vc->s->kbd_owner == vc) {
1390            return;
1391        } else {
1392            gd_ungrab_keyboard(vc->s);
1393        }
1394    }
1395
1396#if GTK_CHECK_VERSION(3, 20, 0)
1397    gd_grab_update(vc, true, vc->s->ptr_owner == vc);
1398#elif GTK_CHECK_VERSION(3, 0, 0)
1399    gd_grab_devices(vc, true, GDK_SOURCE_KEYBOARD,
1400                   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
1401                   NULL);
1402#else
1403    gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1404                      FALSE,
1405                      GDK_CURRENT_TIME);
1406#endif
1407    vc->s->kbd_owner = vc;
1408    gd_update_caption(vc->s);
1409    trace_gd_grab(vc->label, "kbd", reason);
1410}
1411
1412static void gd_ungrab_keyboard(GtkDisplayState *s)
1413{
1414    VirtualConsole *vc = s->kbd_owner;
1415
1416    if (vc == NULL) {
1417        return;
1418    }
1419    s->kbd_owner = NULL;
1420
1421#if GTK_CHECK_VERSION(3, 20, 0)
1422    gd_grab_update(vc, false, vc->s->ptr_owner == vc);
1423#elif GTK_CHECK_VERSION(3, 0, 0)
1424    gd_grab_devices(vc, false, GDK_SOURCE_KEYBOARD, 0, NULL);
1425#else
1426    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
1427#endif
1428    gd_update_caption(s);
1429    trace_gd_ungrab(vc->label, "kbd");
1430}
1431
1432static void gd_grab_pointer(VirtualConsole *vc, const char *reason)
1433{
1434    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1435
1436    if (vc->s->ptr_owner) {
1437        if (vc->s->ptr_owner == vc) {
1438            return;
1439        } else {
1440            gd_ungrab_pointer(vc->s);
1441        }
1442    }
1443
1444#if GTK_CHECK_VERSION(3, 20, 0)
1445    gd_grab_update(vc, vc->s->kbd_owner == vc, true);
1446    gdk_device_get_position(gd_get_pointer(display),
1447                            NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
1448#elif GTK_CHECK_VERSION(3, 0, 0)
1449    gd_grab_devices(vc, true, GDK_SOURCE_MOUSE,
1450                    GDK_POINTER_MOTION_MASK |
1451                    GDK_BUTTON_PRESS_MASK |
1452                    GDK_BUTTON_RELEASE_MASK |
1453                    GDK_BUTTON_MOTION_MASK |
1454                    GDK_SCROLL_MASK,
1455                    vc->s->null_cursor);
1456    gdk_device_get_position(gd_get_pointer(display),
1457                            NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
1458#else
1459    gdk_pointer_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1460                     FALSE, /* All events to come to our window directly */
1461                     GDK_POINTER_MOTION_MASK |
1462                     GDK_BUTTON_PRESS_MASK |
1463                     GDK_BUTTON_RELEASE_MASK |
1464                     GDK_BUTTON_MOTION_MASK |
1465                     GDK_SCROLL_MASK,
1466                     NULL, /* Allow cursor to move over entire desktop */
1467                     vc->s->null_cursor,
1468                     GDK_CURRENT_TIME);
1469    gdk_display_get_pointer(display, NULL,
1470                            &vc->s->grab_x_root, &vc->s->grab_y_root, NULL);
1471#endif
1472    vc->s->ptr_owner = vc;
1473    gd_update_caption(vc->s);
1474    trace_gd_grab(vc->label, "ptr", reason);
1475}
1476
1477static void gd_ungrab_pointer(GtkDisplayState *s)
1478{
1479    VirtualConsole *vc = s->ptr_owner;
1480    GdkDisplay *display;
1481
1482    if (vc == NULL) {
1483        return;
1484    }
1485    s->ptr_owner = NULL;
1486
1487    display = gtk_widget_get_display(vc->gfx.drawing_area);
1488#if GTK_CHECK_VERSION(3, 20, 0)
1489    gd_grab_update(vc, vc->s->kbd_owner == vc, false);
1490    gdk_device_warp(gd_get_pointer(display),
1491                    gtk_widget_get_screen(vc->gfx.drawing_area),
1492                    vc->s->grab_x_root, vc->s->grab_y_root);
1493#elif GTK_CHECK_VERSION(3, 0, 0)
1494    gd_grab_devices(vc, false, GDK_SOURCE_MOUSE, 0, NULL);
1495    gdk_device_warp(gd_get_pointer(display),
1496                    gtk_widget_get_screen(vc->gfx.drawing_area),
1497                    vc->s->grab_x_root, vc->s->grab_y_root);
1498#else
1499    gdk_pointer_ungrab(GDK_CURRENT_TIME);
1500    gdk_display_warp_pointer(display,
1501                             gtk_widget_get_screen(vc->gfx.drawing_area),
1502                             vc->s->grab_x_root, vc->s->grab_y_root);
1503#endif
1504    gd_update_caption(s);
1505    trace_gd_ungrab(vc->label, "ptr");
1506}
1507
1508static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
1509{
1510    GtkDisplayState *s = opaque;
1511    VirtualConsole *vc = gd_vc_find_current(s);
1512
1513    if (gd_is_grab_active(s)) {
1514        gd_grab_keyboard(vc, "user-request-main-window");
1515        gd_grab_pointer(vc, "user-request-main-window");
1516    } else {
1517        gd_ungrab_keyboard(s);
1518        gd_ungrab_pointer(s);
1519    }
1520
1521    gd_update_cursor(vc);
1522}
1523
1524static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
1525                           gpointer data)
1526{
1527    GtkDisplayState *s = data;
1528    VirtualConsole *vc;
1529    gboolean on_vga;
1530
1531    if (!gtk_widget_get_realized(s->notebook)) {
1532        return;
1533    }
1534
1535#ifdef VTE_RESIZE_HACK
1536    vc = gd_vc_find_current(s);
1537    if (vc && vc->type == GD_VC_VTE) {
1538        gtk_widget_hide(vc->vte.terminal);
1539    }
1540#endif
1541    vc = gd_vc_find_by_page(s, arg2);
1542    if (!vc) {
1543        return;
1544    }
1545#ifdef VTE_RESIZE_HACK
1546    if (vc->type == GD_VC_VTE) {
1547        gtk_widget_show(vc->vte.terminal);
1548    }
1549#endif
1550    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
1551                                   TRUE);
1552    on_vga = (vc->type == GD_VC_GFX &&
1553              qemu_console_is_graphic(vc->gfx.dcl.con));
1554    if (!on_vga) {
1555        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1556                                       FALSE);
1557    } else if (s->full_screen) {
1558        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1559                                       TRUE);
1560    }
1561    gtk_widget_set_sensitive(s->grab_item, on_vga);
1562
1563    gd_update_windowsize(vc);
1564    gd_update_cursor(vc);
1565}
1566
1567static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
1568                               gpointer opaque)
1569{
1570    VirtualConsole *vc = opaque;
1571    GtkDisplayState *s = vc->s;
1572
1573    if (gd_grab_on_hover(s)) {
1574        gd_grab_keyboard(vc, "grab-on-hover");
1575    }
1576    return TRUE;
1577}
1578
1579static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
1580                               gpointer opaque)
1581{
1582    VirtualConsole *vc = opaque;
1583    GtkDisplayState *s = vc->s;
1584
1585    if (gd_grab_on_hover(s)) {
1586        gd_ungrab_keyboard(s);
1587    }
1588    return TRUE;
1589}
1590
1591static gboolean gd_focus_out_event(GtkWidget *widget,
1592                                   GdkEventCrossing *crossing, gpointer opaque)
1593{
1594    VirtualConsole *vc = opaque;
1595    GtkDisplayState *s = vc->s;
1596
1597    gtk_release_modifiers(s);
1598    return TRUE;
1599}
1600
1601static gboolean gd_configure(GtkWidget *widget,
1602                             GdkEventConfigure *cfg, gpointer opaque)
1603{
1604    VirtualConsole *vc = opaque;
1605
1606    gd_set_ui_info(vc, cfg->width, cfg->height);
1607    return FALSE;
1608}
1609
1610/** Virtual Console Callbacks **/
1611
1612static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
1613                               int idx, GSList *group, GtkWidget *view_menu)
1614{
1615    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
1616    gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
1617            HOTKEY_MODIFIERS, 0,
1618            g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
1619#if GTK_CHECK_VERSION(3, 8, 0)
1620    gtk_accel_label_set_accel(
1621            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
1622            GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
1623#endif
1624
1625    g_signal_connect(vc->menu_item, "activate",
1626                     G_CALLBACK(gd_menu_switch_vc), s);
1627    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
1628
1629    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
1630    return group;
1631}
1632
1633#if defined(CONFIG_VTE)
1634static void gd_menu_copy(GtkMenuItem *item, void *opaque)
1635{
1636    GtkDisplayState *s = opaque;
1637    VirtualConsole *vc = gd_vc_find_current(s);
1638
1639    vte_terminal_copy_clipboard(VTE_TERMINAL(vc->vte.terminal));
1640}
1641
1642static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
1643{
1644    VirtualConsole *vc = opaque;
1645
1646    if (gtk_adjustment_get_upper(adjustment) >
1647        gtk_adjustment_get_page_size(adjustment)) {
1648        gtk_widget_show(vc->vte.scrollbar);
1649    } else {
1650        gtk_widget_hide(vc->vte.scrollbar);
1651    }
1652}
1653
1654static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
1655{
1656    VirtualConsole *vc = chr->opaque;
1657
1658    vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
1659    return len;
1660}
1661
1662static void gd_vc_chr_set_echo(CharDriverState *chr, bool echo)
1663{
1664    VirtualConsole *vc = chr->opaque;
1665
1666    vc->vte.echo = echo;
1667}
1668
1669static int nb_vcs;
1670static CharDriverState *vcs[MAX_VCS];
1671
1672static CharDriverState *gd_vc_handler(ChardevVC *vc, Error **errp)
1673{
1674    ChardevCommon *common = qapi_ChardevVC_base(vc);
1675    CharDriverState *chr;
1676
1677    chr = qemu_chr_alloc(common, errp);
1678    if (!chr) {
1679        return NULL;
1680    }
1681
1682    chr->chr_write = gd_vc_chr_write;
1683    chr->chr_set_echo = gd_vc_chr_set_echo;
1684
1685    /* Temporary, until gd_vc_vte_init runs.  */
1686    chr->opaque = g_new0(VirtualConsole, 1);
1687
1688    /* defer OPENED events until our vc is fully initialized */
1689    chr->explicit_be_open = true;
1690
1691    vcs[nb_vcs++] = chr;
1692
1693    return chr;
1694}
1695
1696static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
1697                         gpointer user_data)
1698{
1699    VirtualConsole *vc = user_data;
1700
1701    if (vc->vte.echo) {
1702        VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
1703        int i;
1704        for (i = 0; i < size; i++) {
1705            uint8_t c = text[i];
1706            if (c >= 128 || isprint(c)) {
1707                /* 8-bit characters are considered printable.  */
1708                vte_terminal_feed(term, &text[i], 1);
1709            } else if (c == '\r' || c == '\n') {
1710                vte_terminal_feed(term, "\r\n", 2);
1711            } else {
1712                char ctrl[2] = { '^', 0};
1713                ctrl[1] = text[i] ^ 64;
1714                vte_terminal_feed(term, ctrl, 2);
1715            }
1716        }
1717    }
1718
1719    qemu_chr_be_write(vc->vte.chr, (uint8_t  *)text, (unsigned int)size);
1720    return TRUE;
1721}
1722
1723static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
1724                              CharDriverState *chr, int idx,
1725                              GSList *group, GtkWidget *view_menu)
1726{
1727    char buffer[32];
1728    GtkWidget *box;
1729    GtkWidget *scrollbar;
1730    GtkAdjustment *vadjustment;
1731    VirtualConsole *tmp_vc = chr->opaque;
1732
1733    vc->s = s;
1734    vc->vte.echo = tmp_vc->vte.echo;
1735
1736    vc->vte.chr = chr;
1737    chr->opaque = vc;
1738    g_free(tmp_vc);
1739
1740    snprintf(buffer, sizeof(buffer), "vc%d", idx);
1741    vc->label = g_strdup_printf("%s", vc->vte.chr->label
1742                                ? vc->vte.chr->label : buffer);
1743    group = gd_vc_menu_init(s, vc, idx, group, view_menu);
1744
1745    vc->vte.terminal = vte_terminal_new();
1746    g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
1747
1748    /* The documentation says that the default is UTF-8, but actually it is
1749     * 7-bit ASCII at least in VTE 0.38.
1750     */
1751#if VTE_CHECK_VERSION(0, 38, 0)
1752    vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8", NULL);
1753#else
1754    vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8");
1755#endif
1756
1757    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
1758    vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal),
1759                          VC_TERM_X_MIN, VC_TERM_Y_MIN);
1760
1761#if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0)
1762    vadjustment = gtk_scrollable_get_vadjustment
1763        (GTK_SCROLLABLE(vc->vte.terminal));
1764#else
1765    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
1766#endif
1767
1768#if GTK_CHECK_VERSION(3, 0, 0)
1769    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1770    scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
1771#else
1772    box = gtk_hbox_new(false, 2);
1773    scrollbar = gtk_vscrollbar_new(vadjustment);
1774#endif
1775
1776    gtk_box_pack_start(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
1777    gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
1778
1779    vc->vte.box = box;
1780    vc->vte.scrollbar = scrollbar;
1781
1782    g_signal_connect(vadjustment, "changed",
1783                     G_CALLBACK(gd_vc_adjustment_changed), vc);
1784
1785    vc->type = GD_VC_VTE;
1786    vc->tab_item = box;
1787    vc->focus = vc->vte.terminal;
1788    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
1789                             gtk_label_new(vc->label));
1790
1791    qemu_chr_be_generic_open(vc->vte.chr);
1792    if (vc->vte.chr->init) {
1793        vc->vte.chr->init(vc->vte.chr);
1794    }
1795
1796    return group;
1797}
1798
1799static void gd_vcs_init(GtkDisplayState *s, GSList *group,
1800                        GtkWidget *view_menu)
1801{
1802    int i;
1803
1804    for (i = 0; i < nb_vcs; i++) {
1805        VirtualConsole *vc = &s->vc[s->nb_vcs];
1806        group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
1807        s->nb_vcs++;
1808    }
1809}
1810#endif /* CONFIG_VTE */
1811
1812/** Window Creation **/
1813
1814static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
1815{
1816#if GTK_CHECK_VERSION(3, 0, 0)
1817    g_signal_connect(vc->gfx.drawing_area, "draw",
1818                     G_CALLBACK(gd_draw_event), vc);
1819#if defined(CONFIG_GTK_GL)
1820    if (display_opengl) {
1821        /* wire up GtkGlArea events */
1822        g_signal_connect(vc->gfx.drawing_area, "render",
1823                         G_CALLBACK(gd_render_event), vc);
1824        g_signal_connect(vc->gfx.drawing_area, "resize",
1825                         G_CALLBACK(gd_resize_event), vc);
1826    }
1827#endif
1828#else
1829    g_signal_connect(vc->gfx.drawing_area, "expose-event",
1830                     G_CALLBACK(gd_expose_event), vc);
1831#endif
1832    if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
1833        g_signal_connect(vc->gfx.drawing_area, "event",
1834                         G_CALLBACK(gd_event), vc);
1835        g_signal_connect(vc->gfx.drawing_area, "button-press-event",
1836                         G_CALLBACK(gd_button_event), vc);
1837        g_signal_connect(vc->gfx.drawing_area, "button-release-event",
1838                         G_CALLBACK(gd_button_event), vc);
1839        g_signal_connect(vc->gfx.drawing_area, "scroll-event",
1840                         G_CALLBACK(gd_scroll_event), vc);
1841        g_signal_connect(vc->gfx.drawing_area, "key-press-event",
1842                         G_CALLBACK(gd_key_event), vc);
1843        g_signal_connect(vc->gfx.drawing_area, "key-release-event",
1844                         G_CALLBACK(gd_key_event), vc);
1845
1846        g_signal_connect(vc->gfx.drawing_area, "enter-notify-event",
1847                         G_CALLBACK(gd_enter_event), vc);
1848        g_signal_connect(vc->gfx.drawing_area, "leave-notify-event",
1849                         G_CALLBACK(gd_leave_event), vc);
1850        g_signal_connect(vc->gfx.drawing_area, "focus-out-event",
1851                         G_CALLBACK(gd_focus_out_event), vc);
1852        g_signal_connect(vc->gfx.drawing_area, "configure-event",
1853                         G_CALLBACK(gd_configure), vc);
1854    } else {
1855        g_signal_connect(vc->gfx.drawing_area, "key-press-event",
1856                         G_CALLBACK(gd_text_key_down), vc);
1857    }
1858}
1859
1860static void gd_connect_signals(GtkDisplayState *s)
1861{
1862    g_signal_connect(s->show_tabs_item, "activate",
1863                     G_CALLBACK(gd_menu_show_tabs), s);
1864    g_signal_connect(s->untabify_item, "activate",
1865                     G_CALLBACK(gd_menu_untabify), s);
1866
1867    g_signal_connect(s->window, "delete-event",
1868                     G_CALLBACK(gd_window_close), s);
1869
1870    g_signal_connect(s->pause_item, "activate",
1871                     G_CALLBACK(gd_menu_pause), s);
1872    g_signal_connect(s->reset_item, "activate",
1873                     G_CALLBACK(gd_menu_reset), s);
1874    g_signal_connect(s->powerdown_item, "activate",
1875                     G_CALLBACK(gd_menu_powerdown), s);
1876    g_signal_connect(s->quit_item, "activate",
1877                     G_CALLBACK(gd_menu_quit), s);
1878#if defined(CONFIG_VTE)
1879    g_signal_connect(s->copy_item, "activate",
1880                     G_CALLBACK(gd_menu_copy), s);
1881#endif
1882    g_signal_connect(s->full_screen_item, "activate",
1883                     G_CALLBACK(gd_menu_full_screen), s);
1884    g_signal_connect(s->zoom_in_item, "activate",
1885                     G_CALLBACK(gd_menu_zoom_in), s);
1886    g_signal_connect(s->zoom_out_item, "activate",
1887                     G_CALLBACK(gd_menu_zoom_out), s);
1888    g_signal_connect(s->zoom_fixed_item, "activate",
1889                     G_CALLBACK(gd_menu_zoom_fixed), s);
1890    g_signal_connect(s->zoom_fit_item, "activate",
1891                     G_CALLBACK(gd_menu_zoom_fit), s);
1892    g_signal_connect(s->grab_item, "activate",
1893                     G_CALLBACK(gd_menu_grab_input), s);
1894    g_signal_connect(s->notebook, "switch-page",
1895                     G_CALLBACK(gd_change_page), s);
1896}
1897
1898static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
1899{
1900    GtkWidget *machine_menu;
1901    GtkWidget *separator;
1902
1903    machine_menu = gtk_menu_new();
1904    gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group);
1905
1906    s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
1907    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
1908
1909    separator = gtk_separator_menu_item_new();
1910    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
1911
1912    s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
1913    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
1914
1915    s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
1916    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
1917
1918    separator = gtk_separator_menu_item_new();
1919    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
1920
1921    s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
1922    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
1923                                 "<QEMU>/Machine/Quit");
1924    gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
1925                            GDK_KEY_q, HOTKEY_MODIFIERS);
1926    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
1927
1928    return machine_menu;
1929}
1930
1931static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
1932                              QemuConsole *con, int idx,
1933                              GSList *group, GtkWidget *view_menu)
1934{
1935    vc->label = qemu_console_get_label(con);
1936    vc->s = s;
1937    vc->gfx.scale_x = 1.0;
1938    vc->gfx.scale_y = 1.0;
1939
1940#if defined(CONFIG_OPENGL)
1941    if (display_opengl) {
1942#if defined(CONFIG_GTK_GL)
1943        vc->gfx.drawing_area = gtk_gl_area_new();
1944        vc->gfx.dcl.ops = &dcl_gl_area_ops;
1945#else
1946        vc->gfx.drawing_area = gtk_drawing_area_new();
1947        /*
1948         * gtk_widget_set_double_buffered() was deprecated in 3.14.
1949         * It is required for opengl rendering on X11 though.  A
1950         * proper replacement (native opengl support) is only
1951         * available in 3.16+.  Silence the warning if possible.
1952         */
1953#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
1954#pragma GCC diagnostic push
1955#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1956#endif
1957        gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
1958#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
1959#pragma GCC diagnostic pop
1960#endif
1961        vc->gfx.dcl.ops = &dcl_egl_ops;
1962#endif /* CONFIG_GTK_GL */
1963    } else
1964#endif
1965    {
1966        vc->gfx.drawing_area = gtk_drawing_area_new();
1967        vc->gfx.dcl.ops = &dcl_ops;
1968    }
1969
1970
1971    gtk_widget_add_events(vc->gfx.drawing_area,
1972                          GDK_POINTER_MOTION_MASK |
1973                          GDK_BUTTON_PRESS_MASK |
1974                          GDK_BUTTON_RELEASE_MASK |
1975                          GDK_BUTTON_MOTION_MASK |
1976                          GDK_ENTER_NOTIFY_MASK |
1977                          GDK_LEAVE_NOTIFY_MASK |
1978                          GDK_SCROLL_MASK |
1979                          GDK_KEY_PRESS_MASK);
1980    gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);
1981
1982    vc->type = GD_VC_GFX;
1983    vc->tab_item = vc->gfx.drawing_area;
1984    vc->focus = vc->gfx.drawing_area;
1985    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
1986                             vc->tab_item, gtk_label_new(vc->label));
1987
1988    vc->gfx.dcl.con = con;
1989    register_displaychangelistener(&vc->gfx.dcl);
1990
1991    gd_connect_vc_gfx_signals(vc);
1992    group = gd_vc_menu_init(s, vc, idx, group, view_menu);
1993
1994    if (dpy_ui_info_supported(vc->gfx.dcl.con)) {
1995        gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
1996        s->free_scale = true;
1997    }
1998
1999    return group;
2000}
2001
2002static GtkWidget *gd_create_menu_view(GtkDisplayState *s)
2003{
2004    GSList *group = NULL;
2005    GtkWidget *view_menu;
2006    GtkWidget *separator;
2007    QemuConsole *con;
2008    int vc;
2009
2010    view_menu = gtk_menu_new();
2011    gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
2012
2013    s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
2014
2015#if defined(CONFIG_VTE)
2016    s->copy_item = gtk_menu_item_new_with_mnemonic(_("_Copy"));
2017    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->copy_item);
2018#endif
2019
2020    gtk_accel_group_connect(s->accel_group, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
2021            g_cclosure_new_swap(G_CALLBACK(gd_accel_full_screen), s, NULL));
2022#if GTK_CHECK_VERSION(3, 8, 0)
2023    gtk_accel_label_set_accel(
2024            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))),
2025            GDK_KEY_f, HOTKEY_MODIFIERS);
2026#endif
2027    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
2028
2029    separator = gtk_separator_menu_item_new();
2030    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
2031
2032    s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
2033    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
2034                                 "<QEMU>/View/Zoom In");
2035    gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
2036                            HOTKEY_MODIFIERS);
2037    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
2038
2039    s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
2040    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
2041                                 "<QEMU>/View/Zoom Out");
2042    gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
2043                            HOTKEY_MODIFIERS);
2044    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
2045
2046    s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
2047    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
2048                                 "<QEMU>/View/Zoom Fixed");
2049    gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
2050                            HOTKEY_MODIFIERS);
2051    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
2052
2053    s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
2054    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
2055
2056    separator = gtk_separator_menu_item_new();
2057    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
2058
2059    s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
2060    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item);
2061
2062    s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
2063    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
2064                                 "<QEMU>/View/Grab Input");
2065    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
2066                            HOTKEY_MODIFIERS);
2067    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
2068
2069    separator = gtk_separator_menu_item_new();
2070    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
2071
2072    /* gfx */
2073    for (vc = 0;; vc++) {
2074        con = qemu_console_lookup_by_index(vc);
2075        if (!con) {
2076            break;
2077        }
2078        group = gd_vc_gfx_init(s, &s->vc[vc], con,
2079                               vc, group, view_menu);
2080        s->nb_vcs++;
2081    }
2082
2083#if defined(CONFIG_VTE)
2084    /* vte */
2085    gd_vcs_init(s, group, view_menu);
2086#endif
2087
2088    separator = gtk_separator_menu_item_new();
2089    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
2090
2091    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
2092    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
2093
2094    s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab"));
2095    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item);
2096
2097    return view_menu;
2098}
2099
2100static void gd_create_menus(GtkDisplayState *s)
2101{
2102    s->accel_group = gtk_accel_group_new();
2103    s->machine_menu = gd_create_menu_machine(s);
2104    s->view_menu = gd_create_menu_view(s);
2105
2106    s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
2107    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
2108                              s->machine_menu);
2109    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item);
2110
2111    s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
2112    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
2113    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
2114
2115    g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group);
2116    gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group);
2117}
2118
2119static void gd_set_keycode_type(GtkDisplayState *s)
2120{
2121#ifdef GDK_WINDOWING_X11
2122    GdkDisplay *display = gtk_widget_get_display(s->window);
2123    if (GDK_IS_X11_DISPLAY(display)) {
2124        Display *x11_display = gdk_x11_display_get_xdisplay(display);
2125        XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask,
2126                                         XkbUseCoreKbd);
2127        char *keycodes = NULL;
2128
2129        if (desc && desc->names) {
2130            keycodes = XGetAtomName(x11_display, desc->names->keycodes);
2131        }
2132        if (keycodes == NULL) {
2133            fprintf(stderr, "could not lookup keycode name\n");
2134        } else if (strstart(keycodes, "evdev", NULL)) {
2135            s->has_evdev = true;
2136        } else if (!strstart(keycodes, "xfree86", NULL)) {
2137            fprintf(stderr, "unknown keycodes `%s', please report to "
2138                    "qemu-devel@nongnu.org\n", keycodes);
2139        }
2140
2141        if (desc) {
2142            XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
2143        }
2144        if (keycodes) {
2145            XFree(keycodes);
2146        }
2147    }
2148#endif
2149}
2150
2151static gboolean gtkinit;
2152
2153void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover)
2154{
2155    GtkDisplayState *s = g_malloc0(sizeof(*s));
2156    char *filename;
2157    GdkDisplay *window_display;
2158
2159    if (!gtkinit) {
2160        fprintf(stderr, "gtk initialization failed\n");
2161        exit(1);
2162    }
2163
2164    s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
2165#if GTK_CHECK_VERSION(3, 2, 0)
2166    s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2167#else
2168    s->vbox = gtk_vbox_new(FALSE, 0);
2169#endif
2170    s->notebook = gtk_notebook_new();
2171    s->menu_bar = gtk_menu_bar_new();
2172
2173    s->free_scale = FALSE;
2174
2175    /* LC_MESSAGES only. See early_gtk_display_init() for details */
2176    setlocale(LC_MESSAGES, "");
2177    bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
2178    textdomain("qemu");
2179
2180    window_display = gtk_widget_get_display(s->window);
2181    s->null_cursor = gdk_cursor_new_for_display(window_display,
2182                                                GDK_BLANK_CURSOR);
2183
2184    s->mouse_mode_notifier.notify = gd_mouse_mode_change;
2185    qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
2186    qemu_add_vm_change_state_handler(gd_change_runstate, s);
2187
2188    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu_logo_no_text.svg");
2189    if (filename) {
2190        GError *error = NULL;
2191        GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
2192        if (pixbuf) {
2193            gtk_window_set_icon(GTK_WINDOW(s->window), pixbuf);
2194        } else {
2195            g_error_free(error);
2196        }
2197        g_free(filename);
2198    }
2199
2200    gd_create_menus(s);
2201
2202    gd_connect_signals(s);
2203
2204    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
2205    gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
2206
2207    gd_update_caption(s);
2208
2209    gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
2210    gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
2211
2212    gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
2213
2214    gtk_widget_show_all(s->window);
2215
2216#ifdef VTE_RESIZE_HACK
2217    {
2218        VirtualConsole *cur = gd_vc_find_current(s);
2219        if (cur) {
2220            int i;
2221
2222            for (i = 0; i < s->nb_vcs; i++) {
2223                VirtualConsole *vc = &s->vc[i];
2224                if (vc && vc->type == GD_VC_VTE && vc != cur) {
2225                    gtk_widget_hide(vc->vte.terminal);
2226                }
2227            }
2228            gd_update_windowsize(cur);
2229        }
2230    }
2231#endif
2232
2233    if (full_screen) {
2234        gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
2235    }
2236    if (grab_on_hover) {
2237        gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
2238    }
2239
2240    gd_set_keycode_type(s);
2241}
2242
2243void early_gtk_display_init(int opengl)
2244{
2245    /* The QEMU code relies on the assumption that it's always run in
2246     * the C locale. Therefore it is not prepared to deal with
2247     * operations that produce different results depending on the
2248     * locale, such as printf's formatting of decimal numbers, and
2249     * possibly others.
2250     *
2251     * Since GTK+ calls setlocale() by default -importing the locale
2252     * settings from the environment- we must prevent it from doing so
2253     * using gtk_disable_setlocale().
2254     *
2255     * QEMU's GTK+ UI, however, _does_ have translations for some of
2256     * the menu items. As a trade-off between a functionally correct
2257     * QEMU and a fully internationalized UI we support importing
2258     * LC_MESSAGES from the environment (see the setlocale() call
2259     * earlier in this file). This allows us to display translated
2260     * messages leaving everything else untouched.
2261     */
2262    gtk_disable_setlocale();
2263    gtkinit = gtk_init_check(NULL, NULL);
2264    if (!gtkinit) {
2265        /* don't exit yet, that'll break -help */
2266        return;
2267    }
2268
2269    switch (opengl) {
2270    case -1: /* default */
2271    case 0:  /* off */
2272        break;
2273    case 1: /* on */
2274#if defined(CONFIG_OPENGL)
2275#if defined(CONFIG_GTK_GL)
2276        gtk_gl_area_init();
2277#else
2278        gtk_egl_init();
2279#endif
2280#endif
2281        break;
2282    default:
2283        g_assert_not_reached();
2284        break;
2285    }
2286
2287#if defined(CONFIG_VTE)
2288    register_vc_handler(gd_vc_handler);
2289#endif
2290}
2291