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