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