]> git.proxmox.com Git - mirror_qemu.git/blame - ui/gtk.c
gtk: zap unused global_state
[mirror_qemu.git] / ui / gtk.c
CommitLineData
a4ccabcf
AL
1/*
2 * GTK UI
3 *
4 * Copyright IBM, Corp. 2012
5 *
6 * Authors:
7 * Anthony Liguori <aliguori@us.ibm.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
10 * See the COPYING file in the top-level directory.
11 *
12 * Portions from gtk-vnc:
13 *
14 * GTK VNC Widget
15 *
16 * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
17 * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
18 *
19 * This library is free software; you can redistribute it and/or
20 * modify it under the terms of the GNU Lesser General Public
21 * License as published by the Free Software Foundation; either
22 * version 2.0 of the License, or (at your option) any later version.
23 *
24 * This library is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * Lesser General Public License for more details.
28 *
29 * You should have received a copy of the GNU Lesser General Public
30 * License along with this library; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32 */
33
834574ea
AL
34#define GETTEXT_PACKAGE "qemu"
35#define LOCALEDIR "po"
36
2777ccc5
SW
37#ifdef _WIN32
38# define _WIN32_WINNT 0x0601 /* needed to get definition of MAPVK_VK_TO_VSC */
39#endif
40
c95e3080
KW
41#include "qemu-common.h"
42
43#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
44/* Work around an -Wstrict-prototypes warning in GTK headers */
e6f53fd5 45#pragma GCC diagnostic push
c95e3080
KW
46#pragma GCC diagnostic ignored "-Wstrict-prototypes"
47#endif
a4ccabcf 48#include <gtk/gtk.h>
c95e3080 49#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
e6f53fd5 50#pragma GCC diagnostic pop
c95e3080
KW
51#endif
52
53
a4ccabcf 54#include <gdk/gdkkeysyms.h>
834574ea 55#include <glib/gi18n.h>
3f58eade 56#include <locale.h>
bbbf9bfb 57#if defined(CONFIG_VTE)
a4ccabcf 58#include <vte/vte.h>
bbbf9bfb 59#endif
a4ccabcf
AL
60#include <math.h>
61
ef0dd982 62#include "trace.h"
a4ccabcf 63#include "ui/console.h"
af98ba92 64#include "ui/input.h"
a4ccabcf
AL
65#include "sysemu/sysemu.h"
66#include "qmp-commands.h"
67#include "x_keymap.h"
68#include "keymaps.h"
dccfcd0e 69#include "sysemu/char.h"
6a24ced5 70#include "qom/object.h"
3158a348
BR
71#ifndef _WIN32
72#include <gdk/gdkx.h>
73#include <X11/XKBlib.h>
74#endif
a4ccabcf 75
d861def3
AL
76#define MAX_VCS 10
77
bbbf9bfb
SW
78#if !defined(CONFIG_VTE)
79# define VTE_CHECK_VERSION(a, b, c) 0
80#endif
cba68834
DB
81
82/* Compatibility define to let us build on both Gtk2 and Gtk3 */
83#if GTK_CHECK_VERSION(3, 0, 0)
84static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh)
85{
86 *ww = gdk_window_get_width(w);
87 *wh = gdk_window_get_height(w);
88}
89#endif
90
ef6413a2
DB
91#if !GTK_CHECK_VERSION(2, 20, 0)
92#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget)
93#endif
94
bc0477c7
DB
95#ifndef GDK_KEY_0
96#define GDK_KEY_0 GDK_0
97#define GDK_KEY_1 GDK_1
98#define GDK_KEY_2 GDK_2
99#define GDK_KEY_f GDK_f
100#define GDK_KEY_g GDK_g
db1da1f2 101#define GDK_KEY_q GDK_q
bc0477c7
DB
102#define GDK_KEY_plus GDK_plus
103#define GDK_KEY_minus GDK_minus
104#endif
ef6413a2 105
b1e749c0 106#define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK)
b1e749c0 107
6db253ca
JK
108static const int modifier_keycode[] = {
109 /* shift, control, alt keys, meta keys, both left & right */
110 0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd,
111};
112
e3500d1f
GH
113typedef struct GtkDisplayState GtkDisplayState;
114
115typedef struct VirtualGfxConsole {
116 GtkWidget *drawing_area;
117 DisplayChangeListener dcl;
118 DisplaySurface *ds;
119 pixman_image_t *convert;
120 cairo_surface_t *surface;
121 double scale_x;
122 double scale_y;
123} VirtualGfxConsole;
124
bbbf9bfb 125#if defined(CONFIG_VTE)
271a25c0 126typedef struct VirtualVteConsole {
0fb20d1c
CR
127 GtkWidget *box;
128 GtkWidget *scrollbar;
ee5f31e4 129 GtkWidget *terminal;
a4ccabcf 130 CharDriverState *chr;
271a25c0
GH
131} VirtualVteConsole;
132#endif
133
e3500d1f
GH
134typedef enum VirtualConsoleType {
135 GD_VC_GFX,
136 GD_VC_VTE,
137} VirtualConsoleType;
138
271a25c0 139typedef struct VirtualConsole {
e3500d1f 140 GtkDisplayState *s;
cdeb7090
GH
141 char *label;
142 GtkWidget *window;
271a25c0
GH
143 GtkWidget *menu_item;
144 GtkWidget *tab_item;
e3500d1f 145 VirtualConsoleType type;
271a25c0 146 union {
e3500d1f 147 VirtualGfxConsole gfx;
271a25c0
GH
148#if defined(CONFIG_VTE)
149 VirtualVteConsole vte;
bbbf9bfb 150#endif
271a25c0 151 };
a4ccabcf
AL
152} VirtualConsole;
153
e3500d1f 154struct GtkDisplayState {
a4ccabcf
AL
155 GtkWidget *window;
156
157 GtkWidget *menu_bar;
158
73d4dc71
AL
159 GtkAccelGroup *accel_group;
160
30e8f22b
JK
161 GtkWidget *machine_menu_item;
162 GtkWidget *machine_menu;
163 GtkWidget *pause_item;
164 GtkWidget *reset_item;
165 GtkWidget *powerdown_item;
a4ccabcf
AL
166 GtkWidget *quit_item;
167
168 GtkWidget *view_menu_item;
169 GtkWidget *view_menu;
c6158483
AL
170 GtkWidget *full_screen_item;
171 GtkWidget *zoom_in_item;
172 GtkWidget *zoom_out_item;
173 GtkWidget *zoom_fixed_item;
174 GtkWidget *zoom_fit_item;
5104a1f6
AL
175 GtkWidget *grab_item;
176 GtkWidget *grab_on_hover_item;
a4ccabcf 177
d861def3
AL
178 int nb_vcs;
179 VirtualConsole vc[MAX_VCS];
180
a4ccabcf 181 GtkWidget *show_tabs_item;
cdeb7090 182 GtkWidget *untabify_item;
a4ccabcf
AL
183
184 GtkWidget *vbox;
185 GtkWidget *notebook;
a4ccabcf 186 int button_mask;
e61031cd 187 gboolean last_set;
a4ccabcf
AL
188 int last_x;
189 int last_y;
ecce1929
TI
190 int grab_x_root;
191 int grab_y_root;
4c638e2e
GH
192 VirtualConsole *kbd_owner;
193 VirtualConsole *ptr_owner;
a4ccabcf 194
c6158483 195 gboolean full_screen;
a4ccabcf
AL
196
197 GdkCursor *null_cursor;
198 Notifier mouse_mode_notifier;
c6158483 199 gboolean free_scale;
30e8f22b
JK
200
201 bool external_pause_update;
6db253ca
JK
202
203 bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
3158a348 204 bool has_evdev;
e3500d1f 205};
a4ccabcf 206
2884cf5b
GH
207static void gd_grab_pointer(VirtualConsole *vc);
208static void gd_ungrab_pointer(GtkDisplayState *s);
209
a4ccabcf
AL
210/** Utility Functions **/
211
271a25c0
GH
212static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
213{
214 VirtualConsole *vc;
215 gint i;
216
217 for (i = 0; i < s->nb_vcs; i++) {
218 vc = &s->vc[i];
219 if (gtk_check_menu_item_get_active
220 (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
221 return vc;
222 }
223 }
224 return NULL;
225}
226
227static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
228{
229 VirtualConsole *vc;
230 gint i, p;
231
232 for (i = 0; i < s->nb_vcs; i++) {
233 vc = &s->vc[i];
234 p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
235 if (p == page) {
236 return vc;
237 }
238 }
239 return NULL;
240}
241
e3500d1f
GH
242static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
243{
244 gint page;
245
246 page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
247 return gd_vc_find_by_page(s, page);
248}
249
5104a1f6
AL
250static bool gd_is_grab_active(GtkDisplayState *s)
251{
252 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
253}
254
255static bool gd_grab_on_hover(GtkDisplayState *s)
256{
257 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
258}
259
e3500d1f 260static void gd_update_cursor(VirtualConsole *vc)
a4ccabcf 261{
e3500d1f 262 GtkDisplayState *s = vc->s;
a4ccabcf 263 GdkWindow *window;
a4ccabcf 264
e3500d1f
GH
265 if (vc->type != GD_VC_GFX) {
266 return;
267 }
a4ccabcf 268
e3500d1f 269 window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
2884cf5b 270 if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) {
a4ccabcf
AL
271 gdk_window_set_cursor(window, s->null_cursor);
272 } else {
273 gdk_window_set_cursor(window, NULL);
274 }
275}
276
277static void gd_update_caption(GtkDisplayState *s)
278{
279 const char *status = "";
4eeaa3a8 280 gchar *prefix;
a4ccabcf 281 gchar *title;
5104a1f6 282 const char *grab = "";
30e8f22b 283 bool is_paused = !runstate_is_running();
4eeaa3a8
GH
284 int i;
285
286 if (qemu_name) {
287 prefix = g_strdup_printf("QEMU (%s)", qemu_name);
288 } else {
289 prefix = g_strdup_printf("QEMU");
290 }
5104a1f6 291
4eeaa3a8
GH
292 if (s->ptr_owner != NULL &&
293 s->ptr_owner->window == NULL) {
d8da9ee8 294 grab = _(" - Press Ctrl+Alt+G to release grab");
5104a1f6 295 }
a4ccabcf 296
30e8f22b 297 if (is_paused) {
d8da9ee8 298 status = _(" [Paused]");
a4ccabcf 299 }
30e8f22b
JK
300 s->external_pause_update = true;
301 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item),
302 is_paused);
303 s->external_pause_update = false;
a4ccabcf 304
4eeaa3a8 305 title = g_strdup_printf("%s%s%s", prefix, status, grab);
a4ccabcf 306 gtk_window_set_title(GTK_WINDOW(s->window), title);
a4ccabcf 307 g_free(title);
4eeaa3a8
GH
308
309 for (i = 0; i < s->nb_vcs; i++) {
310 VirtualConsole *vc = &s->vc[i];
311
312 if (!vc->window) {
313 continue;
314 }
315 title = g_strdup_printf("%s: %s%s%s", prefix, vc->label,
316 vc == s->kbd_owner ? " +kbd" : "",
317 vc == s->ptr_owner ? " +ptr" : "");
318 gtk_window_set_title(GTK_WINDOW(vc->window), title);
319 g_free(title);
320 }
321
322 g_free(prefix);
a4ccabcf
AL
323}
324
e3500d1f 325static void gd_update_windowsize(VirtualConsole *vc)
9d9801cf 326{
e3500d1f 327 GtkDisplayState *s = vc->s;
d3ef5750 328 double sx, sy;
e3500d1f 329
d3ef5750
GH
330 if (vc->type != GD_VC_GFX || s->full_screen) {
331 return;
332 }
9d9801cf 333
d3ef5750
GH
334 if (s->free_scale) {
335 sx = 1.0;
336 sy = 1.0;
337 } else {
338 sx = vc->gfx.scale_x;
339 sy = vc->gfx.scale_y;
9d9801cf 340 }
d3ef5750
GH
341 gtk_widget_set_size_request(vc->gfx.drawing_area,
342 surface_width(vc->gfx.ds) * sx,
343 surface_height(vc->gfx.ds) * sy);
aa0a55d4
GH
344 if (vc->window) {
345 gtk_window_resize(GTK_WINDOW(vc->window), 320, 240);
346 } else {
347 gtk_window_resize(GTK_WINDOW(s->window), 320, 240);
348 }
9d9801cf
GH
349}
350
e3500d1f 351static void gd_update_full_redraw(VirtualConsole *vc)
9d9801cf 352{
e3500d1f 353 GtkWidget *area = vc->gfx.drawing_area;
9d9801cf 354 int ww, wh;
e3500d1f
GH
355 gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh);
356 gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
9d9801cf
GH
357}
358
6db253ca
JK
359static void gtk_release_modifiers(GtkDisplayState *s)
360{
e3500d1f 361 VirtualConsole *vc = gd_vc_find_current(s);
6db253ca
JK
362 int i, keycode;
363
e3500d1f 364 if (vc->type != GD_VC_GFX) {
6db253ca
JK
365 return;
366 }
367 for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
368 keycode = modifier_keycode[i];
369 if (!s->modifier_pressed[i]) {
370 continue;
371 }
e3500d1f 372 qemu_input_event_send_key_number(vc->gfx.dcl.con, keycode, false);
6db253ca
JK
373 s->modifier_pressed[i] = false;
374 }
375}
376
a4ccabcf
AL
377/** DisplayState Callbacks **/
378
7c20b4a3 379static void gd_update(DisplayChangeListener *dcl,
bc2ed970 380 int x, int y, int w, int h)
a4ccabcf 381{
e3500d1f 382 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
a4ccabcf 383 int x1, x2, y1, y2;
c6158483
AL
384 int mx, my;
385 int fbw, fbh;
386 int ww, wh;
a4ccabcf 387
74444bc1 388 trace_gd_update(vc->label, x, y, w, h);
a4ccabcf 389
e3500d1f
GH
390 if (vc->gfx.convert) {
391 pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
392 NULL, vc->gfx.convert,
f0875536
GH
393 x, y, 0, 0, x, y, w, h);
394 }
395
e3500d1f
GH
396 x1 = floor(x * vc->gfx.scale_x);
397 y1 = floor(y * vc->gfx.scale_y);
a4ccabcf 398
e3500d1f
GH
399 x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
400 y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
a4ccabcf 401
e3500d1f
GH
402 fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
403 fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
c6158483 404
e3500d1f
GH
405 gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
406 &ww, &wh);
c6158483
AL
407
408 mx = my = 0;
409 if (ww > fbw) {
410 mx = (ww - fbw) / 2;
411 }
412 if (wh > fbh) {
413 my = (wh - fbh) / 2;
414 }
415
e3500d1f
GH
416 gtk_widget_queue_draw_area(vc->gfx.drawing_area,
417 mx + x1, my + y1, (x2 - x1), (y2 - y1));
a4ccabcf
AL
418}
419
bc2ed970 420static void gd_refresh(DisplayChangeListener *dcl)
a4ccabcf 421{
284d1c6b 422 graphic_hw_update(dcl->con);
a4ccabcf
AL
423}
424
b087143b
IM
425#if GTK_CHECK_VERSION(3, 0, 0)
426static void gd_mouse_set(DisplayChangeListener *dcl,
427 int x, int y, int visible)
428{
e3500d1f 429 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
b087143b
IM
430 GdkDisplay *dpy;
431 GdkDeviceManager *mgr;
432 gint x_root, y_root;
433
2bda6602
CR
434 if (qemu_input_is_absolute()) {
435 return;
436 }
437
e3500d1f 438 dpy = gtk_widget_get_display(vc->gfx.drawing_area);
b087143b 439 mgr = gdk_display_get_device_manager(dpy);
e3500d1f 440 gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
b087143b
IM
441 x, y, &x_root, &y_root);
442 gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
e3500d1f 443 gtk_widget_get_screen(vc->gfx.drawing_area),
298526fe 444 x_root, y_root);
b087143b
IM
445}
446#else
9697f5d2
GH
447static void gd_mouse_set(DisplayChangeListener *dcl,
448 int x, int y, int visible)
449{
e3500d1f 450 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
9697f5d2
GH
451 gint x_root, y_root;
452
2bda6602
CR
453 if (qemu_input_is_absolute()) {
454 return;
455 }
456
e3500d1f 457 gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
9697f5d2 458 x, y, &x_root, &y_root);
e3500d1f
GH
459 gdk_display_warp_pointer(gtk_widget_get_display(vc->gfx.drawing_area),
460 gtk_widget_get_screen(vc->gfx.drawing_area),
9697f5d2
GH
461 x_root, y_root);
462}
b087143b 463#endif
9697f5d2
GH
464
465static void gd_cursor_define(DisplayChangeListener *dcl,
466 QEMUCursor *c)
467{
e3500d1f 468 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
9697f5d2
GH
469 GdkPixbuf *pixbuf;
470 GdkCursor *cursor;
471
472 pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
473 GDK_COLORSPACE_RGB, true, 8,
474 c->width, c->height, c->width * 4,
475 NULL, NULL);
e3500d1f
GH
476 cursor = gdk_cursor_new_from_pixbuf
477 (gtk_widget_get_display(vc->gfx.drawing_area),
478 pixbuf, c->hot_x, c->hot_y);
479 gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor);
9697f5d2 480 g_object_unref(pixbuf);
030b4b7d 481#if !GTK_CHECK_VERSION(3, 0, 0)
17139240 482 gdk_cursor_unref(cursor);
030b4b7d
SW
483#else
484 g_object_unref(cursor);
485#endif
9697f5d2
GH
486}
487
c12aeb86 488static void gd_switch(DisplayChangeListener *dcl,
c12aeb86 489 DisplaySurface *surface)
a4ccabcf 490{
e3500d1f 491 VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
9d9801cf 492 bool resized = true;
a4ccabcf 493
74444bc1 494 trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
a4ccabcf 495
e3500d1f
GH
496 if (vc->gfx.surface) {
497 cairo_surface_destroy(vc->gfx.surface);
a4ccabcf
AL
498 }
499
e3500d1f
GH
500 if (vc->gfx.ds &&
501 surface_width(vc->gfx.ds) == surface_width(surface) &&
502 surface_height(vc->gfx.ds) == surface_height(surface)) {
9d9801cf
GH
503 resized = false;
504 }
e3500d1f 505 vc->gfx.ds = surface;
a4ccabcf 506
e3500d1f
GH
507 if (vc->gfx.convert) {
508 pixman_image_unref(vc->gfx.convert);
509 vc->gfx.convert = NULL;
f0875536 510 }
a4ccabcf 511
f0875536
GH
512 if (surface->format == PIXMAN_x8r8g8b8) {
513 /*
514 * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24
515 *
516 * No need to convert, use surface directly. Should be the
517 * common case as this is qemu_default_pixelformat(32) too.
518 */
e3500d1f 519 vc->gfx.surface = cairo_image_surface_create_for_data
f0875536
GH
520 (surface_data(surface),
521 CAIRO_FORMAT_RGB24,
522 surface_width(surface),
523 surface_height(surface),
524 surface_stride(surface));
525 } else {
526 /* Must convert surface, use pixman to do it. */
e3500d1f
GH
527 vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
528 surface_width(surface),
529 surface_height(surface),
530 NULL, 0);
531 vc->gfx.surface = cairo_image_surface_create_for_data
532 ((void *)pixman_image_get_data(vc->gfx.convert),
f0875536 533 CAIRO_FORMAT_RGB24,
e3500d1f
GH
534 pixman_image_get_width(vc->gfx.convert),
535 pixman_image_get_height(vc->gfx.convert),
536 pixman_image_get_stride(vc->gfx.convert));
537 pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
538 NULL, vc->gfx.convert,
f0875536 539 0, 0, 0, 0, 0, 0,
e3500d1f
GH
540 pixman_image_get_width(vc->gfx.convert),
541 pixman_image_get_height(vc->gfx.convert));
f0875536 542 }
c6158483 543
9d9801cf 544 if (resized) {
e3500d1f 545 gd_update_windowsize(vc);
9d9801cf 546 } else {
e3500d1f 547 gd_update_full_redraw(vc);
c6158483 548 }
a4ccabcf
AL
549}
550
551/** QEMU Events **/
552
553static void gd_change_runstate(void *opaque, int running, RunState state)
554{
555 GtkDisplayState *s = opaque;
556
557 gd_update_caption(s);
558}
559
560static void gd_mouse_mode_change(Notifier *notify, void *data)
561{
800b0e81 562 GtkDisplayState *s;
99623c90 563 int i;
800b0e81
TI
564
565 s = container_of(notify, GtkDisplayState, mouse_mode_notifier);
566 /* release the grab at switching to absolute mode */
567 if (qemu_input_is_absolute() && gd_is_grab_active(s)) {
568 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
569 FALSE);
570 }
99623c90
GH
571 for (i = 0; i < s->nb_vcs; i++) {
572 VirtualConsole *vc = &s->vc[i];
573 gd_update_cursor(vc);
574 }
a4ccabcf
AL
575}
576
577/** GTK Events **/
578
579static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
580 void *opaque)
581{
582 GtkDisplayState *s = opaque;
e3500d1f 583 int i;
a4ccabcf
AL
584
585 if (!no_quit) {
e3500d1f
GH
586 for (i = 0; i < s->nb_vcs; i++) {
587 if (s->vc[i].type != GD_VC_GFX) {
588 continue;
589 }
590 unregister_displaychangelistener(&s->vc[i].gfx.dcl);
591 }
a4ccabcf
AL
592 qmp_quit(NULL);
593 return FALSE;
594 }
595
596 return TRUE;
597}
598
599static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
600{
e3500d1f
GH
601 VirtualConsole *vc = opaque;
602 GtkDisplayState *s = vc->s;
c6158483 603 int mx, my;
a4ccabcf
AL
604 int ww, wh;
605 int fbw, fbh;
606
c6158483
AL
607 if (!gtk_widget_get_realized(widget)) {
608 return FALSE;
609 }
610
e3500d1f
GH
611 fbw = surface_width(vc->gfx.ds);
612 fbh = surface_height(vc->gfx.ds);
a4ccabcf
AL
613
614 gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
615
c6158483 616 if (s->full_screen) {
e3500d1f
GH
617 vc->gfx.scale_x = (double)ww / fbw;
618 vc->gfx.scale_y = (double)wh / fbh;
c6158483
AL
619 } else if (s->free_scale) {
620 double sx, sy;
621
622 sx = (double)ww / fbw;
623 sy = (double)wh / fbh;
624
e3500d1f 625 vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
a4ccabcf
AL
626 }
627
e3500d1f
GH
628 fbw *= vc->gfx.scale_x;
629 fbh *= vc->gfx.scale_y;
5104a1f6 630
c6158483
AL
631 mx = my = 0;
632 if (ww > fbw) {
633 mx = (ww - fbw) / 2;
634 }
635 if (wh > fbh) {
636 my = (wh - fbh) / 2;
637 }
638
639 cairo_rectangle(cr, 0, 0, ww, wh);
640
641 /* Optionally cut out the inner area where the pixmap
642 will be drawn. This avoids 'flashing' since we're
643 not double-buffering. Note we're using the undocumented
644 behaviour of drawing the rectangle from right to left
645 to cut out the whole */
646 cairo_rectangle(cr, mx + fbw, my,
647 -1 * fbw, fbh);
648 cairo_fill(cr);
649
e3500d1f
GH
650 cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
651 cairo_set_source_surface(cr, vc->gfx.surface,
652 mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
a4ccabcf
AL
653 cairo_paint(cr);
654
655 return TRUE;
656}
657
fe43bca8 658#if !GTK_CHECK_VERSION(3, 0, 0)
a4ccabcf
AL
659static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
660 void *opaque)
661{
662 cairo_t *cr;
663 gboolean ret;
664
665 cr = gdk_cairo_create(gtk_widget_get_window(widget));
666 cairo_rectangle(cr,
667 expose->area.x,
668 expose->area.y,
669 expose->area.width,
670 expose->area.height);
671 cairo_clip(cr);
672
673 ret = gd_draw_event(widget, cr, opaque);
674
675 cairo_destroy(cr);
676
677 return ret;
678}
fe43bca8 679#endif
a4ccabcf
AL
680
681static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
682 void *opaque)
683{
e3500d1f
GH
684 VirtualConsole *vc = opaque;
685 GtkDisplayState *s = vc->s;
a4ccabcf 686 int x, y;
c6158483
AL
687 int mx, my;
688 int fbh, fbw;
689 int ww, wh;
690
e3500d1f
GH
691 fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
692 fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
a4ccabcf 693
e3500d1f
GH
694 gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
695 &ww, &wh);
c6158483
AL
696
697 mx = my = 0;
698 if (ww > fbw) {
699 mx = (ww - fbw) / 2;
700 }
701 if (wh > fbh) {
702 my = (wh - fbh) / 2;
703 }
704
e3500d1f
GH
705 x = (motion->x - mx) / vc->gfx.scale_x;
706 y = (motion->y - my) / vc->gfx.scale_y;
c6158483 707
192f81bf 708 if (qemu_input_is_absolute()) {
e61031cd 709 if (x < 0 || y < 0 ||
e3500d1f
GH
710 x >= surface_width(vc->gfx.ds) ||
711 y >= surface_height(vc->gfx.ds)) {
e61031cd
TI
712 return TRUE;
713 }
e3500d1f
GH
714 qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
715 surface_width(vc->gfx.ds));
716 qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
717 surface_height(vc->gfx.ds));
192f81bf 718 qemu_input_event_sync();
2884cf5b 719 } else if (s->last_set && s->ptr_owner == vc) {
e3500d1f
GH
720 qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
721 qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
192f81bf 722 qemu_input_event_sync();
a4ccabcf 723 }
a4ccabcf
AL
724 s->last_x = x;
725 s->last_y = y;
e61031cd 726 s->last_set = TRUE;
a4ccabcf 727
2884cf5b 728 if (!qemu_input_is_absolute() && s->ptr_owner == vc) {
e3500d1f 729 GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
5104a1f6
AL
730 int x = (int)motion->x_root;
731 int y = (int)motion->y_root;
732
733 /* In relative mode check to see if client pointer hit
734 * one of the screen edges, and if so move it back by
735 * 200 pixels. This is important because the pointer
736 * in the server doesn't correspond 1-for-1, and so
737 * may still be only half way across the screen. Without
738 * this warp, the server pointer would thus appear to hit
739 * an invisible wall */
740 if (x == 0) {
741 x += 200;
742 }
743 if (y == 0) {
744 y += 200;
745 }
746 if (x == (gdk_screen_get_width(screen) - 1)) {
747 x -= 200;
748 }
749 if (y == (gdk_screen_get_height(screen) - 1)) {
750 y -= 200;
751 }
752
753 if (x != (int)motion->x_root || y != (int)motion->y_root) {
8906de76
DB
754#if GTK_CHECK_VERSION(3, 0, 0)
755 GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
756 gdk_device_warp(dev, screen, x, y);
757#else
758 GdkDisplay *display = gtk_widget_get_display(widget);
5104a1f6 759 gdk_display_warp_pointer(display, screen, x, y);
8906de76 760#endif
e61031cd 761 s->last_set = FALSE;
5104a1f6
AL
762 return FALSE;
763 }
764 }
a4ccabcf
AL
765 return TRUE;
766}
767
768static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
769 void *opaque)
770{
e3500d1f
GH
771 VirtualConsole *vc = opaque;
772 GtkDisplayState *s = vc->s;
192f81bf 773 InputButton btn;
a4ccabcf 774
800b0e81
TI
775 /* implicitly grab the input at the first click in the relative mode */
776 if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
2884cf5b
GH
777 !qemu_input_is_absolute() && s->ptr_owner != vc) {
778 gd_ungrab_pointer(s);
779 if (!vc->window) {
780 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
781 TRUE);
782 } else {
2884cf5b 783 gd_grab_pointer(vc);
2884cf5b
GH
784 gd_update_caption(s);
785 }
800b0e81
TI
786 return TRUE;
787 }
788
a4ccabcf 789 if (button->button == 1) {
192f81bf 790 btn = INPUT_BUTTON_LEFT;
a4ccabcf 791 } else if (button->button == 2) {
192f81bf 792 btn = INPUT_BUTTON_MIDDLE;
a4ccabcf 793 } else if (button->button == 3) {
192f81bf 794 btn = INPUT_BUTTON_RIGHT;
a4ccabcf 795 } else {
192f81bf 796 return TRUE;
a4ccabcf
AL
797 }
798
e3500d1f
GH
799 qemu_input_queue_btn(vc->gfx.dcl.con, btn,
800 button->type == GDK_BUTTON_PRESS);
192f81bf 801 qemu_input_event_sync();
a4ccabcf
AL
802 return TRUE;
803}
804
d58b9122
JK
805static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
806 void *opaque)
807{
e3500d1f 808 VirtualConsole *vc = opaque;
d58b9122
JK
809 InputButton btn;
810
811 if (scroll->direction == GDK_SCROLL_UP) {
812 btn = INPUT_BUTTON_WHEEL_UP;
813 } else if (scroll->direction == GDK_SCROLL_DOWN) {
814 btn = INPUT_BUTTON_WHEEL_DOWN;
815 } else {
816 return TRUE;
817 }
818
e3500d1f 819 qemu_input_queue_btn(vc->gfx.dcl.con, btn, true);
d58b9122 820 qemu_input_event_sync();
e3500d1f 821 qemu_input_queue_btn(vc->gfx.dcl.con, btn, false);
d58b9122
JK
822 qemu_input_event_sync();
823 return TRUE;
824}
825
a4ccabcf
AL
826static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
827{
e3500d1f
GH
828 VirtualConsole *vc = opaque;
829 GtkDisplayState *s = vc->s;
2777ccc5 830 int gdk_keycode = key->hardware_keycode;
6db253ca 831 int i;
a4ccabcf 832
2777ccc5
SW
833#ifdef _WIN32
834 UINT qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC);
835 switch (qemu_keycode) {
836 case 103: /* alt gr */
837 qemu_keycode = 56 | SCANCODE_GREY;
838 break;
839 }
840#else
841 int qemu_keycode;
a4ccabcf
AL
842
843 if (gdk_keycode < 9) {
844 qemu_keycode = 0;
845 } else if (gdk_keycode < 97) {
846 qemu_keycode = gdk_keycode - 8;
847 } else if (gdk_keycode < 158) {
3158a348
BR
848 if (s->has_evdev) {
849 qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
850 } else {
851 qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97);
852 }
a4ccabcf
AL
853 } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
854 qemu_keycode = 0x70;
855 } else if (gdk_keycode == 211) { /* backslash */
856 qemu_keycode = 0x73;
857 } else {
858 qemu_keycode = 0;
859 }
2777ccc5 860#endif
a4ccabcf 861
74444bc1 862 trace_gd_key_event(vc->label, gdk_keycode, qemu_keycode,
ef0dd982 863 (key->type == GDK_KEY_PRESS) ? "down" : "up");
a4ccabcf 864
6db253ca
JK
865 for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
866 if (qemu_keycode == modifier_keycode[i]) {
867 s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS);
868 }
869 }
870
e3500d1f 871 qemu_input_event_send_key_number(vc->gfx.dcl.con, qemu_keycode,
af98ba92 872 key->type == GDK_KEY_PRESS);
a4ccabcf
AL
873
874 return TRUE;
875}
876
0d0e044d
TI
877static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque)
878{
879 if (event->type == GDK_MOTION_NOTIFY) {
880 return gd_motion_event(widget, &event->motion, opaque);
881 }
882 return FALSE;
883}
884
a4ccabcf
AL
885/** Window Menu Actions **/
886
30e8f22b
JK
887static void gd_menu_pause(GtkMenuItem *item, void *opaque)
888{
889 GtkDisplayState *s = opaque;
890
891 if (s->external_pause_update) {
892 return;
893 }
894 if (runstate_is_running()) {
895 qmp_stop(NULL);
896 } else {
897 qmp_cont(NULL);
898 }
899}
900
901static void gd_menu_reset(GtkMenuItem *item, void *opaque)
902{
903 qmp_system_reset(NULL);
904}
905
906static void gd_menu_powerdown(GtkMenuItem *item, void *opaque)
907{
908 qmp_system_powerdown(NULL);
909}
910
a4ccabcf
AL
911static void gd_menu_quit(GtkMenuItem *item, void *opaque)
912{
913 qmp_quit(NULL);
914}
915
916static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
917{
918 GtkDisplayState *s = opaque;
e3500d1f 919 VirtualConsole *vc = gd_vc_find_by_menu(s);
832189c9 920 gint page;
a4ccabcf 921
e3500d1f
GH
922 gtk_release_modifiers(s);
923 if (vc) {
832189c9 924 page = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook),
e3500d1f 925 vc->tab_item);
832189c9 926 gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), page);
a4ccabcf
AL
927 }
928}
929
930static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
931{
932 GtkDisplayState *s = opaque;
933
934 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
935 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
936 } else {
937 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
938 }
939}
940
cdeb7090
GH
941static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
942 void *opaque)
943{
944 VirtualConsole *vc = opaque;
945 GtkDisplayState *s = vc->s;
946
947 gtk_widget_set_sensitive(vc->menu_item, true);
948 gtk_widget_reparent(vc->tab_item, s->notebook);
949 gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
950 vc->tab_item, vc->label);
951 gtk_widget_destroy(vc->window);
952 vc->window = NULL;
953 return TRUE;
954}
955
0c77a37f
GH
956static gboolean gd_win_grab(void *opaque)
957{
958 VirtualConsole *vc = opaque;
959
960 fprintf(stderr, "%s: %s\n", __func__, vc->label);
961 if (vc->s->ptr_owner) {
962 gd_ungrab_pointer(vc->s);
963 } else {
964 gd_grab_pointer(vc);
965 }
966 gd_update_caption(vc->s);
967 return TRUE;
968}
969
cdeb7090
GH
970static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
971{
972 GtkDisplayState *s = opaque;
973 VirtualConsole *vc = gd_vc_find_current(s);
cdeb7090
GH
974
975 if (vc->type == GD_VC_GFX) {
aa0a55d4
GH
976 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
977 FALSE);
cdeb7090
GH
978 }
979 if (!vc->window) {
980 gtk_widget_set_sensitive(vc->menu_item, false);
981 vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
982 gtk_widget_reparent(vc->tab_item, vc->window);
983
cdeb7090
GH
984 g_signal_connect(vc->window, "delete-event",
985 G_CALLBACK(gd_tab_window_close), vc);
986 gtk_widget_show_all(vc->window);
4eeaa3a8 987
0c77a37f
GH
988 GtkAccelGroup *ag = gtk_accel_group_new();
989 gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
990
991 GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab), vc, NULL);
992 gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
993
994 fprintf(stderr, "%s: %p\n", __func__, vc);
4eeaa3a8 995 gd_update_caption(s);
cdeb7090
GH
996 }
997}
998
c6158483
AL
999static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
1000{
1001 GtkDisplayState *s = opaque;
e3500d1f 1002 VirtualConsole *vc = gd_vc_find_current(s);
c6158483 1003
10409282 1004 if (!s->full_screen) {
c6158483
AL
1005 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1006 gtk_widget_set_size_request(s->menu_bar, 0, 0);
e3500d1f
GH
1007 if (vc->type == GD_VC_GFX) {
1008 gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
1009 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1010 TRUE);
c6158483 1011 }
e3500d1f 1012 gtk_window_fullscreen(GTK_WINDOW(s->window));
c6158483
AL
1013 s->full_screen = TRUE;
1014 } else {
1015 gtk_window_unfullscreen(GTK_WINDOW(s->window));
1016 gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
1017 gtk_widget_set_size_request(s->menu_bar, -1, -1);
c6158483 1018 s->full_screen = FALSE;
e3500d1f
GH
1019 if (vc->type == GD_VC_GFX) {
1020 vc->gfx.scale_x = 1.0;
1021 vc->gfx.scale_y = 1.0;
1022 gtk_widget_set_size_request(vc->gfx.drawing_area,
1023 surface_width(vc->gfx.ds),
1024 surface_height(vc->gfx.ds));
1025 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1026 FALSE);
1027 }
c6158483
AL
1028 }
1029
e3500d1f 1030 gd_update_cursor(vc);
c6158483
AL
1031}
1032
1033static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
1034{
1035 GtkDisplayState *s = opaque;
e3500d1f 1036 VirtualConsole *vc = gd_vc_find_current(s);
c6158483
AL
1037
1038 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1039 FALSE);
1040
e3500d1f
GH
1041 vc->gfx.scale_x += .25;
1042 vc->gfx.scale_y += .25;
c6158483 1043
e3500d1f 1044 gd_update_windowsize(vc);
c6158483
AL
1045}
1046
1047static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
1048{
1049 GtkDisplayState *s = opaque;
e3500d1f 1050 VirtualConsole *vc = gd_vc_find_current(s);
c6158483
AL
1051
1052 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
1053 FALSE);
1054
e3500d1f
GH
1055 vc->gfx.scale_x -= .25;
1056 vc->gfx.scale_y -= .25;
c6158483 1057
e3500d1f
GH
1058 vc->gfx.scale_x = MAX(vc->gfx.scale_x, .25);
1059 vc->gfx.scale_y = MAX(vc->gfx.scale_y, .25);
c6158483 1060
e3500d1f 1061 gd_update_windowsize(vc);
c6158483
AL
1062}
1063
1064static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
1065{
1066 GtkDisplayState *s = opaque;
e3500d1f 1067 VirtualConsole *vc = gd_vc_find_current(s);
c6158483 1068
e3500d1f
GH
1069 vc->gfx.scale_x = 1.0;
1070 vc->gfx.scale_y = 1.0;
c6158483 1071
e3500d1f 1072 gd_update_windowsize(vc);
c6158483
AL
1073}
1074
1075static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
1076{
1077 GtkDisplayState *s = opaque;
e3500d1f 1078 VirtualConsole *vc = gd_vc_find_current(s);
c6158483
AL
1079
1080 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
1081 s->free_scale = TRUE;
1082 } else {
1083 s->free_scale = FALSE;
e3500d1f
GH
1084 vc->gfx.scale_x = 1.0;
1085 vc->gfx.scale_y = 1.0;
1086 gd_update_windowsize(vc);
c6158483
AL
1087 }
1088
e3500d1f 1089 gd_update_full_redraw(vc);
c6158483
AL
1090}
1091
e3500d1f 1092static void gd_grab_keyboard(VirtualConsole *vc)
5104a1f6 1093{
655199da 1094#if GTK_CHECK_VERSION(3, 0, 0)
e3500d1f 1095 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
655199da
DB
1096 GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1097 GList *devices = gdk_device_manager_list_devices(mgr,
1098 GDK_DEVICE_TYPE_MASTER);
1099 GList *tmp = devices;
1100 while (tmp) {
1101 GdkDevice *dev = tmp->data;
1102 if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
1103 gdk_device_grab(dev,
e3500d1f 1104 gtk_widget_get_window(vc->gfx.drawing_area),
655199da
DB
1105 GDK_OWNERSHIP_NONE,
1106 FALSE,
1107 GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
1108 NULL,
1109 GDK_CURRENT_TIME);
1110 }
1111 tmp = tmp->next;
1112 }
1113 g_list_free(devices);
1114#else
e3500d1f 1115 gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area),
5104a1f6
AL
1116 FALSE,
1117 GDK_CURRENT_TIME);
655199da 1118#endif
4c638e2e 1119 vc->s->kbd_owner = vc;
1c856da5 1120 trace_gd_grab(vc->label, "kbd", true);
5104a1f6
AL
1121}
1122
4c638e2e 1123static void gd_ungrab_keyboard(GtkDisplayState *s)
5104a1f6 1124{
4c638e2e
GH
1125 VirtualConsole *vc = s->kbd_owner;
1126
1127 if (vc == NULL) {
1128 return;
1129 }
1130 s->kbd_owner = NULL;
1131
655199da 1132#if GTK_CHECK_VERSION(3, 0, 0)
e3500d1f 1133 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
655199da
DB
1134 GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1135 GList *devices = gdk_device_manager_list_devices(mgr,
1136 GDK_DEVICE_TYPE_MASTER);
1137 GList *tmp = devices;
1138 while (tmp) {
1139 GdkDevice *dev = tmp->data;
1140 if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
1141 gdk_device_ungrab(dev,
1142 GDK_CURRENT_TIME);
1143 }
1144 tmp = tmp->next;
1145 }
1146 g_list_free(devices);
1147#else
5104a1f6 1148 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
655199da 1149#endif
1c856da5 1150 trace_gd_grab(vc->label, "kbd", false);
5104a1f6
AL
1151}
1152
e3500d1f 1153static void gd_grab_pointer(VirtualConsole *vc)
2a05485d 1154{
e3500d1f 1155 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
ecce1929 1156#if GTK_CHECK_VERSION(3, 0, 0)
2a05485d
DB
1157 GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1158 GList *devices = gdk_device_manager_list_devices(mgr,
1159 GDK_DEVICE_TYPE_MASTER);
1160 GList *tmp = devices;
1161 while (tmp) {
1162 GdkDevice *dev = tmp->data;
1163 if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
1164 gdk_device_grab(dev,
e3500d1f 1165 gtk_widget_get_window(vc->gfx.drawing_area),
2a05485d
DB
1166 GDK_OWNERSHIP_NONE,
1167 FALSE, /* All events to come to our
1168 window directly */
1169 GDK_POINTER_MOTION_MASK |
1170 GDK_BUTTON_PRESS_MASK |
1171 GDK_BUTTON_RELEASE_MASK |
1172 GDK_BUTTON_MOTION_MASK |
1173 GDK_SCROLL_MASK,
e3500d1f 1174 vc->s->null_cursor,
2a05485d
DB
1175 GDK_CURRENT_TIME);
1176 }
1177 tmp = tmp->next;
1178 }
1179 g_list_free(devices);
ecce1929 1180 gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr),
e3500d1f 1181 NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
2a05485d 1182#else
e3500d1f 1183 gdk_pointer_grab(gtk_widget_get_window(vc->gfx.drawing_area),
2a05485d
DB
1184 FALSE, /* All events to come to our window directly */
1185 GDK_POINTER_MOTION_MASK |
1186 GDK_BUTTON_PRESS_MASK |
1187 GDK_BUTTON_RELEASE_MASK |
1188 GDK_BUTTON_MOTION_MASK |
1189 GDK_SCROLL_MASK,
1190 NULL, /* Allow cursor to move over entire desktop */
e3500d1f 1191 vc->s->null_cursor,
2a05485d 1192 GDK_CURRENT_TIME);
ecce1929 1193 gdk_display_get_pointer(display, NULL,
e3500d1f 1194 &vc->s->grab_x_root, &vc->s->grab_y_root, NULL);
2a05485d 1195#endif
4c638e2e 1196 vc->s->ptr_owner = vc;
1c856da5 1197 trace_gd_grab(vc->label, "ptr", true);
2a05485d
DB
1198}
1199
4c638e2e 1200static void gd_ungrab_pointer(GtkDisplayState *s)
2a05485d 1201{
4c638e2e
GH
1202 VirtualConsole *vc = s->ptr_owner;
1203
1204 if (vc == NULL) {
1205 return;
1206 }
1207 s->ptr_owner = NULL;
1208
e3500d1f 1209 GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
ecce1929 1210#if GTK_CHECK_VERSION(3, 0, 0)
2a05485d
DB
1211 GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1212 GList *devices = gdk_device_manager_list_devices(mgr,
1213 GDK_DEVICE_TYPE_MASTER);
1214 GList *tmp = devices;
1215 while (tmp) {
1216 GdkDevice *dev = tmp->data;
1217 if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
1218 gdk_device_ungrab(dev,
1219 GDK_CURRENT_TIME);
1220 }
1221 tmp = tmp->next;
1222 }
1223 g_list_free(devices);
ecce1929 1224 gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
e3500d1f
GH
1225 gtk_widget_get_screen(vc->gfx.drawing_area),
1226 vc->s->grab_x_root, vc->s->grab_y_root);
2a05485d
DB
1227#else
1228 gdk_pointer_ungrab(GDK_CURRENT_TIME);
ecce1929 1229 gdk_display_warp_pointer(display,
e3500d1f
GH
1230 gtk_widget_get_screen(vc->gfx.drawing_area),
1231 vc->s->grab_x_root, vc->s->grab_y_root);
2a05485d 1232#endif
1c856da5 1233 trace_gd_grab(vc->label, "ptr", false);
2a05485d
DB
1234}
1235
5104a1f6
AL
1236static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
1237{
1238 GtkDisplayState *s = opaque;
e3500d1f 1239 VirtualConsole *vc = gd_vc_find_current(s);
5104a1f6
AL
1240
1241 if (gd_is_grab_active(s)) {
746b8670
GH
1242 if (!gd_grab_on_hover(s)) {
1243 gd_grab_keyboard(vc);
1244 }
e3500d1f 1245 gd_grab_pointer(vc);
5104a1f6 1246 } else {
4c638e2e
GH
1247 gd_ungrab_keyboard(s);
1248 gd_ungrab_pointer(s);
5104a1f6
AL
1249 }
1250
1251 gd_update_caption(s);
e3500d1f 1252 gd_update_cursor(vc);
5104a1f6
AL
1253}
1254
a4ccabcf
AL
1255static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
1256 gpointer data)
1257{
1258 GtkDisplayState *s = data;
e3500d1f 1259 VirtualConsole *vc;
5104a1f6 1260 gboolean on_vga;
a4ccabcf
AL
1261
1262 if (!gtk_widget_get_realized(s->notebook)) {
1263 return;
1264 }
1265
e3500d1f
GH
1266 vc = gd_vc_find_by_page(s, arg2);
1267 if (!vc) {
1268 return;
1269 }
1270 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
1271 TRUE);
e3500d1f 1272 on_vga = (vc->type == GD_VC_GFX);
5104a1f6
AL
1273 if (!on_vga) {
1274 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1275 FALSE);
c6158483
AL
1276 } else if (s->full_screen) {
1277 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
1278 TRUE);
5104a1f6 1279 }
5104a1f6
AL
1280 gtk_widget_set_sensitive(s->grab_item, on_vga);
1281
e3500d1f 1282 gd_update_cursor(vc);
a4ccabcf
AL
1283}
1284
e3500d1f
GH
1285static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
1286 gpointer opaque)
5104a1f6 1287{
e3500d1f
GH
1288 VirtualConsole *vc = opaque;
1289 GtkDisplayState *s = vc->s;
5104a1f6 1290
2884cf5b
GH
1291 if (gd_grab_on_hover(s)) {
1292 gd_ungrab_keyboard(s);
e3500d1f 1293 gd_grab_keyboard(vc);
2884cf5b 1294 gd_update_caption(s);
5104a1f6 1295 }
5104a1f6
AL
1296 return TRUE;
1297}
1298
e3500d1f
GH
1299static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
1300 gpointer opaque)
5104a1f6 1301{
e3500d1f
GH
1302 VirtualConsole *vc = opaque;
1303 GtkDisplayState *s = vc->s;
5104a1f6 1304
2884cf5b 1305 if (gd_grab_on_hover(s)) {
4c638e2e 1306 gd_ungrab_keyboard(s);
2884cf5b 1307 gd_update_caption(s);
5104a1f6 1308 }
5104a1f6
AL
1309 return TRUE;
1310}
1311
6db253ca 1312static gboolean gd_focus_out_event(GtkWidget *widget,
e3500d1f 1313 GdkEventCrossing *crossing, gpointer opaque)
6db253ca 1314{
e3500d1f
GH
1315 VirtualConsole *vc = opaque;
1316 GtkDisplayState *s = vc->s;
6db253ca
JK
1317
1318 gtk_release_modifiers(s);
6db253ca
JK
1319 return TRUE;
1320}
1321
d861def3
AL
1322/** Virtual Console Callbacks **/
1323
ed1132e4 1324static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
cdeb7090 1325 int idx, GSList *group, GtkWidget *view_menu)
ed1132e4
GH
1326{
1327 char path[32];
1328
1329 snprintf(path, sizeof(path), "<QEMU>/View/VC%d", idx);
1330
cdeb7090 1331 vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
ed1132e4
GH
1332 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
1333 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
1334 gtk_accel_map_add_entry(path, GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
1335
1336 g_signal_connect(vc->menu_item, "activate",
1337 G_CALLBACK(gd_menu_switch_vc), s);
1338 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
1339
1340 return group;
1341}
1342
ee5f31e4 1343#if defined(CONFIG_VTE)
0fb20d1c
CR
1344static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
1345{
1346 VirtualConsole *vc = opaque;
1347
1348 if (gtk_adjustment_get_upper(adjustment) >
1349 gtk_adjustment_get_page_size(adjustment)) {
271a25c0 1350 gtk_widget_show(vc->vte.scrollbar);
0fb20d1c 1351 } else {
271a25c0 1352 gtk_widget_hide(vc->vte.scrollbar);
0fb20d1c
CR
1353 }
1354}
1355
d861def3
AL
1356static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
1357{
1358 VirtualConsole *vc = chr->opaque;
1359
271a25c0 1360 vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
d4370741 1361 return len;
d861def3
AL
1362}
1363
1364static int nb_vcs;
1365static CharDriverState *vcs[MAX_VCS];
1366
702ec69c 1367static CharDriverState *gd_vc_handler(ChardevVC *unused)
d861def3
AL
1368{
1369 CharDriverState *chr;
1370
1371 chr = g_malloc0(sizeof(*chr));
1372 chr->chr_write = gd_vc_chr_write;
bd5c51ee
MR
1373 /* defer OPENED events until our vc is fully initialized */
1374 chr->explicit_be_open = true;
d861def3
AL
1375
1376 vcs[nb_vcs++] = chr;
1377
1378 return chr;
1379}
1380
d4370741
CR
1381static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
1382 gpointer user_data)
d861def3 1383{
d4370741 1384 VirtualConsole *vc = user_data;
d861def3 1385
271a25c0 1386 qemu_chr_be_write(vc->vte.chr, (uint8_t *)text, (unsigned int)size);
d861def3
AL
1387 return TRUE;
1388}
1389
ed1132e4
GH
1390static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
1391 CharDriverState *chr, int idx,
e3500d1f 1392 GSList *group, GtkWidget *view_menu)
d861def3 1393{
d861def3 1394 char buffer[32];
0fb20d1c
CR
1395 GtkWidget *box;
1396 GtkWidget *scrollbar;
1397 GtkAdjustment *vadjustment;
d861def3 1398
e3500d1f 1399 vc->s = s;
ed1132e4 1400 vc->vte.chr = chr;
d861def3 1401
ed1132e4 1402 snprintf(buffer, sizeof(buffer), "vc%d", idx);
cdeb7090
GH
1403 vc->label = g_strdup_printf("%s", vc->vte.chr->label
1404 ? vc->vte.chr->label : buffer);
1405 group = gd_vc_menu_init(s, vc, idx, group, view_menu);
d861def3 1406
271a25c0
GH
1407 vc->vte.terminal = vte_terminal_new();
1408 g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
d861def3 1409
271a25c0
GH
1410 vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
1411 vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal), 80, 25);
d861def3 1412
0fb20d1c 1413#if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0)
271a25c0
GH
1414 vadjustment = gtk_scrollable_get_vadjustment
1415 (GTK_SCROLLABLE(vc->vte.terminal));
0fb20d1c 1416#else
271a25c0 1417 vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
0fb20d1c
CR
1418#endif
1419
1420#if GTK_CHECK_VERSION(3, 0, 0)
1421 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1422 scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
1423#else
1424 box = gtk_hbox_new(false, 2);
1425 scrollbar = gtk_vscrollbar_new(vadjustment);
1426#endif
1427
271a25c0 1428 gtk_box_pack_start(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
0fb20d1c
CR
1429 gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
1430
271a25c0
GH
1431 vc->vte.chr->opaque = vc;
1432 vc->vte.box = box;
1433 vc->vte.scrollbar = scrollbar;
0fb20d1c
CR
1434
1435 g_signal_connect(vadjustment, "changed",
1436 G_CALLBACK(gd_vc_adjustment_changed), vc);
d861def3 1437
e3500d1f 1438 vc->type = GD_VC_VTE;
271a25c0
GH
1439 vc->tab_item = box;
1440 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
cdeb7090 1441 gtk_label_new(vc->label));
d861def3 1442
271a25c0
GH
1443 qemu_chr_be_generic_open(vc->vte.chr);
1444 if (vc->vte.chr->init) {
1445 vc->vte.chr->init(vc->vte.chr);
d861def3
AL
1446 }
1447
d861def3 1448 return group;
a4ccabcf
AL
1449}
1450
ed1132e4 1451static void gd_vcs_init(GtkDisplayState *s, GSList *group,
ee5f31e4
GH
1452 GtkWidget *view_menu)
1453{
1454 int i;
1455
1456 for (i = 0; i < nb_vcs; i++) {
ed1132e4
GH
1457 VirtualConsole *vc = &s->vc[s->nb_vcs];
1458 group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
ee5f31e4
GH
1459 s->nb_vcs++;
1460 }
1461}
1462#endif /* CONFIG_VTE */
1463
a4ccabcf
AL
1464/** Window Creation **/
1465
e3500d1f
GH
1466static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
1467{
1468#if GTK_CHECK_VERSION(3, 0, 0)
1469 g_signal_connect(vc->gfx.drawing_area, "draw",
1470 G_CALLBACK(gd_draw_event), vc);
1471#else
1472 g_signal_connect(vc->gfx.drawing_area, "expose-event",
1473 G_CALLBACK(gd_expose_event), vc);
1474#endif
1475 g_signal_connect(vc->gfx.drawing_area, "event",
1476 G_CALLBACK(gd_event), vc);
1477 g_signal_connect(vc->gfx.drawing_area, "button-press-event",
1478 G_CALLBACK(gd_button_event), vc);
1479 g_signal_connect(vc->gfx.drawing_area, "button-release-event",
1480 G_CALLBACK(gd_button_event), vc);
1481 g_signal_connect(vc->gfx.drawing_area, "scroll-event",
1482 G_CALLBACK(gd_scroll_event), vc);
1483 g_signal_connect(vc->gfx.drawing_area, "key-press-event",
1484 G_CALLBACK(gd_key_event), vc);
1485 g_signal_connect(vc->gfx.drawing_area, "key-release-event",
1486 G_CALLBACK(gd_key_event), vc);
1487
1488 g_signal_connect(vc->gfx.drawing_area, "enter-notify-event",
1489 G_CALLBACK(gd_enter_event), vc);
1490 g_signal_connect(vc->gfx.drawing_area, "leave-notify-event",
1491 G_CALLBACK(gd_leave_event), vc);
1492 g_signal_connect(vc->gfx.drawing_area, "focus-out-event",
1493 G_CALLBACK(gd_focus_out_event), vc);
1494}
1495
a4ccabcf
AL
1496static void gd_connect_signals(GtkDisplayState *s)
1497{
1498 g_signal_connect(s->show_tabs_item, "activate",
1499 G_CALLBACK(gd_menu_show_tabs), s);
cdeb7090
GH
1500 g_signal_connect(s->untabify_item, "activate",
1501 G_CALLBACK(gd_menu_untabify), s);
a4ccabcf
AL
1502
1503 g_signal_connect(s->window, "delete-event",
1504 G_CALLBACK(gd_window_close), s);
1505
30e8f22b
JK
1506 g_signal_connect(s->pause_item, "activate",
1507 G_CALLBACK(gd_menu_pause), s);
1508 g_signal_connect(s->reset_item, "activate",
1509 G_CALLBACK(gd_menu_reset), s);
1510 g_signal_connect(s->powerdown_item, "activate",
1511 G_CALLBACK(gd_menu_powerdown), s);
a4ccabcf
AL
1512 g_signal_connect(s->quit_item, "activate",
1513 G_CALLBACK(gd_menu_quit), s);
c6158483
AL
1514 g_signal_connect(s->full_screen_item, "activate",
1515 G_CALLBACK(gd_menu_full_screen), s);
1516 g_signal_connect(s->zoom_in_item, "activate",
1517 G_CALLBACK(gd_menu_zoom_in), s);
1518 g_signal_connect(s->zoom_out_item, "activate",
1519 G_CALLBACK(gd_menu_zoom_out), s);
1520 g_signal_connect(s->zoom_fixed_item, "activate",
1521 G_CALLBACK(gd_menu_zoom_fixed), s);
1522 g_signal_connect(s->zoom_fit_item, "activate",
1523 G_CALLBACK(gd_menu_zoom_fit), s);
5104a1f6
AL
1524 g_signal_connect(s->grab_item, "activate",
1525 G_CALLBACK(gd_menu_grab_input), s);
a4ccabcf
AL
1526 g_signal_connect(s->notebook, "switch-page",
1527 G_CALLBACK(gd_change_page), s);
1528}
1529
bf9b255f 1530static GtkWidget *gd_create_menu_machine(GtkDisplayState *s, GtkAccelGroup *accel_group)
a4ccabcf 1531{
bf9b255f 1532 GtkWidget *machine_menu;
a4ccabcf
AL
1533 GtkWidget *separator;
1534
bf9b255f
AL
1535 machine_menu = gtk_menu_new();
1536 gtk_menu_set_accel_group(GTK_MENU(machine_menu), accel_group);
30e8f22b
JK
1537
1538 s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
bf9b255f 1539 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
30e8f22b
JK
1540
1541 separator = gtk_separator_menu_item_new();
bf9b255f 1542 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
30e8f22b 1543
9068f20d 1544 s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
bf9b255f 1545 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
30e8f22b 1546
9068f20d 1547 s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
bf9b255f 1548 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
30e8f22b
JK
1549
1550 separator = gtk_separator_menu_item_new();
bf9b255f 1551 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
a4ccabcf 1552
3d914488 1553 s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
a4ccabcf 1554 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
30e8f22b 1555 "<QEMU>/Machine/Quit");
3d914488 1556 gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
db1da1f2 1557 GDK_KEY_q, HOTKEY_MODIFIERS);
bf9b255f 1558 gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
a4ccabcf 1559
bf9b255f
AL
1560 return machine_menu;
1561}
1562
e3500d1f
GH
1563static const DisplayChangeListenerOps dcl_ops = {
1564 .dpy_name = "gtk",
1565 .dpy_gfx_update = gd_update,
1566 .dpy_gfx_switch = gd_switch,
1567 .dpy_refresh = gd_refresh,
1568 .dpy_mouse_set = gd_mouse_set,
1569 .dpy_cursor_define = gd_cursor_define,
1570};
1571
1572static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
ed1132e4 1573 QemuConsole *con, int idx,
e3500d1f
GH
1574 GSList *group, GtkWidget *view_menu)
1575{
6a24ced5
GH
1576 Error *local_err = NULL;
1577 Object *obj;
1578
1579 obj = object_property_get_link(OBJECT(con), "device", &local_err);
1580 if (obj) {
cdeb7090
GH
1581 vc->label = g_strdup_printf("%s", object_get_typename(obj));
1582 } else {
1583 vc->label = g_strdup_printf("VGA");
6a24ced5
GH
1584 }
1585
e3500d1f
GH
1586 vc->s = s;
1587 vc->gfx.scale_x = 1.0;
1588 vc->gfx.scale_y = 1.0;
1589
1590 vc->gfx.drawing_area = gtk_drawing_area_new();
1591 gtk_widget_add_events(vc->gfx.drawing_area,
1592 GDK_POINTER_MOTION_MASK |
1593 GDK_BUTTON_PRESS_MASK |
1594 GDK_BUTTON_RELEASE_MASK |
1595 GDK_BUTTON_MOTION_MASK |
1596 GDK_ENTER_NOTIFY_MASK |
1597 GDK_LEAVE_NOTIFY_MASK |
1598 GDK_SCROLL_MASK |
1599 GDK_KEY_PRESS_MASK);
1600 gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
1601 gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);
1602
1603 vc->type = GD_VC_GFX;
1604 vc->tab_item = vc->gfx.drawing_area;
1605 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
cdeb7090 1606 vc->tab_item, gtk_label_new(vc->label));
e3500d1f
GH
1607 gd_connect_vc_gfx_signals(vc);
1608
cdeb7090 1609 group = gd_vc_menu_init(s, vc, idx, group, view_menu);
e3500d1f
GH
1610
1611 vc->gfx.dcl.ops = &dcl_ops;
1612 vc->gfx.dcl.con = con;
1613 register_displaychangelistener(&vc->gfx.dcl);
1614
1615 return group;
1616}
1617
bf9b255f
AL
1618static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_group)
1619{
1620 GSList *group = NULL;
1621 GtkWidget *view_menu;
1622 GtkWidget *separator;
ed1132e4
GH
1623 QemuConsole *con;
1624 int vc;
bf9b255f
AL
1625
1626 view_menu = gtk_menu_new();
1627 gtk_menu_set_accel_group(GTK_MENU(view_menu), accel_group);
a4ccabcf 1628
3d914488 1629 s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
c6158483
AL
1630 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item),
1631 "<QEMU>/View/Full Screen");
b1e749c0
JK
1632 gtk_accel_map_add_entry("<QEMU>/View/Full Screen", GDK_KEY_f,
1633 HOTKEY_MODIFIERS);
bf9b255f 1634 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
c6158483
AL
1635
1636 separator = gtk_separator_menu_item_new();
bf9b255f 1637 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
c6158483 1638
3d914488 1639 s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
c6158483
AL
1640 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
1641 "<QEMU>/View/Zoom In");
b1e749c0
JK
1642 gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
1643 HOTKEY_MODIFIERS);
bf9b255f 1644 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
c6158483 1645
3d914488 1646 s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
c6158483
AL
1647 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
1648 "<QEMU>/View/Zoom Out");
b1e749c0
JK
1649 gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
1650 HOTKEY_MODIFIERS);
bf9b255f 1651 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
c6158483 1652
3d914488 1653 s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
c6158483
AL
1654 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
1655 "<QEMU>/View/Zoom Fixed");
b1e749c0
JK
1656 gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
1657 HOTKEY_MODIFIERS);
bf9b255f 1658 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
c6158483 1659
834574ea 1660 s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
bf9b255f 1661 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
c6158483
AL
1662
1663 separator = gtk_separator_menu_item_new();
bf9b255f 1664 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
c6158483 1665
834574ea 1666 s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
bf9b255f 1667 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item);
5104a1f6 1668
834574ea 1669 s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
5104a1f6
AL
1670 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
1671 "<QEMU>/View/Grab Input");
b1e749c0
JK
1672 gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
1673 HOTKEY_MODIFIERS);
bf9b255f 1674 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
5104a1f6 1675
a4ccabcf 1676 separator = gtk_separator_menu_item_new();
bf9b255f 1677 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
a4ccabcf 1678
e3500d1f 1679 /* gfx */
ed1132e4
GH
1680 for (vc = 0;; vc++) {
1681 con = qemu_console_lookup_by_index(vc);
1682 if (!con || !qemu_console_is_graphic(con)) {
1683 break;
1684 }
1685 group = gd_vc_gfx_init(s, &s->vc[vc], con,
1686 vc, group, view_menu);
1687 s->nb_vcs++;
1688 }
a4ccabcf 1689
ee5f31e4 1690#if defined(CONFIG_VTE)
e3500d1f 1691 /* vte */
ed1132e4 1692 gd_vcs_init(s, group, view_menu);
ee5f31e4 1693#endif
d861def3 1694
a4ccabcf 1695 separator = gtk_separator_menu_item_new();
bf9b255f 1696 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
a4ccabcf 1697
834574ea 1698 s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
bf9b255f 1699 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
a4ccabcf 1700
cdeb7090
GH
1701 s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab"));
1702 gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item);
1703
bf9b255f
AL
1704 return view_menu;
1705}
1706
1707static void gd_create_menus(GtkDisplayState *s)
1708{
1709 GtkAccelGroup *accel_group;
a4ccabcf 1710
bf9b255f
AL
1711 accel_group = gtk_accel_group_new();
1712 s->machine_menu = gd_create_menu_machine(s, accel_group);
1713 s->view_menu = gd_create_menu_view(s, accel_group);
1714
1715 s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
30e8f22b
JK
1716 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
1717 s->machine_menu);
1718 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item);
a4ccabcf 1719
bf9b255f 1720 s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
a4ccabcf
AL
1721 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
1722 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
bf9b255f
AL
1723
1724 g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
1725 gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
1726 s->accel_group = accel_group;
a4ccabcf
AL
1727}
1728
3158a348
BR
1729static void gd_set_keycode_type(GtkDisplayState *s)
1730{
1731#ifndef _WIN32
1732 char *keycodes = NULL;
1733 GdkDisplay *display = gtk_widget_get_display(s->window);
1734 Display *x11_display = gdk_x11_display_get_xdisplay(display);
1735 XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask,
1736 XkbUseCoreKbd);
1737
1738 if (desc && desc->names) {
1739 keycodes = XGetAtomName(x11_display, desc->names->keycodes);
1740 }
1741 if (keycodes == NULL) {
1742 fprintf(stderr, "could not lookup keycode name\n");
1743 } else if (strstart(keycodes, "evdev", NULL)) {
1744 s->has_evdev = true;
1745 } else if (!strstart(keycodes, "xfree86", NULL)) {
1746 fprintf(stderr, "unknown keycodes `%s', please report to "
1747 "qemu-devel@nongnu.org\n", keycodes);
1748 }
1749#endif
1750}
1751
881249c7 1752void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover)
a4ccabcf
AL
1753{
1754 GtkDisplayState *s = g_malloc0(sizeof(*s));
d819cdcc 1755 char *filename;
a4ccabcf
AL
1756
1757 gtk_init(NULL, NULL);
1758
a4ccabcf 1759 s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
51572ab0
DB
1760#if GTK_CHECK_VERSION(3, 2, 0)
1761 s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
1762#else
a4ccabcf 1763 s->vbox = gtk_vbox_new(FALSE, 0);
51572ab0 1764#endif
a4ccabcf 1765 s->notebook = gtk_notebook_new();
a4ccabcf
AL
1766 s->menu_bar = gtk_menu_bar_new();
1767
c6158483 1768 s->free_scale = FALSE;
a4ccabcf 1769
834574ea
AL
1770 setlocale(LC_ALL, "");
1771 bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
1772 textdomain("qemu");
1773
a4ccabcf
AL
1774 s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
1775
1776 s->mouse_mode_notifier.notify = gd_mouse_mode_change;
1777 qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
1778 qemu_add_vm_change_state_handler(gd_change_runstate, s);
1779
f7da9c17 1780 filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu_logo_no_text.svg");
d819cdcc
SW
1781 if (filename) {
1782 GError *error = NULL;
1783 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
1784 if (pixbuf) {
1785 gtk_window_set_icon(GTK_WINDOW(s->window), pixbuf);
1786 } else {
1787 g_error_free(error);
1788 }
1789 g_free(filename);
1790 }
1791
a4ccabcf
AL
1792 gd_create_menus(s);
1793
1794 gd_connect_signals(s);
1795
a4ccabcf
AL
1796 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1797 gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
1798
a4ccabcf
AL
1799 gd_update_caption(s);
1800
1801 gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
1802 gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
1803
1804 gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
1805
1806 gtk_widget_show_all(s->window);
1807
787ba4f0
PW
1808 if (full_screen) {
1809 gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
1810 }
881249c7
JK
1811 if (grab_on_hover) {
1812 gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
1813 }
787ba4f0 1814
3158a348 1815 gd_set_keycode_type(s);
a4ccabcf 1816}
ee5f31e4
GH
1817
1818void early_gtk_display_init(void)
1819{
1820#if defined(CONFIG_VTE)
1821 register_vc_handler(gd_vc_handler);
1822#endif
1823}