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