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