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