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