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