]> git.proxmox.com Git - mirror_qemu.git/blame - ui/gtk.c
ui/gtk: require at least GTK 2.18 and VTE 0.26
[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
a4ccabcf
AL
37#include <gtk/gtk.h>
38#include <gdk/gdkkeysyms.h>
834574ea 39#include <glib/gi18n.h>
3f58eade 40#include <locale.h>
a4ccabcf
AL
41#include <vte/vte.h>
42#include <sys/types.h>
43#include <sys/socket.h>
44#include <sys/un.h>
45#include <sys/wait.h>
46#include <pty.h>
47#include <math.h>
48
49#include "qemu-common.h"
50#include "ui/console.h"
51#include "sysemu/sysemu.h"
52#include "qmp-commands.h"
53#include "x_keymap.h"
54#include "keymaps.h"
d861def3 55#include "char/char.h"
a4ccabcf
AL
56
57//#define DEBUG_GTK
58
59#ifdef DEBUG_GTK
60#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
61#else
62#define DPRINTF(fmt, ...) do { } while (0)
63#endif
64
d861def3
AL
65#define MAX_VCS 10
66
a4ccabcf
AL
67typedef struct VirtualConsole
68{
69 GtkWidget *menu_item;
70 GtkWidget *terminal;
71 GtkWidget *scrolled_window;
72 CharDriverState *chr;
73 int fd;
74} VirtualConsole;
75
76typedef struct GtkDisplayState
77{
78 GtkWidget *window;
79
80 GtkWidget *menu_bar;
81
73d4dc71
AL
82 GtkAccelGroup *accel_group;
83
a4ccabcf
AL
84 GtkWidget *file_menu_item;
85 GtkWidget *file_menu;
86 GtkWidget *quit_item;
87
88 GtkWidget *view_menu_item;
89 GtkWidget *view_menu;
c6158483
AL
90 GtkWidget *full_screen_item;
91 GtkWidget *zoom_in_item;
92 GtkWidget *zoom_out_item;
93 GtkWidget *zoom_fixed_item;
94 GtkWidget *zoom_fit_item;
5104a1f6
AL
95 GtkWidget *grab_item;
96 GtkWidget *grab_on_hover_item;
a4ccabcf
AL
97 GtkWidget *vga_item;
98
d861def3
AL
99 int nb_vcs;
100 VirtualConsole vc[MAX_VCS];
101
a4ccabcf
AL
102 GtkWidget *show_tabs_item;
103
104 GtkWidget *vbox;
105 GtkWidget *notebook;
106 GtkWidget *drawing_area;
107 cairo_surface_t *surface;
108 DisplayChangeListener dcl;
109 DisplayState *ds;
110 int button_mask;
111 int last_x;
112 int last_y;
113
114 double scale_x;
115 double scale_y;
c6158483 116 gboolean full_screen;
a4ccabcf
AL
117
118 GdkCursor *null_cursor;
119 Notifier mouse_mode_notifier;
c6158483 120 gboolean free_scale;
a4ccabcf
AL
121} GtkDisplayState;
122
123static GtkDisplayState *global_state;
124
125/** Utility Functions **/
126
5104a1f6
AL
127static bool gd_is_grab_active(GtkDisplayState *s)
128{
129 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
130}
131
132static bool gd_grab_on_hover(GtkDisplayState *s)
133{
134 return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
135}
136
137static bool gd_on_vga(GtkDisplayState *s)
138{
139 return gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0;
140}
141
a4ccabcf
AL
142static void gd_update_cursor(GtkDisplayState *s, gboolean override)
143{
144 GdkWindow *window;
145 bool on_vga;
146
147 window = gtk_widget_get_window(GTK_WIDGET(s->drawing_area));
148
5104a1f6 149 on_vga = gd_on_vga(s);
a4ccabcf 150
5104a1f6 151 if ((override || on_vga) &&
c6158483 152 (s->full_screen || kbd_mouse_is_absolute() || gd_is_grab_active(s))) {
a4ccabcf
AL
153 gdk_window_set_cursor(window, s->null_cursor);
154 } else {
155 gdk_window_set_cursor(window, NULL);
156 }
157}
158
159static void gd_update_caption(GtkDisplayState *s)
160{
161 const char *status = "";
162 gchar *title;
5104a1f6
AL
163 const char *grab = "";
164
165 if (gd_is_grab_active(s)) {
166 grab = " - Press Ctrl+Alt+G to release grab";
167 }
a4ccabcf
AL
168
169 if (!runstate_is_running()) {
170 status = " [Stopped]";
171 }
172
173 if (qemu_name) {
5104a1f6 174 title = g_strdup_printf("QEMU (%s)%s%s", qemu_name, status, grab);
a4ccabcf 175 } else {
5104a1f6 176 title = g_strdup_printf("QEMU%s%s", status, grab);
a4ccabcf
AL
177 }
178
179 gtk_window_set_title(GTK_WINDOW(s->window), title);
180
181 g_free(title);
182}
183
184/** DisplayState Callbacks **/
185
186static void gd_update(DisplayState *ds, int x, int y, int w, int h)
187{
188 GtkDisplayState *s = ds->opaque;
189 int x1, x2, y1, y2;
c6158483
AL
190 int mx, my;
191 int fbw, fbh;
192 int ww, wh;
a4ccabcf
AL
193
194 DPRINTF("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h);
195
196 x1 = floor(x * s->scale_x);
197 y1 = floor(y * s->scale_y);
198
199 x2 = ceil(x * s->scale_x + w * s->scale_x);
200 y2 = ceil(y * s->scale_y + h * s->scale_y);
201
c6158483
AL
202 fbw = ds_get_width(s->ds) * s->scale_x;
203 fbh = ds_get_height(s->ds) * s->scale_y;
204
205 gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
206
207 mx = my = 0;
208 if (ww > fbw) {
209 mx = (ww - fbw) / 2;
210 }
211 if (wh > fbh) {
212 my = (wh - fbh) / 2;
213 }
214
215 gtk_widget_queue_draw_area(s->drawing_area, mx + x1, my + y1, (x2 - x1), (y2 - y1));
a4ccabcf
AL
216}
217
218static void gd_refresh(DisplayState *ds)
219{
220 vga_hw_update();
221}
222
223static void gd_resize(DisplayState *ds)
224{
225 GtkDisplayState *s = ds->opaque;
226 cairo_format_t kind;
227 int stride;
228
229 DPRINTF("resize(width=%d, height=%d)\n",
230 ds_get_width(ds), ds_get_height(ds));
231
232 if (s->surface) {
233 cairo_surface_destroy(s->surface);
234 }
235
236 switch (ds->surface->pf.bits_per_pixel) {
237 case 8:
238 kind = CAIRO_FORMAT_A8;
239 break;
240 case 16:
241 kind = CAIRO_FORMAT_RGB16_565;
242 break;
243 case 32:
244 kind = CAIRO_FORMAT_RGB24;
245 break;
246 default:
247 g_assert_not_reached();
248 break;
249 }
250
251 stride = cairo_format_stride_for_width(kind, ds_get_width(ds));
252 g_assert(ds_get_linesize(ds) == stride);
253
254 s->surface = cairo_image_surface_create_for_data(ds_get_data(ds),
255 kind,
256 ds_get_width(ds),
257 ds_get_height(ds),
258 ds_get_linesize(ds));
259
c6158483
AL
260 if (!s->full_screen) {
261 GtkRequisition req;
262 double sx, sy;
263
264 if (s->free_scale) {
265 sx = s->scale_x;
266 sy = s->scale_y;
267
268 s->scale_y = 1.0;
269 s->scale_x = 1.0;
270 } else {
271 sx = 1.0;
272 sy = 1.0;
273 }
274
275 gtk_widget_set_size_request(s->drawing_area,
276 ds_get_width(ds) * s->scale_x,
277 ds_get_height(ds) * s->scale_y);
278 gtk_widget_size_request(s->vbox, &req);
279
280 gtk_window_resize(GTK_WINDOW(s->window),
281 req.width * sx, req.height * sy);
282 }
a4ccabcf
AL
283}
284
285/** QEMU Events **/
286
287static void gd_change_runstate(void *opaque, int running, RunState state)
288{
289 GtkDisplayState *s = opaque;
290
291 gd_update_caption(s);
292}
293
294static void gd_mouse_mode_change(Notifier *notify, void *data)
295{
296 gd_update_cursor(container_of(notify, GtkDisplayState, mouse_mode_notifier),
297 FALSE);
298}
299
300/** GTK Events **/
301
73d4dc71
AL
302static gboolean gd_window_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
303{
304 GtkDisplayState *s = opaque;
305 GtkAccelGroupEntry *entries;
306 guint n_entries = 0;
307 gboolean propagate_accel = TRUE;
308 gboolean handled = FALSE;
309
310 entries = gtk_accel_group_query(s->accel_group, key->keyval,
311 key->state, &n_entries);
312 if (n_entries) {
313 const char *quark = g_quark_to_string(entries[0].accel_path_quark);
314
315 if (gd_is_grab_active(s) && strstart(quark, "<QEMU>/File/", NULL)) {
316 propagate_accel = FALSE;
317 }
318 }
319
320 if (!handled && propagate_accel) {
321 handled = gtk_window_activate_key(GTK_WINDOW(widget), key);
322 }
323
324 if (!handled) {
325 handled = gtk_window_propagate_key_event(GTK_WINDOW(widget), key);
326 }
327
328 return handled;
329}
330
a4ccabcf
AL
331static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
332 void *opaque)
333{
334 GtkDisplayState *s = opaque;
335
336 if (!no_quit) {
337 unregister_displaychangelistener(s->ds, &s->dcl);
338 qmp_quit(NULL);
339 return FALSE;
340 }
341
342 return TRUE;
343}
344
345static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
346{
347 GtkDisplayState *s = opaque;
c6158483 348 int mx, my;
a4ccabcf
AL
349 int ww, wh;
350 int fbw, fbh;
351
c6158483
AL
352 if (!gtk_widget_get_realized(widget)) {
353 return FALSE;
354 }
355
a4ccabcf
AL
356 fbw = ds_get_width(s->ds);
357 fbh = ds_get_height(s->ds);
358
359 gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
360
c6158483 361 if (s->full_screen) {
a4ccabcf
AL
362 s->scale_x = (double)ww / fbw;
363 s->scale_y = (double)wh / fbh;
c6158483
AL
364 } else if (s->free_scale) {
365 double sx, sy;
366
367 sx = (double)ww / fbw;
368 sy = (double)wh / fbh;
369
370 s->scale_x = s->scale_y = MIN(sx, sy);
a4ccabcf
AL
371 }
372
5104a1f6
AL
373 fbw *= s->scale_x;
374 fbh *= s->scale_y;
375
c6158483
AL
376 mx = my = 0;
377 if (ww > fbw) {
378 mx = (ww - fbw) / 2;
379 }
380 if (wh > fbh) {
381 my = (wh - fbh) / 2;
382 }
383
384 cairo_rectangle(cr, 0, 0, ww, wh);
385
386 /* Optionally cut out the inner area where the pixmap
387 will be drawn. This avoids 'flashing' since we're
388 not double-buffering. Note we're using the undocumented
389 behaviour of drawing the rectangle from right to left
390 to cut out the whole */
391 cairo_rectangle(cr, mx + fbw, my,
392 -1 * fbw, fbh);
393 cairo_fill(cr);
394
395 cairo_scale(cr, s->scale_x, s->scale_y);
396 cairo_set_source_surface(cr, s->surface, mx / s->scale_x, my / s->scale_y);
a4ccabcf
AL
397 cairo_paint(cr);
398
399 return TRUE;
400}
401
402static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
403 void *opaque)
404{
405 cairo_t *cr;
406 gboolean ret;
407
408 cr = gdk_cairo_create(gtk_widget_get_window(widget));
409 cairo_rectangle(cr,
410 expose->area.x,
411 expose->area.y,
412 expose->area.width,
413 expose->area.height);
414 cairo_clip(cr);
415
416 ret = gd_draw_event(widget, cr, opaque);
417
418 cairo_destroy(cr);
419
420 return ret;
421}
422
423static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
424 void *opaque)
425{
426 GtkDisplayState *s = opaque;
427 int dx, dy;
428 int x, y;
c6158483
AL
429 int mx, my;
430 int fbh, fbw;
431 int ww, wh;
432
433 fbw = ds_get_width(s->ds) * s->scale_x;
434 fbh = ds_get_height(s->ds) * s->scale_y;
a4ccabcf 435
c6158483
AL
436 gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
437
438 mx = my = 0;
439 if (ww > fbw) {
440 mx = (ww - fbw) / 2;
441 }
442 if (wh > fbh) {
443 my = (wh - fbh) / 2;
444 }
445
446 x = (motion->x - mx) / s->scale_x;
447 y = (motion->y - my) / s->scale_y;
448
449 if (x < 0 || y < 0 ||
450 x >= ds_get_width(s->ds) ||
451 y >= ds_get_height(s->ds)) {
452 return TRUE;
453 }
a4ccabcf
AL
454
455 if (kbd_mouse_is_absolute()) {
456 dx = x * 0x7FFF / (ds_get_width(s->ds) - 1);
457 dy = y * 0x7FFF / (ds_get_height(s->ds) - 1);
458 } else if (s->last_x == -1 || s->last_y == -1) {
459 dx = 0;
460 dy = 0;
461 } else {
462 dx = x - s->last_x;
463 dy = y - s->last_y;
464 }
465
466 s->last_x = x;
467 s->last_y = y;
468
5104a1f6 469 if (kbd_mouse_is_absolute() || gd_is_grab_active(s)) {
a4ccabcf
AL
470 kbd_mouse_event(dx, dy, 0, s->button_mask);
471 }
472
5104a1f6
AL
473 if (!kbd_mouse_is_absolute() && gd_is_grab_active(s)) {
474 GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(s->drawing_area));
475 GdkDisplay *display = gdk_drawable_get_display(drawable);
476 GdkScreen *screen = gdk_drawable_get_screen(drawable);
477 int x = (int)motion->x_root;
478 int y = (int)motion->y_root;
479
480 /* In relative mode check to see if client pointer hit
481 * one of the screen edges, and if so move it back by
482 * 200 pixels. This is important because the pointer
483 * in the server doesn't correspond 1-for-1, and so
484 * may still be only half way across the screen. Without
485 * this warp, the server pointer would thus appear to hit
486 * an invisible wall */
487 if (x == 0) {
488 x += 200;
489 }
490 if (y == 0) {
491 y += 200;
492 }
493 if (x == (gdk_screen_get_width(screen) - 1)) {
494 x -= 200;
495 }
496 if (y == (gdk_screen_get_height(screen) - 1)) {
497 y -= 200;
498 }
499
500 if (x != (int)motion->x_root || y != (int)motion->y_root) {
501 gdk_display_warp_pointer(display, screen, x, y);
502 s->last_x = -1;
503 s->last_y = -1;
504 return FALSE;
505 }
506 }
a4ccabcf
AL
507 return TRUE;
508}
509
510static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
511 void *opaque)
512{
513 GtkDisplayState *s = opaque;
514 int dx, dy;
515 int n;
516
517 if (button->button == 1) {
518 n = 0x01;
519 } else if (button->button == 2) {
520 n = 0x04;
521 } else if (button->button == 3) {
522 n = 0x02;
523 } else {
524 n = 0x00;
525 }
526
527 if (button->type == GDK_BUTTON_PRESS) {
528 s->button_mask |= n;
529 } else if (button->type == GDK_BUTTON_RELEASE) {
530 s->button_mask &= ~n;
531 }
532
533 if (kbd_mouse_is_absolute()) {
534 dx = s->last_x * 0x7FFF / (ds_get_width(s->ds) - 1);
535 dy = s->last_y * 0x7FFF / (ds_get_height(s->ds) - 1);
536 } else {
537 dx = 0;
538 dy = 0;
539 }
540
541 kbd_mouse_event(dx, dy, 0, s->button_mask);
542
543 return TRUE;
544}
545
546static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
547{
548 int gdk_keycode;
549 int qemu_keycode;
550
551 gdk_keycode = key->hardware_keycode;
552
553 if (gdk_keycode < 9) {
554 qemu_keycode = 0;
555 } else if (gdk_keycode < 97) {
556 qemu_keycode = gdk_keycode - 8;
557 } else if (gdk_keycode < 158) {
558 qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
559 } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
560 qemu_keycode = 0x70;
561 } else if (gdk_keycode == 211) { /* backslash */
562 qemu_keycode = 0x73;
563 } else {
564 qemu_keycode = 0;
565 }
566
567 DPRINTF("translated GDK keycode %d to QEMU keycode %d (%s)\n",
568 gdk_keycode, qemu_keycode,
569 (key->type == GDK_KEY_PRESS) ? "down" : "up");
570
571 if (qemu_keycode & SCANCODE_GREY) {
572 kbd_put_keycode(SCANCODE_EMUL0);
573 }
574
575 if (key->type == GDK_KEY_PRESS) {
576 kbd_put_keycode(qemu_keycode & SCANCODE_KEYCODEMASK);
577 } else if (key->type == GDK_KEY_RELEASE) {
578 kbd_put_keycode(qemu_keycode | SCANCODE_UP);
579 } else {
580 g_assert_not_reached();
581 }
582
583 return TRUE;
584}
585
586/** Window Menu Actions **/
587
588static void gd_menu_quit(GtkMenuItem *item, void *opaque)
589{
590 qmp_quit(NULL);
591}
592
593static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
594{
595 GtkDisplayState *s = opaque;
596
597 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
598 gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
d861def3
AL
599 } else {
600 int i;
601
602 for (i = 0; i < s->nb_vcs; i++) {
603 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vc[i].menu_item))) {
604 gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), i + 1);
605 break;
606 }
607 }
a4ccabcf
AL
608 }
609}
610
611static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
612{
613 GtkDisplayState *s = opaque;
614
615 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
616 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
617 } else {
618 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
619 }
620}
621
c6158483
AL
622static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
623{
624 GtkDisplayState *s = opaque;
625
626 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->full_screen_item))) {
627 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
628 gtk_widget_set_size_request(s->menu_bar, 0, 0);
629 gtk_widget_set_size_request(s->drawing_area, -1, -1);
630 gtk_window_fullscreen(GTK_WINDOW(s->window));
631 if (gd_on_vga(s)) {
632 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), TRUE);
633 }
634 s->full_screen = TRUE;
635 } else {
636 gtk_window_unfullscreen(GTK_WINDOW(s->window));
637 gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
638 gtk_widget_set_size_request(s->menu_bar, -1, -1);
639 gtk_widget_set_size_request(s->drawing_area,
640 ds_get_width(s->ds), ds_get_height(s->ds));
641 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE);
642 s->full_screen = FALSE;
643 s->scale_x = 1.0;
644 s->scale_y = 1.0;
645 }
646
647 gd_update_cursor(s, FALSE);
648}
649
650static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
651{
652 GtkDisplayState *s = opaque;
653
654 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
655 FALSE);
656
657 s->scale_x += .25;
658 s->scale_y += .25;
659
660 gd_resize(s->ds);
661}
662
663static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
664{
665 GtkDisplayState *s = opaque;
666
667 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
668 FALSE);
669
670 s->scale_x -= .25;
671 s->scale_y -= .25;
672
673 s->scale_x = MAX(s->scale_x, .25);
674 s->scale_y = MAX(s->scale_y, .25);
675
676 gd_resize(s->ds);
677}
678
679static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
680{
681 GtkDisplayState *s = opaque;
682
683 s->scale_x = 1.0;
684 s->scale_y = 1.0;
685
686 gd_resize(s->ds);
687}
688
689static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
690{
691 GtkDisplayState *s = opaque;
692 int ww, wh;
693
694 if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
695 s->free_scale = TRUE;
696 } else {
697 s->free_scale = FALSE;
698 }
699
700 gd_resize(s->ds);
701
702 gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
703 gtk_widget_queue_draw_area(s->drawing_area, 0, 0, ww, wh);
704}
705
5104a1f6
AL
706static void gd_grab_keyboard(GtkDisplayState *s)
707{
708 gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
709 FALSE,
710 GDK_CURRENT_TIME);
711}
712
713static void gd_ungrab_keyboard(GtkDisplayState *s)
714{
715 gdk_keyboard_ungrab(GDK_CURRENT_TIME);
716}
717
718static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
719{
720 GtkDisplayState *s = opaque;
721
722 if (gd_is_grab_active(s)) {
723 gd_grab_keyboard(s);
724 gdk_pointer_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
725 FALSE, /* All events to come to our window directly */
726 GDK_POINTER_MOTION_MASK |
727 GDK_BUTTON_PRESS_MASK |
728 GDK_BUTTON_RELEASE_MASK |
729 GDK_BUTTON_MOTION_MASK |
730 GDK_SCROLL_MASK,
731 NULL, /* Allow cursor to move over entire desktop */
732 s->null_cursor,
733 GDK_CURRENT_TIME);
734 } else {
735 gd_ungrab_keyboard(s);
736 gdk_pointer_ungrab(GDK_CURRENT_TIME);
737 }
738
739 gd_update_caption(s);
740 gd_update_cursor(s, FALSE);
741}
742
a4ccabcf
AL
743static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
744 gpointer data)
745{
746 GtkDisplayState *s = data;
d861def3 747 guint last_page;
5104a1f6 748 gboolean on_vga;
a4ccabcf
AL
749
750 if (!gtk_widget_get_realized(s->notebook)) {
751 return;
752 }
753
d861def3
AL
754 last_page = gtk_notebook_get_current_page(nb);
755
756 if (last_page) {
757 gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
758 }
759
5104a1f6
AL
760 on_vga = arg2 == 0;
761
762 if (!on_vga) {
763 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
764 FALSE);
c6158483
AL
765 } else if (s->full_screen) {
766 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
767 TRUE);
5104a1f6
AL
768 }
769
d861def3
AL
770 if (arg2 == 0) {
771 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
772 } else {
773 VirtualConsole *vc = &s->vc[arg2 - 1];
774 VteTerminal *term = VTE_TERMINAL(vc->terminal);
775 int width, height;
776
777 width = 80 * vte_terminal_get_char_width(term);
778 height = 25 * vte_terminal_get_char_height(term);
779
780 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
781 gtk_widget_set_size_request(vc->terminal, width, height);
782 }
783
5104a1f6
AL
784 gtk_widget_set_sensitive(s->grab_item, on_vga);
785
a4ccabcf
AL
786 gd_update_cursor(s, TRUE);
787}
788
5104a1f6
AL
789static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data)
790{
791 GtkDisplayState *s = data;
792
793 if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) {
794 gd_grab_keyboard(s);
795 }
796
797 return TRUE;
798}
799
800static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data)
801{
802 GtkDisplayState *s = data;
803
804 if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) {
805 gd_ungrab_keyboard(s);
806 }
807
808 return TRUE;
809}
810
d861def3
AL
811/** Virtual Console Callbacks **/
812
813static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
814{
815 VirtualConsole *vc = chr->opaque;
816
817 return write(vc->fd, buf, len);
818}
819
820static int nb_vcs;
821static CharDriverState *vcs[MAX_VCS];
822
823static CharDriverState *gd_vc_handler(QemuOpts *opts)
824{
825 CharDriverState *chr;
826
827 chr = g_malloc0(sizeof(*chr));
828 chr->chr_write = gd_vc_chr_write;
829
830 vcs[nb_vcs++] = chr;
831
832 return chr;
833}
834
a4ccabcf
AL
835void early_gtk_display_init(void)
836{
d861def3
AL
837 register_vc_handler(gd_vc_handler);
838}
839
840static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque)
841{
842 VirtualConsole *vc = opaque;
843 uint8_t buffer[1024];
844 ssize_t len;
845
846 len = read(vc->fd, buffer, sizeof(buffer));
847 if (len <= 0) {
848 return FALSE;
849 }
850
851 qemu_chr_be_write(vc->chr, buffer, len);
852
853 return TRUE;
854}
855
856static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group)
857{
858 const char *label;
859 char buffer[32];
860 char path[32];
861 VtePty *pty;
862 GIOChannel *chan;
863 GtkWidget *scrolled_window;
864 GtkAdjustment *vadjustment;
865 int master_fd, slave_fd, ret;
866 struct termios tty;
867
868 snprintf(buffer, sizeof(buffer), "vc%d", index);
869 snprintf(path, sizeof(path), "<QEMU>/View/VC%d", index);
870
871 vc->chr = vcs[index];
872
873 if (vc->chr->label) {
874 label = vc->chr->label;
875 } else {
876 label = buffer;
877 }
878
879 vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, label);
880 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
881 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
882 gtk_accel_map_add_entry(path, GDK_KEY_2 + index, GDK_CONTROL_MASK | GDK_MOD1_MASK);
883
884 vc->terminal = vte_terminal_new();
885
886 ret = openpty(&master_fd, &slave_fd, NULL, NULL, NULL);
887 g_assert(ret != -1);
888
889 /* Set raw attributes on the pty. */
890 tcgetattr(slave_fd, &tty);
891 cfmakeraw(&tty);
892 tcsetattr(slave_fd, TCSAFLUSH, &tty);
893
894 pty = vte_pty_new_foreign(master_fd, NULL);
895
896 vte_terminal_set_pty_object(VTE_TERMINAL(vc->terminal), pty);
897
898 vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->terminal), -1);
899
900 vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->terminal));
901
902 scrolled_window = gtk_scrolled_window_new(NULL, vadjustment);
903 gtk_container_add(GTK_CONTAINER(scrolled_window), vc->terminal);
904
905 vte_terminal_set_size(VTE_TERMINAL(vc->terminal), 80, 25);
906
907 vc->fd = slave_fd;
908 vc->chr->opaque = vc;
909 vc->scrolled_window = scrolled_window;
910
911 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vc->scrolled_window),
912 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
913
914 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), scrolled_window, gtk_label_new(label));
915 g_signal_connect(vc->menu_item, "activate",
916 G_CALLBACK(gd_menu_switch_vc), s);
917
918 gtk_menu_append(GTK_MENU(s->view_menu), vc->menu_item);
919
920 qemu_chr_generic_open(vc->chr);
921 if (vc->chr->init) {
922 vc->chr->init(vc->chr);
923 }
924
925 chan = g_io_channel_unix_new(vc->fd);
926 g_io_add_watch(chan, G_IO_IN, gd_vc_in, vc);
927
928 return group;
a4ccabcf
AL
929}
930
931/** Window Creation **/
932
933static void gd_connect_signals(GtkDisplayState *s)
934{
935 g_signal_connect(s->show_tabs_item, "activate",
936 G_CALLBACK(gd_menu_show_tabs), s);
937
73d4dc71
AL
938 g_signal_connect(s->window, "key-press-event",
939 G_CALLBACK(gd_window_key_event), s);
a4ccabcf
AL
940 g_signal_connect(s->window, "delete-event",
941 G_CALLBACK(gd_window_close), s);
942
943 g_signal_connect(s->drawing_area, "expose-event",
944 G_CALLBACK(gd_expose_event), s);
945 g_signal_connect(s->drawing_area, "motion-notify-event",
946 G_CALLBACK(gd_motion_event), s);
947 g_signal_connect(s->drawing_area, "button-press-event",
948 G_CALLBACK(gd_button_event), s);
949 g_signal_connect(s->drawing_area, "button-release-event",
950 G_CALLBACK(gd_button_event), s);
951 g_signal_connect(s->drawing_area, "key-press-event",
952 G_CALLBACK(gd_key_event), s);
953 g_signal_connect(s->drawing_area, "key-release-event",
954 G_CALLBACK(gd_key_event), s);
955
956 g_signal_connect(s->quit_item, "activate",
957 G_CALLBACK(gd_menu_quit), s);
c6158483
AL
958 g_signal_connect(s->full_screen_item, "activate",
959 G_CALLBACK(gd_menu_full_screen), s);
960 g_signal_connect(s->zoom_in_item, "activate",
961 G_CALLBACK(gd_menu_zoom_in), s);
962 g_signal_connect(s->zoom_out_item, "activate",
963 G_CALLBACK(gd_menu_zoom_out), s);
964 g_signal_connect(s->zoom_fixed_item, "activate",
965 G_CALLBACK(gd_menu_zoom_fixed), s);
966 g_signal_connect(s->zoom_fit_item, "activate",
967 G_CALLBACK(gd_menu_zoom_fit), s);
a4ccabcf
AL
968 g_signal_connect(s->vga_item, "activate",
969 G_CALLBACK(gd_menu_switch_vc), s);
5104a1f6
AL
970 g_signal_connect(s->grab_item, "activate",
971 G_CALLBACK(gd_menu_grab_input), s);
a4ccabcf
AL
972 g_signal_connect(s->notebook, "switch-page",
973 G_CALLBACK(gd_change_page), s);
5104a1f6
AL
974 g_signal_connect(s->drawing_area, "enter-notify-event",
975 G_CALLBACK(gd_enter_event), s);
976 g_signal_connect(s->drawing_area, "leave-notify-event",
977 G_CALLBACK(gd_leave_event), s);
a4ccabcf
AL
978}
979
980static void gd_create_menus(GtkDisplayState *s)
981{
982 GtkStockItem item;
983 GtkAccelGroup *accel_group;
984 GSList *group = NULL;
985 GtkWidget *separator;
d861def3 986 int i;
a4ccabcf
AL
987
988 accel_group = gtk_accel_group_new();
989 s->file_menu = gtk_menu_new();
990 gtk_menu_set_accel_group(GTK_MENU(s->file_menu), accel_group);
834574ea 991 s->file_menu_item = gtk_menu_item_new_with_mnemonic(_("_File"));
a4ccabcf
AL
992
993 s->quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
994 gtk_stock_lookup(GTK_STOCK_QUIT, &item);
995 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
996 "<QEMU>/File/Quit");
997 gtk_accel_map_add_entry("<QEMU>/File/Quit", item.keyval, item.modifier);
998
999 s->view_menu = gtk_menu_new();
1000 gtk_menu_set_accel_group(GTK_MENU(s->view_menu), accel_group);
834574ea 1001 s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
a4ccabcf 1002
834574ea 1003 s->full_screen_item = gtk_check_menu_item_new_with_mnemonic(_("_Full Screen"));
c6158483
AL
1004 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item),
1005 "<QEMU>/View/Full Screen");
1006 gtk_accel_map_add_entry("<QEMU>/View/Full Screen", GDK_KEY_f, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1007 gtk_menu_append(GTK_MENU(s->view_menu), s->full_screen_item);
1008
1009 separator = gtk_separator_menu_item_new();
1010 gtk_menu_append(GTK_MENU(s->view_menu), separator);
1011
1012 s->zoom_in_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_IN, NULL);
1013 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
1014 "<QEMU>/View/Zoom In");
1015 gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1016 gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_in_item);
1017
1018 s->zoom_out_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_OUT, NULL);
1019 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
1020 "<QEMU>/View/Zoom Out");
1021 gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1022 gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_out_item);
1023
1024 s->zoom_fixed_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_100, NULL);
1025 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
1026 "<QEMU>/View/Zoom Fixed");
1027 gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1028 gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fixed_item);
1029
834574ea 1030 s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
c6158483
AL
1031 gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fit_item);
1032
1033 separator = gtk_separator_menu_item_new();
1034 gtk_menu_append(GTK_MENU(s->view_menu), separator);
1035
834574ea 1036 s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
5104a1f6
AL
1037 gtk_menu_append(GTK_MENU(s->view_menu), s->grab_on_hover_item);
1038
834574ea 1039 s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
5104a1f6
AL
1040 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
1041 "<QEMU>/View/Grab Input");
1042 gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1043 gtk_menu_append(GTK_MENU(s->view_menu), s->grab_item);
1044
a4ccabcf
AL
1045 separator = gtk_separator_menu_item_new();
1046 gtk_menu_append(GTK_MENU(s->view_menu), separator);
1047
1048 s->vga_item = gtk_radio_menu_item_new_with_mnemonic(group, "_VGA");
1049 group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(s->vga_item));
1050 gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->vga_item),
1051 "<QEMU>/View/VGA");
1052 gtk_accel_map_add_entry("<QEMU>/View/VGA", GDK_KEY_1, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1053 gtk_menu_append(GTK_MENU(s->view_menu), s->vga_item);
1054
d861def3
AL
1055 for (i = 0; i < nb_vcs; i++) {
1056 VirtualConsole *vc = &s->vc[i];
1057
1058 group = gd_vc_init(s, vc, i, group);
1059 s->nb_vcs++;
1060 }
1061
a4ccabcf
AL
1062 separator = gtk_separator_menu_item_new();
1063 gtk_menu_append(GTK_MENU(s->view_menu), separator);
1064
834574ea 1065 s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
a4ccabcf
AL
1066 gtk_menu_append(GTK_MENU(s->view_menu), s->show_tabs_item);
1067
1068 g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
1069 gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
73d4dc71 1070 s->accel_group = accel_group;
a4ccabcf
AL
1071
1072 gtk_menu_append(GTK_MENU(s->file_menu), s->quit_item);
1073 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->file_menu_item), s->file_menu);
1074 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->file_menu_item);
1075
1076 gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
1077 gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
1078}
1079
1080void gtk_display_init(DisplayState *ds)
1081{
1082 GtkDisplayState *s = g_malloc0(sizeof(*s));
1083
1084 gtk_init(NULL, NULL);
1085
1086 ds->opaque = s;
1087 s->ds = ds;
1088 s->dcl.dpy_gfx_update = gd_update;
1089 s->dcl.dpy_gfx_resize = gd_resize;
1090 s->dcl.dpy_refresh = gd_refresh;
1091
1092 s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1093 s->vbox = gtk_vbox_new(FALSE, 0);
1094 s->notebook = gtk_notebook_new();
1095 s->drawing_area = gtk_drawing_area_new();
1096 s->menu_bar = gtk_menu_bar_new();
1097
1098 s->scale_x = 1.0;
1099 s->scale_y = 1.0;
c6158483 1100 s->free_scale = FALSE;
a4ccabcf 1101
834574ea
AL
1102 setlocale(LC_ALL, "");
1103 bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
1104 textdomain("qemu");
1105
a4ccabcf
AL
1106 s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
1107
1108 s->mouse_mode_notifier.notify = gd_mouse_mode_change;
1109 qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
1110 qemu_add_vm_change_state_handler(gd_change_runstate, s);
1111
1112 gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), s->drawing_area, gtk_label_new("VGA"));
1113
1114 gd_create_menus(s);
1115
1116 gd_connect_signals(s);
1117
1118 gtk_widget_add_events(s->drawing_area,
1119 GDK_POINTER_MOTION_MASK |
1120 GDK_BUTTON_PRESS_MASK |
1121 GDK_BUTTON_RELEASE_MASK |
1122 GDK_BUTTON_MOTION_MASK |
5104a1f6
AL
1123 GDK_ENTER_NOTIFY_MASK |
1124 GDK_LEAVE_NOTIFY_MASK |
a4ccabcf
AL
1125 GDK_SCROLL_MASK |
1126 GDK_KEY_PRESS_MASK);
1127 gtk_widget_set_double_buffered(s->drawing_area, FALSE);
1128 gtk_widget_set_can_focus(s->drawing_area, TRUE);
1129
1130 gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1131 gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
1132
a4ccabcf
AL
1133 gd_update_caption(s);
1134
1135 gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
1136 gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
1137
1138 gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
1139
1140 gtk_widget_show_all(s->window);
1141
1142 register_displaychangelistener(ds, &s->dcl);
1143
1144 global_state = s;
1145}