gtk.c 62.2 KB
Newer Older
A
Anthony Liguori 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
/*
 * GTK UI
 *
 * Copyright IBM, Corp. 2012
 *
 * Authors:
 *  Anthony Liguori   <aliguori@us.ibm.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 *
 * Portions from gtk-vnc:
 *
 * GTK VNC Widget
 *
 * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
 * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.0 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

34 35 36
#define GETTEXT_PACKAGE "qemu"
#define LOCALEDIR "po"

K
Kevin Wolf 已提交
37 38
#include "qemu-common.h"

G
Gerd Hoffmann 已提交
39 40
#include "ui/console.h"
#include "ui/gtk.h"
K
Kevin Wolf 已提交
41

42
#include <glib/gi18n.h>
43
#include <locale.h>
S
Stefan Weil 已提交
44
#if defined(CONFIG_VTE)
A
Anthony Liguori 已提交
45
#include <vte/vte.h>
S
Stefan Weil 已提交
46
#endif
A
Anthony Liguori 已提交
47 48
#include <math.h>

49
#include "trace.h"
50
#include "ui/input.h"
A
Anthony Liguori 已提交
51 52 53 54
#include "sysemu/sysemu.h"
#include "qmp-commands.h"
#include "x_keymap.h"
#include "keymaps.h"
55
#include "sysemu/char.h"
G
Gerd Hoffmann 已提交
56
#include "qom/object.h"
A
Anthony Liguori 已提交
57

58
#define MAX_VCS 10
G
Gerd Hoffmann 已提交
59 60 61 62 63 64
#define VC_WINDOW_X_MIN  320
#define VC_WINDOW_Y_MIN  240
#define VC_TERM_X_MIN     80
#define VC_TERM_Y_MIN     25
#define VC_SCALE_MIN    0.25
#define VC_SCALE_STEP   0.25
65

S
Stefan Weil 已提交
66 67 68
#if !defined(CONFIG_VTE)
# define VTE_CHECK_VERSION(a, b, c) 0
#endif
69

70 71 72 73 74 75 76 77 78 79 80 81 82 83
#if defined(CONFIG_VTE) && !GTK_CHECK_VERSION(3, 0, 0)
/*
 * The gtk2 vte terminal widget seriously messes up the window resize
 * for some reason.  You basically can't make the qemu window smaller
 * any more because the toplevel window geoemtry hints are overridden.
 *
 * Workaround that by hiding all vte widgets, except the one in the
 * current tab.
 *
 * Luckily everything works smooth in gtk3.
 */
# define VTE_RESIZE_HACK 1
#endif

84 85 86 87
#if !GTK_CHECK_VERSION(2, 20, 0)
#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget)
#endif

88 89 90 91 92 93 94
#ifndef GDK_IS_X11_DISPLAY
#define GDK_IS_X11_DISPLAY(dpy) (dpy == dpy)
#endif
#ifndef GDK_IS_WIN32_DISPLAY
#define GDK_IS_WIN32_DISPLAY(dpy) (dpy == dpy)
#endif

95 96 97 98 99 100
#ifndef GDK_KEY_0
#define GDK_KEY_0 GDK_0
#define GDK_KEY_1 GDK_1
#define GDK_KEY_2 GDK_2
#define GDK_KEY_f GDK_f
#define GDK_KEY_g GDK_g
101
#define GDK_KEY_q GDK_q
102 103
#define GDK_KEY_plus GDK_plus
#define GDK_KEY_minus GDK_minus
G
Gerd Hoffmann 已提交
104
#define GDK_KEY_Pause GDK_Pause
105
#endif
106

107 108 109 110 111 112 113 114 115
/* Some older mingw versions lack this constant or have
 * it conditionally defined */
#ifdef _WIN32
# ifndef MAPVK_VK_TO_VSC
#  define MAPVK_VK_TO_VSC 0
# endif
#endif


J
Jan Kiszka 已提交
116 117
#define HOTKEY_MODIFIERS        (GDK_CONTROL_MASK | GDK_MOD1_MASK)

118 119 120 121 122
static const int modifier_keycode[] = {
    /* shift, control, alt keys, meta keys, both left & right */
    0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd,
};

123
struct GtkDisplayState {
A
Anthony Liguori 已提交
124 125 126 127
    GtkWidget *window;

    GtkWidget *menu_bar;

128 129
    GtkAccelGroup *accel_group;

130 131 132 133 134
    GtkWidget *machine_menu_item;
    GtkWidget *machine_menu;
    GtkWidget *pause_item;
    GtkWidget *reset_item;
    GtkWidget *powerdown_item;
A
Anthony Liguori 已提交
135 136 137 138
    GtkWidget *quit_item;

    GtkWidget *view_menu_item;
    GtkWidget *view_menu;
139 140 141 142 143
    GtkWidget *full_screen_item;
    GtkWidget *zoom_in_item;
    GtkWidget *zoom_out_item;
    GtkWidget *zoom_fixed_item;
    GtkWidget *zoom_fit_item;
144 145
    GtkWidget *grab_item;
    GtkWidget *grab_on_hover_item;
A
Anthony Liguori 已提交
146

147 148 149
    int nb_vcs;
    VirtualConsole vc[MAX_VCS];

A
Anthony Liguori 已提交
150
    GtkWidget *show_tabs_item;
151
    GtkWidget *untabify_item;
A
Anthony Liguori 已提交
152 153 154 155

    GtkWidget *vbox;
    GtkWidget *notebook;
    int button_mask;
156
    gboolean last_set;
A
Anthony Liguori 已提交
157 158
    int last_x;
    int last_y;
159 160
    int grab_x_root;
    int grab_y_root;
G
Gerd Hoffmann 已提交
161 162
    VirtualConsole *kbd_owner;
    VirtualConsole *ptr_owner;
A
Anthony Liguori 已提交
163

164
    gboolean full_screen;
A
Anthony Liguori 已提交
165 166 167

    GdkCursor *null_cursor;
    Notifier mouse_mode_notifier;
168
    gboolean free_scale;
169 170

    bool external_pause_update;
171 172

    bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
173
    bool has_evdev;
174
    bool ignore_keys;
175
};
A
Anthony Liguori 已提交
176

G
Gerd Hoffmann 已提交
177
static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
G
Gerd Hoffmann 已提交
178
static void gd_ungrab_pointer(GtkDisplayState *s);
G
Gerd Hoffmann 已提交
179
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
180
static void gd_ungrab_keyboard(GtkDisplayState *s);
G
Gerd Hoffmann 已提交
181

A
Anthony Liguori 已提交
182 183
/** Utility Functions **/

G
Gerd Hoffmann 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
{
    VirtualConsole *vc;
    gint i;

    for (i = 0; i < s->nb_vcs; i++) {
        vc = &s->vc[i];
        if (gtk_check_menu_item_get_active
            (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
            return vc;
        }
    }
    return NULL;
}

static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
{
    VirtualConsole *vc;
    gint i, p;

    for (i = 0; i < s->nb_vcs; i++) {
        vc = &s->vc[i];
        p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
        if (p == page) {
            return vc;
        }
    }
    return NULL;
}

214 215 216 217 218 219 220 221
static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
{
    gint page;

    page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
    return gd_vc_find_by_page(s, page);
}

222 223 224 225 226 227 228 229 230 231
static bool gd_is_grab_active(GtkDisplayState *s)
{
    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
}

static bool gd_grab_on_hover(GtkDisplayState *s)
{
    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
}

232
static void gd_update_cursor(VirtualConsole *vc)
A
Anthony Liguori 已提交
233
{
234
    GtkDisplayState *s = vc->s;
A
Anthony Liguori 已提交
235 236
    GdkWindow *window;

237 238
    if (vc->type != GD_VC_GFX ||
        !qemu_console_is_graphic(vc->gfx.dcl.con)) {
239 240
        return;
    }
A
Anthony Liguori 已提交
241

242 243 244 245
    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
        return;
    }

246
    window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
G
Gerd Hoffmann 已提交
247
    if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) {
A
Anthony Liguori 已提交
248 249 250 251 252 253 254 255 256
        gdk_window_set_cursor(window, s->null_cursor);
    } else {
        gdk_window_set_cursor(window, NULL);
    }
}

static void gd_update_caption(GtkDisplayState *s)
{
    const char *status = "";
G
Gerd Hoffmann 已提交
257
    gchar *prefix;
A
Anthony Liguori 已提交
258
    gchar *title;
259
    const char *grab = "";
260
    bool is_paused = !runstate_is_running();
G
Gerd Hoffmann 已提交
261 262 263 264 265 266 267
    int i;

    if (qemu_name) {
        prefix = g_strdup_printf("QEMU (%s)", qemu_name);
    } else {
        prefix = g_strdup_printf("QEMU");
    }
268

G
Gerd Hoffmann 已提交
269 270
    if (s->ptr_owner != NULL &&
        s->ptr_owner->window == NULL) {
271
        grab = _(" - Press Ctrl+Alt+G to release grab");
272
    }
A
Anthony Liguori 已提交
273

274
    if (is_paused) {
275
        status = _(" [Paused]");
A
Anthony Liguori 已提交
276
    }
277 278 279 280
    s->external_pause_update = true;
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item),
                                   is_paused);
    s->external_pause_update = false;
A
Anthony Liguori 已提交
281

G
Gerd Hoffmann 已提交
282
    title = g_strdup_printf("%s%s%s", prefix, status, grab);
A
Anthony Liguori 已提交
283 284
    gtk_window_set_title(GTK_WINDOW(s->window), title);
    g_free(title);
G
Gerd Hoffmann 已提交
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

    for (i = 0; i < s->nb_vcs; i++) {
        VirtualConsole *vc = &s->vc[i];

        if (!vc->window) {
            continue;
        }
        title = g_strdup_printf("%s: %s%s%s", prefix, vc->label,
                                vc == s->kbd_owner ? " +kbd" : "",
                                vc == s->ptr_owner ? " +ptr" : "");
        gtk_window_set_title(GTK_WINDOW(vc->window), title);
        g_free(title);
    }

    g_free(prefix);
A
Anthony Liguori 已提交
300 301
}

G
Gerd Hoffmann 已提交
302
static void gd_update_geometry_hints(VirtualConsole *vc)
G
Gerd Hoffmann 已提交
303
{
304
    GtkDisplayState *s = vc->s;
G
Gerd Hoffmann 已提交
305 306 307 308
    GdkWindowHints mask = 0;
    GdkGeometry geo = {};
    GtkWidget *geo_widget = NULL;
    GtkWindow *geo_window;
309

G
Gerd Hoffmann 已提交
310
    if (vc->type == GD_VC_GFX) {
311 312 313
        if (!vc->gfx.ds) {
            return;
        }
G
Gerd Hoffmann 已提交
314 315 316 317 318 319 320 321 322 323 324
        if (s->free_scale) {
            geo.min_width  = surface_width(vc->gfx.ds) * VC_SCALE_MIN;
            geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN;
            mask |= GDK_HINT_MIN_SIZE;
        } else {
            geo.min_width  = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
            geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
            mask |= GDK_HINT_MIN_SIZE;
        }
        geo_widget = vc->gfx.drawing_area;
        gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height);
G
Gerd Hoffmann 已提交
325

G
Gerd Hoffmann 已提交
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
#if defined(CONFIG_VTE)
    } else if (vc->type == GD_VC_VTE) {
        VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
        GtkBorder *ib;

        geo.width_inc  = vte_terminal_get_char_width(term);
        geo.height_inc = vte_terminal_get_char_height(term);
        mask |= GDK_HINT_RESIZE_INC;
        geo.base_width  = geo.width_inc;
        geo.base_height = geo.height_inc;
        mask |= GDK_HINT_BASE_SIZE;
        geo.min_width  = geo.width_inc * VC_TERM_X_MIN;
        geo.min_height = geo.height_inc * VC_TERM_Y_MIN;
        mask |= GDK_HINT_MIN_SIZE;
        gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL);
        geo.base_width  += ib->left + ib->right;
        geo.base_height += ib->top + ib->bottom;
        geo.min_width   += ib->left + ib->right;
        geo.min_height  += ib->top + ib->bottom;
        geo_widget = vc->vte.terminal;
#endif
G
Gerd Hoffmann 已提交
347
    }
G
Gerd Hoffmann 已提交
348 349 350 351 352

    geo_window = GTK_WINDOW(vc->window ? vc->window : s->window);
    gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask);
}

353
void gd_update_windowsize(VirtualConsole *vc)
G
Gerd Hoffmann 已提交
354 355 356 357 358 359 360 361
{
    GtkDisplayState *s = vc->s;

    gd_update_geometry_hints(vc);

    if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) {
        gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window),
                          VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN);
G
Gerd Hoffmann 已提交
362
    }
G
Gerd Hoffmann 已提交
363 364
}

365
static void gd_update_full_redraw(VirtualConsole *vc)
G
Gerd Hoffmann 已提交
366
{
367
    GtkWidget *area = vc->gfx.drawing_area;
G
Gerd Hoffmann 已提交
368
    int ww, wh;
369 370
    gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh);
    gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
G
Gerd Hoffmann 已提交
371 372
}

373 374
static void gtk_release_modifiers(GtkDisplayState *s)
{
375
    VirtualConsole *vc = gd_vc_find_current(s);
376 377
    int i, keycode;

378 379
    if (vc->type != GD_VC_GFX ||
        !qemu_console_is_graphic(vc->gfx.dcl.con)) {
380 381 382 383 384 385 386
        return;
    }
    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
        keycode = modifier_keycode[i];
        if (!s->modifier_pressed[i]) {
            continue;
        }
387
        qemu_input_event_send_key_number(vc->gfx.dcl.con, keycode, false);
388 389 390 391
        s->modifier_pressed[i] = false;
    }
}

392 393 394 395 396 397 398 399 400
static void gd_widget_reparent(GtkWidget *from, GtkWidget *to,
                               GtkWidget *widget)
{
    g_object_ref(G_OBJECT(widget));
    gtk_container_remove(GTK_CONTAINER(from), widget);
    gtk_container_add(GTK_CONTAINER(to), widget);
    g_object_unref(G_OBJECT(widget));
}

A
Anthony Liguori 已提交
401 402
/** DisplayState Callbacks **/

403
static void gd_update(DisplayChangeListener *dcl,
404
                      int x, int y, int w, int h)
A
Anthony Liguori 已提交
405
{
406
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
407
    GdkWindow *win;
A
Anthony Liguori 已提交
408
    int x1, x2, y1, y2;
409 410 411
    int mx, my;
    int fbw, fbh;
    int ww, wh;
A
Anthony Liguori 已提交
412

G
Gerd Hoffmann 已提交
413
    trace_gd_update(vc->label, x, y, w, h);
A
Anthony Liguori 已提交
414

415 416 417 418
    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
        return;
    }

419 420 421
    if (vc->gfx.convert) {
        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
                               NULL, vc->gfx.convert,
422 423 424
                               x, y, 0, 0, x, y, w, h);
    }

425 426
    x1 = floor(x * vc->gfx.scale_x);
    y1 = floor(y * vc->gfx.scale_y);
A
Anthony Liguori 已提交
427

428 429
    x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
    y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
A
Anthony Liguori 已提交
430

431 432
    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
433

434 435 436 437 438
    win = gtk_widget_get_window(vc->gfx.drawing_area);
    if (!win) {
        return;
    }
    gdk_drawable_get_size(win, &ww, &wh);
439 440 441 442 443 444 445 446 447

    mx = my = 0;
    if (ww > fbw) {
        mx = (ww - fbw) / 2;
    }
    if (wh > fbh) {
        my = (wh - fbh) / 2;
    }

448 449
    gtk_widget_queue_draw_area(vc->gfx.drawing_area,
                               mx + x1, my + y1, (x2 - x1), (y2 - y1));
A
Anthony Liguori 已提交
450 451
}

452
static void gd_refresh(DisplayChangeListener *dcl)
A
Anthony Liguori 已提交
453
{
454
    graphic_hw_update(dcl->con);
A
Anthony Liguori 已提交
455 456
}

457 458 459 460
#if GTK_CHECK_VERSION(3, 0, 0)
static void gd_mouse_set(DisplayChangeListener *dcl,
                         int x, int y, int visible)
{
461
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
462 463 464 465
    GdkDisplay *dpy;
    GdkDeviceManager *mgr;
    gint x_root, y_root;

C
Cole Robinson 已提交
466 467 468 469
    if (qemu_input_is_absolute()) {
        return;
    }

470
    dpy = gtk_widget_get_display(vc->gfx.drawing_area);
471
    mgr = gdk_display_get_device_manager(dpy);
472
    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
473 474
                               x, y, &x_root, &y_root);
    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
475
                    gtk_widget_get_screen(vc->gfx.drawing_area),
C
Cole Robinson 已提交
476
                    x_root, y_root);
477 478
    vc->s->last_x = x;
    vc->s->last_y = y;
479 480
}
#else
G
Gerd Hoffmann 已提交
481 482 483
static void gd_mouse_set(DisplayChangeListener *dcl,
                         int x, int y, int visible)
{
484
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
485 486
    gint x_root, y_root;

C
Cole Robinson 已提交
487 488 489 490
    if (qemu_input_is_absolute()) {
        return;
    }

491
    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
G
Gerd Hoffmann 已提交
492
                               x, y, &x_root, &y_root);
493 494
    gdk_display_warp_pointer(gtk_widget_get_display(vc->gfx.drawing_area),
                             gtk_widget_get_screen(vc->gfx.drawing_area),
G
Gerd Hoffmann 已提交
495 496
                             x_root, y_root);
}
497
#endif
G
Gerd Hoffmann 已提交
498 499 500 501

static void gd_cursor_define(DisplayChangeListener *dcl,
                             QEMUCursor *c)
{
502
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
503 504 505
    GdkPixbuf *pixbuf;
    GdkCursor *cursor;

506 507 508 509
    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
        return;
    }

G
Gerd Hoffmann 已提交
510 511 512 513
    pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
                                      GDK_COLORSPACE_RGB, true, 8,
                                      c->width, c->height, c->width * 4,
                                      NULL, NULL);
514 515 516 517
    cursor = gdk_cursor_new_from_pixbuf
        (gtk_widget_get_display(vc->gfx.drawing_area),
         pixbuf, c->hot_x, c->hot_y);
    gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor);
G
Gerd Hoffmann 已提交
518
    g_object_unref(pixbuf);
519
#if !GTK_CHECK_VERSION(3, 0, 0)
520
    gdk_cursor_unref(cursor);
521 522 523
#else
    g_object_unref(cursor);
#endif
G
Gerd Hoffmann 已提交
524 525
}

526 527
static void gd_switch(DisplayChangeListener *dcl,
                      DisplaySurface *surface)
A
Anthony Liguori 已提交
528
{
529
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
530
    bool resized = true;
A
Anthony Liguori 已提交
531

532 533 534
    trace_gd_switch(vc->label,
                    surface ? surface_width(surface)  : 0,
                    surface ? surface_height(surface) : 0);
A
Anthony Liguori 已提交
535

536 537
    if (vc->gfx.surface) {
        cairo_surface_destroy(vc->gfx.surface);
538 539 540 541 542
        vc->gfx.surface = NULL;
    }
    if (vc->gfx.convert) {
        pixman_image_unref(vc->gfx.convert);
        vc->gfx.convert = NULL;
A
Anthony Liguori 已提交
543 544
    }

545
    if (vc->gfx.ds && surface &&
546 547
        surface_width(vc->gfx.ds) == surface_width(surface) &&
        surface_height(vc->gfx.ds) == surface_height(surface)) {
G
Gerd Hoffmann 已提交
548 549
        resized = false;
    }
550
    vc->gfx.ds = surface;
A
Anthony Liguori 已提交
551

552 553
    if (!surface) {
        return;
554
    }
A
Anthony Liguori 已提交
555

556 557 558 559 560 561 562
    if (surface->format == PIXMAN_x8r8g8b8) {
        /*
         * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24
         *
         * No need to convert, use surface directly.  Should be the
         * common case as this is qemu_default_pixelformat(32) too.
         */
563
        vc->gfx.surface = cairo_image_surface_create_for_data
564 565 566 567 568 569 570
            (surface_data(surface),
             CAIRO_FORMAT_RGB24,
             surface_width(surface),
             surface_height(surface),
             surface_stride(surface));
    } else {
        /* Must convert surface, use pixman to do it. */
571 572 573 574 575 576
        vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
                                                   surface_width(surface),
                                                   surface_height(surface),
                                                   NULL, 0);
        vc->gfx.surface = cairo_image_surface_create_for_data
            ((void *)pixman_image_get_data(vc->gfx.convert),
577
             CAIRO_FORMAT_RGB24,
578 579 580 581 582
             pixman_image_get_width(vc->gfx.convert),
             pixman_image_get_height(vc->gfx.convert),
             pixman_image_get_stride(vc->gfx.convert));
        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
                               NULL, vc->gfx.convert,
583
                               0, 0, 0, 0, 0, 0,
584 585
                               pixman_image_get_width(vc->gfx.convert),
                               pixman_image_get_height(vc->gfx.convert));
586
    }
587

G
Gerd Hoffmann 已提交
588
    if (resized) {
589
        gd_update_windowsize(vc);
G
Gerd Hoffmann 已提交
590
    } else {
591
        gd_update_full_redraw(vc);
592
    }
A
Anthony Liguori 已提交
593 594
}

595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
static const DisplayChangeListenerOps dcl_ops = {
    .dpy_name             = "gtk",
    .dpy_gfx_update       = gd_update,
    .dpy_gfx_switch       = gd_switch,
    .dpy_gfx_check_format = qemu_pixman_check_format,
    .dpy_refresh          = gd_refresh,
    .dpy_mouse_set        = gd_mouse_set,
    .dpy_cursor_define    = gd_cursor_define,
};


#if defined(CONFIG_OPENGL)

/** DisplayState Callbacks (opengl version) **/

static const DisplayChangeListenerOps dcl_egl_ops = {
    .dpy_name             = "gtk-egl",
    .dpy_gfx_update       = gd_egl_update,
    .dpy_gfx_switch       = gd_egl_switch,
    .dpy_gfx_check_format = console_gl_check_format,
    .dpy_refresh          = gd_egl_refresh,
    .dpy_mouse_set        = gd_mouse_set,
    .dpy_cursor_define    = gd_cursor_define,
618 619 620 621 622 623 624

    .dpy_gl_ctx_create       = gd_egl_create_context,
    .dpy_gl_ctx_destroy      = qemu_egl_destroy_context,
    .dpy_gl_ctx_make_current = gd_egl_make_current,
    .dpy_gl_ctx_get_current  = qemu_egl_get_current_context,
    .dpy_gl_scanout          = gd_egl_scanout,
    .dpy_gl_update           = gd_egl_scanout_flush,
625 626 627 628
};

#endif

A
Anthony Liguori 已提交
629 630 631 632 633 634 635 636 637 638 639
/** QEMU Events **/

static void gd_change_runstate(void *opaque, int running, RunState state)
{
    GtkDisplayState *s = opaque;

    gd_update_caption(s);
}

static void gd_mouse_mode_change(Notifier *notify, void *data)
{
640
    GtkDisplayState *s;
641
    int i;
642 643 644 645 646 647 648

    s = container_of(notify, GtkDisplayState, mouse_mode_notifier);
    /* release the grab at switching to absolute mode */
    if (qemu_input_is_absolute() && gd_is_grab_active(s)) {
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       FALSE);
    }
649 650 651 652
    for (i = 0; i < s->nb_vcs; i++) {
        VirtualConsole *vc = &s->vc[i];
        gd_update_cursor(vc);
    }
A
Anthony Liguori 已提交
653 654 655 656 657 658 659 660
}

/** GTK Events **/

static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
                                void *opaque)
{
    GtkDisplayState *s = opaque;
661
    int i;
A
Anthony Liguori 已提交
662 663

    if (!no_quit) {
664 665 666 667 668 669
        for (i = 0; i < s->nb_vcs; i++) {
            if (s->vc[i].type != GD_VC_GFX) {
                continue;
            }
            unregister_displaychangelistener(&s->vc[i].gfx.dcl);
        }
A
Anthony Liguori 已提交
670 671 672 673 674 675 676 677 678
        qmp_quit(NULL);
        return FALSE;
    }

    return TRUE;
}

static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
{
679 680
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
681
    int mx, my;
A
Anthony Liguori 已提交
682 683 684
    int ww, wh;
    int fbw, fbh;

685 686 687 688 689 690 691
#if defined(CONFIG_OPENGL)
    if (vc->gfx.gls) {
        gd_egl_draw(vc);
        return TRUE;
    }
#endif

692 693 694
    if (!gtk_widget_get_realized(widget)) {
        return FALSE;
    }
695 696 697
    if (!vc->gfx.ds) {
        return FALSE;
    }
698

699 700
    fbw = surface_width(vc->gfx.ds);
    fbh = surface_height(vc->gfx.ds);
A
Anthony Liguori 已提交
701 702 703

    gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);

704
    if (s->full_screen) {
705 706
        vc->gfx.scale_x = (double)ww / fbw;
        vc->gfx.scale_y = (double)wh / fbh;
707 708 709 710 711 712
    } else if (s->free_scale) {
        double sx, sy;

        sx = (double)ww / fbw;
        sy = (double)wh / fbh;

713
        vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
A
Anthony Liguori 已提交
714 715
    }

716 717
    fbw *= vc->gfx.scale_x;
    fbh *= vc->gfx.scale_y;
718

719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    mx = my = 0;
    if (ww > fbw) {
        mx = (ww - fbw) / 2;
    }
    if (wh > fbh) {
        my = (wh - fbh) / 2;
    }

    cairo_rectangle(cr, 0, 0, ww, wh);

    /* Optionally cut out the inner area where the pixmap
       will be drawn. This avoids 'flashing' since we're
       not double-buffering. Note we're using the undocumented
       behaviour of drawing the rectangle from right to left
       to cut out the whole */
    cairo_rectangle(cr, mx + fbw, my,
                    -1 * fbw, fbh);
    cairo_fill(cr);

738 739 740
    cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
    cairo_set_source_surface(cr, vc->gfx.surface,
                             mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
A
Anthony Liguori 已提交
741 742 743 744 745
    cairo_paint(cr);

    return TRUE;
}

746
#if !GTK_CHECK_VERSION(3, 0, 0)
A
Anthony Liguori 已提交
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766
static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
                                void *opaque)
{
    cairo_t *cr;
    gboolean ret;

    cr = gdk_cairo_create(gtk_widget_get_window(widget));
    cairo_rectangle(cr,
                    expose->area.x,
                    expose->area.y,
                    expose->area.width,
                    expose->area.height);
    cairo_clip(cr);

    ret = gd_draw_event(widget, cr, opaque);

    cairo_destroy(cr);

    return ret;
}
767
#endif
A
Anthony Liguori 已提交
768 769 770 771

static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
                                void *opaque)
{
772 773
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
A
Anthony Liguori 已提交
774
    int x, y;
775 776 777 778
    int mx, my;
    int fbh, fbw;
    int ww, wh;

779 780 781 782
    if (!vc->gfx.ds) {
        return TRUE;
    }

783 784
    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
A
Anthony Liguori 已提交
785

786 787
    gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
                          &ww, &wh);
788 789 790 791 792 793 794 795 796

    mx = my = 0;
    if (ww > fbw) {
        mx = (ww - fbw) / 2;
    }
    if (wh > fbh) {
        my = (wh - fbh) / 2;
    }

797 798
    x = (motion->x - mx) / vc->gfx.scale_x;
    y = (motion->y - my) / vc->gfx.scale_y;
799

800
    if (qemu_input_is_absolute()) {
801
        if (x < 0 || y < 0 ||
802 803
            x >= surface_width(vc->gfx.ds) ||
            y >= surface_height(vc->gfx.ds)) {
804 805
            return TRUE;
        }
806 807 808 809
        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
                             surface_width(vc->gfx.ds));
        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
                             surface_height(vc->gfx.ds));
810
        qemu_input_event_sync();
G
Gerd Hoffmann 已提交
811
    } else if (s->last_set && s->ptr_owner == vc) {
812 813
        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
814
        qemu_input_event_sync();
A
Anthony Liguori 已提交
815 816 817
    }
    s->last_x = x;
    s->last_y = y;
818
    s->last_set = TRUE;
A
Anthony Liguori 已提交
819

G
Gerd Hoffmann 已提交
820
    if (!qemu_input_is_absolute() && s->ptr_owner == vc) {
821
        GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
        int x = (int)motion->x_root;
        int y = (int)motion->y_root;

        /* In relative mode check to see if client pointer hit
         * one of the screen edges, and if so move it back by
         * 200 pixels. This is important because the pointer
         * in the server doesn't correspond 1-for-1, and so
         * may still be only half way across the screen. Without
         * this warp, the server pointer would thus appear to hit
         * an invisible wall */
        if (x == 0) {
            x += 200;
        }
        if (y == 0) {
            y += 200;
        }
        if (x == (gdk_screen_get_width(screen) - 1)) {
            x -= 200;
        }
        if (y == (gdk_screen_get_height(screen) - 1)) {
            y -= 200;
        }

        if (x != (int)motion->x_root || y != (int)motion->y_root) {
846 847 848 849 850
#if GTK_CHECK_VERSION(3, 0, 0)
            GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
            gdk_device_warp(dev, screen, x, y);
#else
            GdkDisplay *display = gtk_widget_get_display(widget);
851
            gdk_display_warp_pointer(display, screen, x, y);
852
#endif
853
            s->last_set = FALSE;
854 855 856
            return FALSE;
        }
    }
A
Anthony Liguori 已提交
857 858 859 860 861 862
    return TRUE;
}

static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
                                void *opaque)
{
863 864
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
865
    InputButton btn;
A
Anthony Liguori 已提交
866

867 868
    /* implicitly grab the input at the first click in the relative mode */
    if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
G
Gerd Hoffmann 已提交
869 870 871 872 873
        !qemu_input_is_absolute() && s->ptr_owner != vc) {
        if (!vc->window) {
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                           TRUE);
        } else {
G
Gerd Hoffmann 已提交
874
            gd_grab_pointer(vc, "relative-mode-click");
G
Gerd Hoffmann 已提交
875
        }
876 877 878
        return TRUE;
    }

A
Anthony Liguori 已提交
879
    if (button->button == 1) {
880
        btn = INPUT_BUTTON_LEFT;
A
Anthony Liguori 已提交
881
    } else if (button->button == 2) {
882
        btn = INPUT_BUTTON_MIDDLE;
A
Anthony Liguori 已提交
883
    } else if (button->button == 3) {
884
        btn = INPUT_BUTTON_RIGHT;
A
Anthony Liguori 已提交
885
    } else {
886
        return TRUE;
A
Anthony Liguori 已提交
887 888
    }

889 890
    qemu_input_queue_btn(vc->gfx.dcl.con, btn,
                         button->type == GDK_BUTTON_PRESS);
891
    qemu_input_event_sync();
A
Anthony Liguori 已提交
892 893 894
    return TRUE;
}

J
Jan Kiszka 已提交
895 896 897
static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
                                void *opaque)
{
898
    VirtualConsole *vc = opaque;
J
Jan Kiszka 已提交
899 900 901 902 903 904 905 906 907 908
    InputButton btn;

    if (scroll->direction == GDK_SCROLL_UP) {
        btn = INPUT_BUTTON_WHEEL_UP;
    } else if (scroll->direction == GDK_SCROLL_DOWN) {
        btn = INPUT_BUTTON_WHEEL_DOWN;
    } else {
        return TRUE;
    }

909
    qemu_input_queue_btn(vc->gfx.dcl.con, btn, true);
J
Jan Kiszka 已提交
910
    qemu_input_event_sync();
911
    qemu_input_queue_btn(vc->gfx.dcl.con, btn, false);
J
Jan Kiszka 已提交
912 913 914 915
    qemu_input_event_sync();
    return TRUE;
}

916
static int gd_map_keycode(GtkDisplayState *s, GdkDisplay *dpy, int gdk_keycode)
A
Anthony Liguori 已提交
917
{
G
Gerd Hoffmann 已提交
918
    int qemu_keycode;
A
Anthony Liguori 已提交
919

920 921 922 923 924 925 926 927 928
#ifdef GDK_WINDOWING_WIN32
    if (GDK_IS_WIN32_DISPLAY(dpy)) {
        qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC);
        switch (qemu_keycode) {
        case 103:   /* alt gr */
            qemu_keycode = 56 | SCANCODE_GREY;
            break;
        }
        return qemu_keycode;
929
    }
930
#endif
A
Anthony Liguori 已提交
931 932 933 934 935

    if (gdk_keycode < 9) {
        qemu_keycode = 0;
    } else if (gdk_keycode < 97) {
        qemu_keycode = gdk_keycode - 8;
936 937
#ifdef GDK_WINDOWING_X11
    } else if (GDK_IS_X11_DISPLAY(dpy) && gdk_keycode < 158) {
938 939 940 941 942
        if (s->has_evdev) {
            qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
        } else {
            qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97);
        }
943
#endif
A
Anthony Liguori 已提交
944 945 946 947 948 949 950 951
    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
        qemu_keycode = 0x70;
    } else if (gdk_keycode == 211) { /* backslash */
        qemu_keycode = 0x73;
    } else {
        qemu_keycode = 0;
    }

G
Gerd Hoffmann 已提交
952 953 954
    return qemu_keycode;
}

955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971
static gboolean gd_text_key_down(GtkWidget *widget,
                                 GdkEventKey *key, void *opaque)
{
    VirtualConsole *vc = opaque;
    QemuConsole *con = vc->gfx.dcl.con;

    if (key->length) {
        kbd_put_string_console(con, key->string, key->length);
    } else {
        int num = gd_map_keycode(vc->s, gtk_widget_get_display(widget),
                                 key->hardware_keycode);
        int qcode = qemu_input_key_number_to_qcode(num);
        kbd_put_qcode_console(con, qcode);
    }
    return TRUE;
}

G
Gerd Hoffmann 已提交
972 973 974 975 976 977 978 979
static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
{
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
    int gdk_keycode = key->hardware_keycode;
    int qemu_keycode;
    int i;

980 981 982 983 984
    if (s->ignore_keys) {
        s->ignore_keys = (key->type == GDK_KEY_PRESS);
        return TRUE;
    }

M
Martin Decky 已提交
985 986 987 988 989 990
    if (key->keyval == GDK_KEY_Pause) {
        qemu_input_event_send_key_qcode(vc->gfx.dcl.con, Q_KEY_CODE_PAUSE,
                                        key->type == GDK_KEY_PRESS);
        return TRUE;
    }

991 992
    qemu_keycode = gd_map_keycode(s, gtk_widget_get_display(widget),
                                  gdk_keycode);
G
Gerd Hoffmann 已提交
993

G
Gerd Hoffmann 已提交
994
    trace_gd_key_event(vc->label, gdk_keycode, qemu_keycode,
995
                       (key->type == GDK_KEY_PRESS) ? "down" : "up");
A
Anthony Liguori 已提交
996

997 998 999 1000 1001 1002
    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
        if (qemu_keycode == modifier_keycode[i]) {
            s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS);
        }
    }

1003
    qemu_input_event_send_key_number(vc->gfx.dcl.con, qemu_keycode,
1004
                                     key->type == GDK_KEY_PRESS);
A
Anthony Liguori 已提交
1005 1006 1007 1008

    return TRUE;
}

1009 1010 1011 1012 1013 1014 1015 1016
static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque)
{
    if (event->type == GDK_MOTION_NOTIFY) {
        return gd_motion_event(widget, &event->motion, opaque);
    }
    return FALSE;
}

A
Anthony Liguori 已提交
1017 1018
/** Window Menu Actions **/

1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
static void gd_menu_pause(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;

    if (s->external_pause_update) {
        return;
    }
    if (runstate_is_running()) {
        qmp_stop(NULL);
    } else {
        qmp_cont(NULL);
    }
}

static void gd_menu_reset(GtkMenuItem *item, void *opaque)
{
    qmp_system_reset(NULL);
}

static void gd_menu_powerdown(GtkMenuItem *item, void *opaque)
{
    qmp_system_powerdown(NULL);
}

A
Anthony Liguori 已提交
1043 1044 1045 1046 1047 1048 1049 1050
static void gd_menu_quit(GtkMenuItem *item, void *opaque)
{
    qmp_quit(NULL);
}

static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1051
    VirtualConsole *vc = gd_vc_find_by_menu(s);
1052
    GtkNotebook *nb = GTK_NOTEBOOK(s->notebook);
1053
    gint page;
A
Anthony Liguori 已提交
1054

1055 1056
    gtk_release_modifiers(s);
    if (vc) {
1057 1058
        page = gtk_notebook_page_num(nb, vc->tab_item);
        gtk_notebook_set_current_page(nb, page);
J
Jan Kiszka 已提交
1059
        gtk_widget_grab_focus(vc->focus);
A
Anthony Liguori 已提交
1060
    }
1061
    s->ignore_keys = false;
A
Anthony Liguori 已提交
1062 1063
}

1064 1065 1066
static void gd_accel_switch_vc(void *opaque)
{
    VirtualConsole *vc = opaque;
1067

1068
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
1069 1070 1071 1072
#if !GTK_CHECK_VERSION(3, 0, 0)
    /* GTK2 sends the accel key to the target console - ignore this until */
    vc->s->ignore_keys = true;
#endif
1073 1074
}

A
Anthony Liguori 已提交
1075 1076 1077
static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1078
    VirtualConsole *vc = gd_vc_find_current(s);
A
Anthony Liguori 已提交
1079 1080 1081 1082 1083 1084

    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
    } else {
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
    }
1085
    gd_update_windowsize(vc);
A
Anthony Liguori 已提交
1086 1087
}

1088 1089 1090 1091 1092 1093 1094
static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
                                    void *opaque)
{
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;

    gtk_widget_set_sensitive(vc->menu_item, true);
1095
    gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
1096 1097 1098 1099 1100 1101 1102
    gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
                                    vc->tab_item, vc->label);
    gtk_widget_destroy(vc->window);
    vc->window = NULL;
    return TRUE;
}

1103 1104 1105 1106 1107 1108 1109 1110
static gboolean gd_win_grab(void *opaque)
{
    VirtualConsole *vc = opaque;

    fprintf(stderr, "%s: %s\n", __func__, vc->label);
    if (vc->s->ptr_owner) {
        gd_ungrab_pointer(vc->s);
    } else {
G
Gerd Hoffmann 已提交
1111
        gd_grab_pointer(vc, "user-request-detached-tab");
1112 1113 1114 1115
    }
    return TRUE;
}

1116 1117 1118 1119 1120
static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
    VirtualConsole *vc = gd_vc_find_current(s);

1121 1122
    if (vc->type == GD_VC_GFX &&
        qemu_console_is_graphic(vc->gfx.dcl.con)) {
G
Gerd Hoffmann 已提交
1123 1124
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       FALSE);
1125 1126 1127 1128
    }
    if (!vc->window) {
        gtk_widget_set_sensitive(vc->menu_item, false);
        vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1129
        gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
1130 1131 1132 1133

        g_signal_connect(vc->window, "delete-event",
                         G_CALLBACK(gd_tab_window_close), vc);
        gtk_widget_show_all(vc->window);
G
Gerd Hoffmann 已提交
1134

1135 1136 1137
        if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
            GtkAccelGroup *ag = gtk_accel_group_new();
            gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
1138

1139 1140 1141 1142
            GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
                                               vc, NULL);
            gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
        }
1143

G
Gerd Hoffmann 已提交
1144
        gd_update_geometry_hints(vc);
G
Gerd Hoffmann 已提交
1145
        gd_update_caption(s);
1146 1147 1148
    }
}

1149 1150 1151
static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1152
    VirtualConsole *vc = gd_vc_find_current(s);
1153

1154
    if (!s->full_screen) {
1155
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
1156
        gtk_widget_hide(s->menu_bar);
1157 1158
        if (vc->type == GD_VC_GFX) {
            gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
1159
        }
1160
        gtk_window_fullscreen(GTK_WINDOW(s->window));
1161 1162 1163 1164
        s->full_screen = TRUE;
    } else {
        gtk_window_unfullscreen(GTK_WINDOW(s->window));
        gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
1165
        gtk_widget_show(s->menu_bar);
1166
        s->full_screen = FALSE;
1167 1168 1169
        if (vc->type == GD_VC_GFX) {
            vc->gfx.scale_x = 1.0;
            vc->gfx.scale_y = 1.0;
G
Gerd Hoffmann 已提交
1170
            gd_update_windowsize(vc);
1171
        }
1172 1173
    }

1174
    gd_update_cursor(vc);
1175 1176
}

1177 1178 1179 1180 1181 1182
static void gd_accel_full_screen(void *opaque)
{
    GtkDisplayState *s = opaque;
    gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
}

1183 1184 1185
static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1186
    VirtualConsole *vc = gd_vc_find_current(s);
1187 1188 1189 1190

    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
                                   FALSE);

G
Gerd Hoffmann 已提交
1191 1192
    vc->gfx.scale_x += VC_SCALE_STEP;
    vc->gfx.scale_y += VC_SCALE_STEP;
1193

1194
    gd_update_windowsize(vc);
1195 1196 1197 1198 1199
}

static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1200
    VirtualConsole *vc = gd_vc_find_current(s);
1201 1202 1203 1204

    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
                                   FALSE);

G
Gerd Hoffmann 已提交
1205 1206
    vc->gfx.scale_x -= VC_SCALE_STEP;
    vc->gfx.scale_y -= VC_SCALE_STEP;
1207

G
Gerd Hoffmann 已提交
1208 1209
    vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN);
    vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN);
1210

1211
    gd_update_windowsize(vc);
1212 1213 1214 1215 1216
}

static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1217
    VirtualConsole *vc = gd_vc_find_current(s);
1218

1219 1220
    vc->gfx.scale_x = 1.0;
    vc->gfx.scale_y = 1.0;
1221

1222
    gd_update_windowsize(vc);
1223 1224 1225 1226 1227
}

static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1228
    VirtualConsole *vc = gd_vc_find_current(s);
1229 1230 1231 1232 1233

    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
        s->free_scale = TRUE;
    } else {
        s->free_scale = FALSE;
1234 1235
        vc->gfx.scale_x = 1.0;
        vc->gfx.scale_y = 1.0;
1236 1237
    }

G
Gerd Hoffmann 已提交
1238
    gd_update_windowsize(vc);
1239
    gd_update_full_redraw(vc);
1240 1241
}

1242
#if GTK_CHECK_VERSION(3, 0, 0)
1243 1244 1245 1246
static void gd_grab_devices(VirtualConsole *vc, bool grab,
                            GdkInputSource source, GdkEventMask mask,
                            GdkCursor *cursor)
{
1247
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1248
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1249 1250 1251 1252
    GList *devs = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER);
    GList *tmp = devs;

    for (tmp = devs; tmp; tmp = tmp->next) {
1253
        GdkDevice *dev = tmp->data;
1254 1255 1256 1257 1258 1259 1260 1261 1262
        if (gdk_device_get_source(dev) != source) {
            continue;
        }
        if (grab) {
            GdkWindow *win = gtk_widget_get_window(vc->gfx.drawing_area);
            gdk_device_grab(dev, win, GDK_OWNERSHIP_NONE, FALSE,
                            mask, cursor, GDK_CURRENT_TIME);
        } else {
            gdk_device_ungrab(dev, GDK_CURRENT_TIME);
1263 1264
        }
    }
1265 1266 1267 1268
    g_list_free(devs);
}
#endif

G
Gerd Hoffmann 已提交
1269
static void gd_grab_keyboard(VirtualConsole *vc, const char *reason)
1270
{
1271 1272 1273 1274 1275 1276 1277 1278
    if (vc->s->kbd_owner) {
        if (vc->s->kbd_owner == vc) {
            return;
        } else {
            gd_ungrab_keyboard(vc->s);
        }
    }

1279 1280 1281 1282
#if GTK_CHECK_VERSION(3, 0, 0)
    gd_grab_devices(vc, true, GDK_SOURCE_KEYBOARD,
                   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
                   NULL);
1283
#else
1284
    gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1285 1286
                      FALSE,
                      GDK_CURRENT_TIME);
1287
#endif
G
Gerd Hoffmann 已提交
1288
    vc->s->kbd_owner = vc;
1289
    gd_update_caption(vc->s);
G
Gerd Hoffmann 已提交
1290
    trace_gd_grab(vc->label, "kbd", reason);
1291 1292
}

G
Gerd Hoffmann 已提交
1293
static void gd_ungrab_keyboard(GtkDisplayState *s)
1294
{
G
Gerd Hoffmann 已提交
1295 1296 1297 1298 1299 1300 1301
    VirtualConsole *vc = s->kbd_owner;

    if (vc == NULL) {
        return;
    }
    s->kbd_owner = NULL;

1302
#if GTK_CHECK_VERSION(3, 0, 0)
1303
    gd_grab_devices(vc, false, GDK_SOURCE_KEYBOARD, 0, NULL);
1304
#else
1305
    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
1306
#endif
1307
    gd_update_caption(s);
G
Gerd Hoffmann 已提交
1308
    trace_gd_ungrab(vc->label, "kbd");
1309 1310
}

G
Gerd Hoffmann 已提交
1311
static void gd_grab_pointer(VirtualConsole *vc, const char *reason)
1312
{
1313
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1314 1315 1316 1317 1318 1319 1320 1321 1322

    if (vc->s->ptr_owner) {
        if (vc->s->ptr_owner == vc) {
            return;
        } else {
            gd_ungrab_pointer(vc->s);
        }
    }

1323
#if GTK_CHECK_VERSION(3, 0, 0)
1324
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1325 1326 1327 1328 1329 1330 1331
    gd_grab_devices(vc, true, GDK_SOURCE_MOUSE,
                    GDK_POINTER_MOTION_MASK |
                    GDK_BUTTON_PRESS_MASK |
                    GDK_BUTTON_RELEASE_MASK |
                    GDK_BUTTON_MOTION_MASK |
                    GDK_SCROLL_MASK,
                    vc->s->null_cursor);
1332
    gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr),
1333
                            NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
1334
#else
1335
    gdk_pointer_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1336 1337 1338 1339 1340 1341 1342
                     FALSE, /* All events to come to our window directly */
                     GDK_POINTER_MOTION_MASK |
                     GDK_BUTTON_PRESS_MASK |
                     GDK_BUTTON_RELEASE_MASK |
                     GDK_BUTTON_MOTION_MASK |
                     GDK_SCROLL_MASK,
                     NULL, /* Allow cursor to move over entire desktop */
1343
                     vc->s->null_cursor,
1344
                     GDK_CURRENT_TIME);
1345
    gdk_display_get_pointer(display, NULL,
1346
                            &vc->s->grab_x_root, &vc->s->grab_y_root, NULL);
1347
#endif
G
Gerd Hoffmann 已提交
1348
    vc->s->ptr_owner = vc;
1349
    gd_update_caption(vc->s);
G
Gerd Hoffmann 已提交
1350
    trace_gd_grab(vc->label, "ptr", reason);
1351 1352
}

G
Gerd Hoffmann 已提交
1353
static void gd_ungrab_pointer(GtkDisplayState *s)
1354
{
G
Gerd Hoffmann 已提交
1355 1356 1357 1358 1359 1360 1361
    VirtualConsole *vc = s->ptr_owner;

    if (vc == NULL) {
        return;
    }
    s->ptr_owner = NULL;

1362
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1363
#if GTK_CHECK_VERSION(3, 0, 0)
1364
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
1365
    gd_grab_devices(vc, false, GDK_SOURCE_MOUSE, 0, NULL);
1366
    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
1367 1368
                    gtk_widget_get_screen(vc->gfx.drawing_area),
                    vc->s->grab_x_root, vc->s->grab_y_root);
1369 1370
#else
    gdk_pointer_ungrab(GDK_CURRENT_TIME);
1371
    gdk_display_warp_pointer(display,
1372 1373
                             gtk_widget_get_screen(vc->gfx.drawing_area),
                             vc->s->grab_x_root, vc->s->grab_y_root);
1374
#endif
1375
    gd_update_caption(s);
G
Gerd Hoffmann 已提交
1376
    trace_gd_ungrab(vc->label, "ptr");
1377 1378
}

1379 1380 1381
static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1382
    VirtualConsole *vc = gd_vc_find_current(s);
1383 1384

    if (gd_is_grab_active(s)) {
G
Gerd Hoffmann 已提交
1385 1386
        gd_grab_keyboard(vc, "user-request-main-window");
        gd_grab_pointer(vc, "user-request-main-window");
1387
    } else {
G
Gerd Hoffmann 已提交
1388 1389
        gd_ungrab_keyboard(s);
        gd_ungrab_pointer(s);
1390 1391
    }

1392
    gd_update_cursor(vc);
1393 1394
}

A
Anthony Liguori 已提交
1395 1396 1397 1398
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
                           gpointer data)
{
    GtkDisplayState *s = data;
1399
    VirtualConsole *vc;
1400
    gboolean on_vga;
A
Anthony Liguori 已提交
1401 1402 1403 1404 1405

    if (!gtk_widget_get_realized(s->notebook)) {
        return;
    }

1406 1407 1408 1409 1410 1411
#ifdef VTE_RESIZE_HACK
    vc = gd_vc_find_current(s);
    if (vc && vc->type == GD_VC_VTE) {
        gtk_widget_hide(vc->vte.terminal);
    }
#endif
1412 1413 1414 1415
    vc = gd_vc_find_by_page(s, arg2);
    if (!vc) {
        return;
    }
1416 1417 1418 1419 1420
#ifdef VTE_RESIZE_HACK
    if (vc->type == GD_VC_VTE) {
        gtk_widget_show(vc->vte.terminal);
    }
#endif
1421 1422
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
                                   TRUE);
1423 1424
    on_vga = (vc->type == GD_VC_GFX &&
              qemu_console_is_graphic(vc->gfx.dcl.con));
1425 1426 1427
    if (!on_vga) {
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       FALSE);
1428 1429 1430
    } else if (s->full_screen) {
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       TRUE);
1431 1432 1433
    }
    gtk_widget_set_sensitive(s->grab_item, on_vga);

G
Gerd Hoffmann 已提交
1434
    gd_update_windowsize(vc);
1435
    gd_update_cursor(vc);
A
Anthony Liguori 已提交
1436 1437
}

1438 1439
static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
                               gpointer opaque)
1440
{
1441 1442
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1443

G
Gerd Hoffmann 已提交
1444
    if (gd_grab_on_hover(s)) {
G
Gerd Hoffmann 已提交
1445
        gd_grab_keyboard(vc, "grab-on-hover");
1446 1447 1448 1449
    }
    return TRUE;
}

1450 1451
static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
                               gpointer opaque)
1452
{
1453 1454
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1455

G
Gerd Hoffmann 已提交
1456
    if (gd_grab_on_hover(s)) {
G
Gerd Hoffmann 已提交
1457
        gd_ungrab_keyboard(s);
1458 1459 1460 1461
    }
    return TRUE;
}

1462
static gboolean gd_focus_out_event(GtkWidget *widget,
1463
                                   GdkEventCrossing *crossing, gpointer opaque)
1464
{
1465 1466
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1467 1468 1469 1470 1471

    gtk_release_modifiers(s);
    return TRUE;
}

G
Gerd Hoffmann 已提交
1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484
static gboolean gd_configure(GtkWidget *widget,
                             GdkEventConfigure *cfg, gpointer opaque)
{
    VirtualConsole *vc = opaque;
    QemuUIInfo info;

    memset(&info, 0, sizeof(info));
    info.width = cfg->width;
    info.height = cfg->height;
    dpy_set_ui_info(vc->gfx.dcl.con, &info);
    return FALSE;
}

1485 1486
/** Virtual Console Callbacks **/

1487
static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
1488
                               int idx, GSList *group, GtkWidget *view_menu)
1489
{
1490
    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
1491 1492 1493 1494 1495 1496 1497 1498
    gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
            HOTKEY_MODIFIERS, 0,
            g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
#if GTK_CHECK_VERSION(3, 8, 0)
    gtk_accel_label_set_accel(
            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
            GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
#endif
1499 1500 1501 1502 1503

    g_signal_connect(vc->menu_item, "activate",
                     G_CALLBACK(gd_menu_switch_vc), s);
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);

1504
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
1505 1506 1507
    return group;
}

1508
#if defined(CONFIG_VTE)
1509 1510 1511 1512 1513 1514
static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
{
    VirtualConsole *vc = opaque;

    if (gtk_adjustment_get_upper(adjustment) >
        gtk_adjustment_get_page_size(adjustment)) {
G
Gerd Hoffmann 已提交
1515
        gtk_widget_show(vc->vte.scrollbar);
1516
    } else {
G
Gerd Hoffmann 已提交
1517
        gtk_widget_hide(vc->vte.scrollbar);
1518 1519 1520
    }
}

1521 1522 1523 1524
static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
{
    VirtualConsole *vc = chr->opaque;

G
Gerd Hoffmann 已提交
1525
    vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
C
Cole Robinson 已提交
1526
    return len;
1527 1528 1529 1530 1531
}

static int nb_vcs;
static CharDriverState *vcs[MAX_VCS];

G
Gerd Hoffmann 已提交
1532
static CharDriverState *gd_vc_handler(ChardevVC *unused)
1533 1534 1535 1536 1537
{
    CharDriverState *chr;

    chr = g_malloc0(sizeof(*chr));
    chr->chr_write = gd_vc_chr_write;
1538 1539
    /* defer OPENED events until our vc is fully initialized */
    chr->explicit_be_open = true;
1540 1541 1542 1543 1544 1545

    vcs[nb_vcs++] = chr;

    return chr;
}

C
Cole Robinson 已提交
1546 1547
static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
                         gpointer user_data)
1548
{
C
Cole Robinson 已提交
1549
    VirtualConsole *vc = user_data;
1550

G
Gerd Hoffmann 已提交
1551
    qemu_chr_be_write(vc->vte.chr, (uint8_t  *)text, (unsigned int)size);
1552 1553 1554
    return TRUE;
}

1555 1556
static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
                              CharDriverState *chr, int idx,
1557
                              GSList *group, GtkWidget *view_menu)
1558 1559
{
    char buffer[32];
1560 1561 1562
    GtkWidget *box;
    GtkWidget *scrollbar;
    GtkAdjustment *vadjustment;
1563

1564
    vc->s = s;
1565
    vc->vte.chr = chr;
1566

1567
    snprintf(buffer, sizeof(buffer), "vc%d", idx);
1568 1569 1570
    vc->label = g_strdup_printf("%s", vc->vte.chr->label
                                ? vc->vte.chr->label : buffer);
    group = gd_vc_menu_init(s, vc, idx, group, view_menu);
1571

G
Gerd Hoffmann 已提交
1572 1573
    vc->vte.terminal = vte_terminal_new();
    g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
1574

G
Gerd Hoffmann 已提交
1575
    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
G
Gerd Hoffmann 已提交
1576 1577
    vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal),
                          VC_TERM_X_MIN, VC_TERM_Y_MIN);
1578

1579
#if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0)
G
Gerd Hoffmann 已提交
1580 1581
    vadjustment = gtk_scrollable_get_vadjustment
        (GTK_SCROLLABLE(vc->vte.terminal));
1582
#else
G
Gerd Hoffmann 已提交
1583
    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
1584 1585 1586 1587 1588 1589 1590 1591 1592 1593
#endif

#if GTK_CHECK_VERSION(3, 0, 0)
    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
    scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
#else
    box = gtk_hbox_new(false, 2);
    scrollbar = gtk_vscrollbar_new(vadjustment);
#endif

G
Gerd Hoffmann 已提交
1594
    gtk_box_pack_start(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
1595 1596
    gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);

G
Gerd Hoffmann 已提交
1597 1598 1599
    vc->vte.chr->opaque = vc;
    vc->vte.box = box;
    vc->vte.scrollbar = scrollbar;
1600 1601 1602

    g_signal_connect(vadjustment, "changed",
                     G_CALLBACK(gd_vc_adjustment_changed), vc);
1603

1604
    vc->type = GD_VC_VTE;
G
Gerd Hoffmann 已提交
1605
    vc->tab_item = box;
J
Jan Kiszka 已提交
1606
    vc->focus = vc->vte.terminal;
G
Gerd Hoffmann 已提交
1607
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
1608
                             gtk_label_new(vc->label));
1609

G
Gerd Hoffmann 已提交
1610 1611 1612
    qemu_chr_be_generic_open(vc->vte.chr);
    if (vc->vte.chr->init) {
        vc->vte.chr->init(vc->vte.chr);
1613 1614 1615
    }

    return group;
A
Anthony Liguori 已提交
1616 1617
}

1618
static void gd_vcs_init(GtkDisplayState *s, GSList *group,
1619 1620 1621 1622 1623
                        GtkWidget *view_menu)
{
    int i;

    for (i = 0; i < nb_vcs; i++) {
1624 1625
        VirtualConsole *vc = &s->vc[s->nb_vcs];
        group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
1626 1627 1628 1629 1630
        s->nb_vcs++;
    }
}
#endif /* CONFIG_VTE */

A
Anthony Liguori 已提交
1631 1632
/** Window Creation **/

1633 1634 1635 1636 1637 1638 1639 1640 1641
static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
{
#if GTK_CHECK_VERSION(3, 0, 0)
    g_signal_connect(vc->gfx.drawing_area, "draw",
                     G_CALLBACK(gd_draw_event), vc);
#else
    g_signal_connect(vc->gfx.drawing_area, "expose-event",
                     G_CALLBACK(gd_expose_event), vc);
#endif
1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
    if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
        g_signal_connect(vc->gfx.drawing_area, "event",
                         G_CALLBACK(gd_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "button-press-event",
                         G_CALLBACK(gd_button_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "button-release-event",
                         G_CALLBACK(gd_button_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "scroll-event",
                         G_CALLBACK(gd_scroll_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "key-press-event",
                         G_CALLBACK(gd_key_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "key-release-event",
                         G_CALLBACK(gd_key_event), vc);

        g_signal_connect(vc->gfx.drawing_area, "enter-notify-event",
                         G_CALLBACK(gd_enter_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "leave-notify-event",
                         G_CALLBACK(gd_leave_event), vc);
        g_signal_connect(vc->gfx.drawing_area, "focus-out-event",
                         G_CALLBACK(gd_focus_out_event), vc);
G
Gerd Hoffmann 已提交
1662 1663
        g_signal_connect(vc->gfx.drawing_area, "configure-event",
                         G_CALLBACK(gd_configure), vc);
1664 1665 1666 1667
    } else {
        g_signal_connect(vc->gfx.drawing_area, "key-press-event",
                         G_CALLBACK(gd_text_key_down), vc);
    }
1668 1669
}

A
Anthony Liguori 已提交
1670 1671 1672 1673
static void gd_connect_signals(GtkDisplayState *s)
{
    g_signal_connect(s->show_tabs_item, "activate",
                     G_CALLBACK(gd_menu_show_tabs), s);
1674 1675
    g_signal_connect(s->untabify_item, "activate",
                     G_CALLBACK(gd_menu_untabify), s);
A
Anthony Liguori 已提交
1676 1677 1678 1679

    g_signal_connect(s->window, "delete-event",
                     G_CALLBACK(gd_window_close), s);

1680 1681 1682 1683 1684 1685
    g_signal_connect(s->pause_item, "activate",
                     G_CALLBACK(gd_menu_pause), s);
    g_signal_connect(s->reset_item, "activate",
                     G_CALLBACK(gd_menu_reset), s);
    g_signal_connect(s->powerdown_item, "activate",
                     G_CALLBACK(gd_menu_powerdown), s);
A
Anthony Liguori 已提交
1686 1687
    g_signal_connect(s->quit_item, "activate",
                     G_CALLBACK(gd_menu_quit), s);
1688 1689 1690 1691 1692 1693 1694 1695 1696 1697
    g_signal_connect(s->full_screen_item, "activate",
                     G_CALLBACK(gd_menu_full_screen), s);
    g_signal_connect(s->zoom_in_item, "activate",
                     G_CALLBACK(gd_menu_zoom_in), s);
    g_signal_connect(s->zoom_out_item, "activate",
                     G_CALLBACK(gd_menu_zoom_out), s);
    g_signal_connect(s->zoom_fixed_item, "activate",
                     G_CALLBACK(gd_menu_zoom_fixed), s);
    g_signal_connect(s->zoom_fit_item, "activate",
                     G_CALLBACK(gd_menu_zoom_fit), s);
1698 1699
    g_signal_connect(s->grab_item, "activate",
                     G_CALLBACK(gd_menu_grab_input), s);
A
Anthony Liguori 已提交
1700 1701 1702 1703
    g_signal_connect(s->notebook, "switch-page",
                     G_CALLBACK(gd_change_page), s);
}

1704
static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
A
Anthony Liguori 已提交
1705
{
A
Anthony Liguori 已提交
1706
    GtkWidget *machine_menu;
A
Anthony Liguori 已提交
1707 1708
    GtkWidget *separator;

A
Anthony Liguori 已提交
1709
    machine_menu = gtk_menu_new();
1710
    gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group);
1711 1712

    s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
A
Anthony Liguori 已提交
1713
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
1714 1715

    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1716
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
1717

1718
    s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
A
Anthony Liguori 已提交
1719
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
1720

1721
    s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
A
Anthony Liguori 已提交
1722
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
1723 1724

    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1725
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
A
Anthony Liguori 已提交
1726

1727
    s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
A
Anthony Liguori 已提交
1728
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
1729
                                 "<QEMU>/Machine/Quit");
1730
    gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
1731
                            GDK_KEY_q, HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1732
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
A
Anthony Liguori 已提交
1733

A
Anthony Liguori 已提交
1734 1735 1736
    return machine_menu;
}

1737
static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
1738
                              QemuConsole *con, int idx,
1739 1740
                              GSList *group, GtkWidget *view_menu)
{
1741
    vc->label = qemu_console_get_label(con);
1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759
    vc->s = s;
    vc->gfx.scale_x = 1.0;
    vc->gfx.scale_y = 1.0;

    vc->gfx.drawing_area = gtk_drawing_area_new();
    gtk_widget_add_events(vc->gfx.drawing_area,
                          GDK_POINTER_MOTION_MASK |
                          GDK_BUTTON_PRESS_MASK |
                          GDK_BUTTON_RELEASE_MASK |
                          GDK_BUTTON_MOTION_MASK |
                          GDK_ENTER_NOTIFY_MASK |
                          GDK_LEAVE_NOTIFY_MASK |
                          GDK_SCROLL_MASK |
                          GDK_KEY_PRESS_MASK);
    gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);

    vc->type = GD_VC_GFX;
    vc->tab_item = vc->gfx.drawing_area;
J
Jan Kiszka 已提交
1760
    vc->focus = vc->gfx.drawing_area;
1761
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
1762
                             vc->tab_item, gtk_label_new(vc->label));
1763

1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786
#if defined(CONFIG_OPENGL)
    if (display_opengl) {
        /*
         * gtk_widget_set_double_buffered() was deprecated in 3.14.
         * It is required for opengl rendering on X11 though.  A
         * proper replacement (native opengl support) is only
         * available in 3.16+.  Silence the warning if possible.
         */
#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
        gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
#pragma GCC diagnostic pop
#endif
        vc->gfx.dcl.ops = &dcl_egl_ops;
    } else
#endif
    {
        vc->gfx.dcl.ops = &dcl_ops;
    }

1787 1788 1789
    vc->gfx.dcl.con = con;
    register_displaychangelistener(&vc->gfx.dcl);

1790 1791 1792
    gd_connect_vc_gfx_signals(vc);
    group = gd_vc_menu_init(s, vc, idx, group, view_menu);

G
Gerd Hoffmann 已提交
1793 1794
    if (dpy_ui_info_supported(vc->gfx.dcl.con)) {
        gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
1795
        s->free_scale = true;
G
Gerd Hoffmann 已提交
1796 1797
    }

1798 1799 1800
    return group;
}

1801
static GtkWidget *gd_create_menu_view(GtkDisplayState *s)
A
Anthony Liguori 已提交
1802 1803 1804 1805
{
    GSList *group = NULL;
    GtkWidget *view_menu;
    GtkWidget *separator;
1806 1807
    QemuConsole *con;
    int vc;
A
Anthony Liguori 已提交
1808 1809

    view_menu = gtk_menu_new();
1810
    gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
A
Anthony Liguori 已提交
1811

1812
    s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
1813 1814 1815 1816 1817 1818 1819 1820

    gtk_accel_group_connect(s->accel_group, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
            g_cclosure_new_swap(G_CALLBACK(gd_accel_full_screen), s, NULL));
#if GTK_CHECK_VERSION(3, 8, 0)
    gtk_accel_label_set_accel(
            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))),
            GDK_KEY_f, HOTKEY_MODIFIERS);
#endif
A
Anthony Liguori 已提交
1821
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
1822 1823

    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1824
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
1825

1826
    s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
1827 1828
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
                                 "<QEMU>/View/Zoom In");
J
Jan Kiszka 已提交
1829 1830
    gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1831
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
1832

1833
    s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
1834 1835
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
                                 "<QEMU>/View/Zoom Out");
J
Jan Kiszka 已提交
1836 1837
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1838
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
1839

1840
    s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
1841 1842
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
                                 "<QEMU>/View/Zoom Fixed");
J
Jan Kiszka 已提交
1843 1844
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1845
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
1846

1847
    s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
A
Anthony Liguori 已提交
1848
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
1849 1850

    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1851
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
1852

1853
    s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
A
Anthony Liguori 已提交
1854
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item);
1855

1856
    s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
1857 1858
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
                                 "<QEMU>/View/Grab Input");
J
Jan Kiszka 已提交
1859 1860
    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1861
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
1862

A
Anthony Liguori 已提交
1863
    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1864
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
A
Anthony Liguori 已提交
1865

1866
    /* gfx */
1867 1868
    for (vc = 0;; vc++) {
        con = qemu_console_lookup_by_index(vc);
1869
        if (!con) {
1870 1871 1872 1873 1874 1875
            break;
        }
        group = gd_vc_gfx_init(s, &s->vc[vc], con,
                               vc, group, view_menu);
        s->nb_vcs++;
    }
A
Anthony Liguori 已提交
1876

1877
#if defined(CONFIG_VTE)
1878
    /* vte */
1879
    gd_vcs_init(s, group, view_menu);
1880
#endif
1881

A
Anthony Liguori 已提交
1882
    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1883
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
A
Anthony Liguori 已提交
1884

1885
    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
A
Anthony Liguori 已提交
1886
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
A
Anthony Liguori 已提交
1887

1888 1889 1890
    s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab"));
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item);

A
Anthony Liguori 已提交
1891 1892 1893 1894 1895
    return view_menu;
}

static void gd_create_menus(GtkDisplayState *s)
{
1896 1897 1898
    s->accel_group = gtk_accel_group_new();
    s->machine_menu = gd_create_menu_machine(s);
    s->view_menu = gd_create_menu_view(s);
A
Anthony Liguori 已提交
1899 1900

    s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
1901 1902 1903
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
                              s->machine_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item);
A
Anthony Liguori 已提交
1904

A
Anthony Liguori 已提交
1905
    s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
A
Anthony Liguori 已提交
1906 1907
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
A
Anthony Liguori 已提交
1908

1909 1910
    g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group);
    gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group);
A
Anthony Liguori 已提交
1911 1912
}

1913 1914
static void gd_set_keycode_type(GtkDisplayState *s)
{
1915
#ifdef GDK_WINDOWING_X11
1916
    GdkDisplay *display = gtk_widget_get_display(s->window);
1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933
    if (GDK_IS_X11_DISPLAY(display)) {
        Display *x11_display = gdk_x11_display_get_xdisplay(display);
        XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask,
                                         XkbUseCoreKbd);
        char *keycodes = NULL;

        if (desc && desc->names) {
            keycodes = XGetAtomName(x11_display, desc->names->keycodes);
        }
        if (keycodes == NULL) {
            fprintf(stderr, "could not lookup keycode name\n");
        } else if (strstart(keycodes, "evdev", NULL)) {
            s->has_evdev = true;
        } else if (!strstart(keycodes, "xfree86", NULL)) {
            fprintf(stderr, "unknown keycodes `%s', please report to "
                    "qemu-devel@nongnu.org\n", keycodes);
        }
1934 1935 1936 1937 1938 1939 1940

        if (desc) {
            XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
        }
        if (keycodes) {
            XFree(keycodes);
        }
1941 1942 1943 1944
    }
#endif
}

1945 1946
static gboolean gtkinit;

1947
void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover)
A
Anthony Liguori 已提交
1948 1949
{
    GtkDisplayState *s = g_malloc0(sizeof(*s));
S
Stefan Weil 已提交
1950
    char *filename;
M
Max Reitz 已提交
1951
    GdkDisplay *window_display;
A
Anthony Liguori 已提交
1952

1953 1954 1955 1956 1957
    if (!gtkinit) {
        fprintf(stderr, "gtk initialization failed\n");
        exit(1);
    }

A
Anthony Liguori 已提交
1958
    s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1959 1960 1961
#if GTK_CHECK_VERSION(3, 2, 0)
    s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
A
Anthony Liguori 已提交
1962
    s->vbox = gtk_vbox_new(FALSE, 0);
1963
#endif
A
Anthony Liguori 已提交
1964 1965 1966
    s->notebook = gtk_notebook_new();
    s->menu_bar = gtk_menu_bar_new();

1967
    s->free_scale = FALSE;
A
Anthony Liguori 已提交
1968

1969 1970
    /* LC_MESSAGES only. See early_gtk_display_init() for details */
    setlocale(LC_MESSAGES, "");
1971 1972 1973
    bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
    textdomain("qemu");

M
Max Reitz 已提交
1974 1975 1976
    window_display = gtk_widget_get_display(s->window);
    s->null_cursor = gdk_cursor_new_for_display(window_display,
                                                GDK_BLANK_CURSOR);
A
Anthony Liguori 已提交
1977 1978 1979 1980 1981

    s->mouse_mode_notifier.notify = gd_mouse_mode_change;
    qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
    qemu_add_vm_change_state_handler(gd_change_runstate, s);

A
Anthony Liguori 已提交
1982
    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu_logo_no_text.svg");
S
Stefan Weil 已提交
1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993
    if (filename) {
        GError *error = NULL;
        GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
        if (pixbuf) {
            gtk_window_set_icon(GTK_WINDOW(s->window), pixbuf);
        } else {
            g_error_free(error);
        }
        g_free(filename);
    }

A
Anthony Liguori 已提交
1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009
    gd_create_menus(s);

    gd_connect_signals(s);

    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
    gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);

    gd_update_caption(s);

    gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);

    gtk_container_add(GTK_CONTAINER(s->window), s->vbox);

    gtk_widget_show_all(s->window);

2010 2011 2012
#ifdef VTE_RESIZE_HACK
    {
        VirtualConsole *cur = gd_vc_find_current(s);
F
Fam Zheng 已提交
2013 2014 2015 2016 2017 2018 2019 2020
        if (cur) {
            int i;

            for (i = 0; i < s->nb_vcs; i++) {
                VirtualConsole *vc = &s->vc[i];
                if (vc && vc->type == GD_VC_VTE && vc != cur) {
                    gtk_widget_hide(vc->vte.terminal);
                }
2021
            }
F
Fam Zheng 已提交
2022
            gd_update_windowsize(cur);
2023 2024 2025 2026
        }
    }
#endif

P
Peter Wu 已提交
2027 2028 2029
    if (full_screen) {
        gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
    }
2030 2031 2032
    if (grab_on_hover) {
        gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
    }
P
Peter Wu 已提交
2033

2034
    gd_set_keycode_type(s);
A
Anthony Liguori 已提交
2035
}
2036

2037
void early_gtk_display_init(int opengl)
2038
{
2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056
    /* The QEMU code relies on the assumption that it's always run in
     * the C locale. Therefore it is not prepared to deal with
     * operations that produce different results depending on the
     * locale, such as printf's formatting of decimal numbers, and
     * possibly others.
     *
     * Since GTK+ calls setlocale() by default -importing the locale
     * settings from the environment- we must prevent it from doing so
     * using gtk_disable_setlocale().
     *
     * QEMU's GTK+ UI, however, _does_ have translations for some of
     * the menu items. As a trade-off between a functionally correct
     * QEMU and a fully internationalized UI we support importing
     * LC_MESSAGES from the environment (see the setlocale() call
     * earlier in this file). This allows us to display translated
     * messages leaving everything else untouched.
     */
    gtk_disable_setlocale();
2057 2058 2059 2060 2061
    gtkinit = gtk_init_check(NULL, NULL);
    if (!gtkinit) {
        /* don't exit yet, that'll break -help */
        return;
    }
2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076

    switch (opengl) {
    case -1: /* default */
    case 0:  /* off */
        break;
    case 1: /* on */
#if defined(CONFIG_OPENGL)
        gtk_egl_init();
#endif
        break;
    default:
        g_assert_not_reached();
        break;
    }

2077 2078 2079 2080
#if defined(CONFIG_VTE)
    register_vc_handler(gd_vc_handler);
#endif
}