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