gtk.c 54.5 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"

37 38 39 40
#ifdef _WIN32
# define _WIN32_WINNT 0x0601 /* needed to get definition of MAPVK_VK_TO_VSC */
#endif

K
Kevin Wolf 已提交
41 42 43 44
#include "qemu-common.h"

#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
/* Work around an -Wstrict-prototypes warning in GTK headers */
45
#pragma GCC diagnostic push
K
Kevin Wolf 已提交
46 47
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#endif
A
Anthony Liguori 已提交
48
#include <gtk/gtk.h>
K
Kevin Wolf 已提交
49
#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
50
#pragma GCC diagnostic pop
K
Kevin Wolf 已提交
51 52 53
#endif


A
Anthony Liguori 已提交
54
#include <gdk/gdkkeysyms.h>
55
#include <glib/gi18n.h>
56
#include <locale.h>
S
Stefan Weil 已提交
57
#if defined(CONFIG_VTE)
A
Anthony Liguori 已提交
58
#include <vte/vte.h>
S
Stefan Weil 已提交
59
#endif
A
Anthony Liguori 已提交
60 61
#include <math.h>

62
#include "trace.h"
A
Anthony Liguori 已提交
63
#include "ui/console.h"
64
#include "ui/input.h"
A
Anthony Liguori 已提交
65 66 67 68
#include "sysemu/sysemu.h"
#include "qmp-commands.h"
#include "x_keymap.h"
#include "keymaps.h"
69
#include "sysemu/char.h"
G
Gerd Hoffmann 已提交
70
#include "qom/object.h"
71 72 73 74
#ifndef _WIN32
#include <gdk/gdkx.h>
#include <X11/XKBlib.h>
#endif
A
Anthony Liguori 已提交
75

76 77
#define MAX_VCS 10

S
Stefan Weil 已提交
78 79 80
#if !defined(CONFIG_VTE)
# define VTE_CHECK_VERSION(a, b, c) 0
#endif
81 82 83 84 85 86 87 88 89 90

/* Compatibility define to let us build on both Gtk2 and Gtk3 */
#if GTK_CHECK_VERSION(3, 0, 0)
static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh)
{
    *ww = gdk_window_get_width(w);
    *wh = gdk_window_get_height(w);
}
#endif

91 92 93 94
#if !GTK_CHECK_VERSION(2, 20, 0)
#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget)
#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 104
#define GDK_KEY_plus GDK_plus
#define GDK_KEY_minus GDK_minus
#endif
105

J
Jan Kiszka 已提交
106 107
#define HOTKEY_MODIFIERS        (GDK_CONTROL_MASK | GDK_MOD1_MASK)

108 109 110 111 112
static const int modifier_keycode[] = {
    /* shift, control, alt keys, meta keys, both left & right */
    0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd,
};

113 114 115 116 117 118 119 120 121 122 123 124
typedef struct GtkDisplayState GtkDisplayState;

typedef struct VirtualGfxConsole {
    GtkWidget *drawing_area;
    DisplayChangeListener dcl;
    DisplaySurface *ds;
    pixman_image_t *convert;
    cairo_surface_t *surface;
    double scale_x;
    double scale_y;
} VirtualGfxConsole;

S
Stefan Weil 已提交
125
#if defined(CONFIG_VTE)
G
Gerd Hoffmann 已提交
126
typedef struct VirtualVteConsole {
127 128
    GtkWidget *box;
    GtkWidget *scrollbar;
129
    GtkWidget *terminal;
A
Anthony Liguori 已提交
130
    CharDriverState *chr;
G
Gerd Hoffmann 已提交
131 132 133
} VirtualVteConsole;
#endif

134 135 136 137 138
typedef enum VirtualConsoleType {
    GD_VC_GFX,
    GD_VC_VTE,
} VirtualConsoleType;

G
Gerd Hoffmann 已提交
139
typedef struct VirtualConsole {
140
    GtkDisplayState *s;
141 142
    char *label;
    GtkWidget *window;
G
Gerd Hoffmann 已提交
143 144
    GtkWidget *menu_item;
    GtkWidget *tab_item;
145
    VirtualConsoleType type;
G
Gerd Hoffmann 已提交
146
    union {
147
        VirtualGfxConsole gfx;
G
Gerd Hoffmann 已提交
148 149
#if defined(CONFIG_VTE)
        VirtualVteConsole vte;
S
Stefan Weil 已提交
150
#endif
G
Gerd Hoffmann 已提交
151
    };
A
Anthony Liguori 已提交
152 153
} VirtualConsole;

154
struct GtkDisplayState {
A
Anthony Liguori 已提交
155 156 157 158
    GtkWidget *window;

    GtkWidget *menu_bar;

159 160
    GtkAccelGroup *accel_group;

161 162 163 164 165
    GtkWidget *machine_menu_item;
    GtkWidget *machine_menu;
    GtkWidget *pause_item;
    GtkWidget *reset_item;
    GtkWidget *powerdown_item;
A
Anthony Liguori 已提交
166 167 168 169
    GtkWidget *quit_item;

    GtkWidget *view_menu_item;
    GtkWidget *view_menu;
170 171 172 173 174
    GtkWidget *full_screen_item;
    GtkWidget *zoom_in_item;
    GtkWidget *zoom_out_item;
    GtkWidget *zoom_fixed_item;
    GtkWidget *zoom_fit_item;
175 176
    GtkWidget *grab_item;
    GtkWidget *grab_on_hover_item;
A
Anthony Liguori 已提交
177

178 179 180
    int nb_vcs;
    VirtualConsole vc[MAX_VCS];

A
Anthony Liguori 已提交
181
    GtkWidget *show_tabs_item;
182
    GtkWidget *untabify_item;
A
Anthony Liguori 已提交
183 184 185 186

    GtkWidget *vbox;
    GtkWidget *notebook;
    int button_mask;
187
    gboolean last_set;
A
Anthony Liguori 已提交
188 189
    int last_x;
    int last_y;
190 191
    int grab_x_root;
    int grab_y_root;
G
Gerd Hoffmann 已提交
192 193
    VirtualConsole *kbd_owner;
    VirtualConsole *ptr_owner;
A
Anthony Liguori 已提交
194

195
    gboolean full_screen;
A
Anthony Liguori 已提交
196 197 198

    GdkCursor *null_cursor;
    Notifier mouse_mode_notifier;
199
    gboolean free_scale;
200 201

    bool external_pause_update;
202 203

    bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
204
    bool has_evdev;
205
};
A
Anthony Liguori 已提交
206

G
Gerd Hoffmann 已提交
207 208 209
static void gd_grab_pointer(VirtualConsole *vc);
static void gd_ungrab_pointer(GtkDisplayState *s);

A
Anthony Liguori 已提交
210 211
/** Utility Functions **/

G
Gerd Hoffmann 已提交
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
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;
}

242 243 244 245 246 247 248 249
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);
}

250 251 252 253 254 255 256 257 258 259
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));
}

260
static void gd_update_cursor(VirtualConsole *vc)
A
Anthony Liguori 已提交
261
{
262
    GtkDisplayState *s = vc->s;
A
Anthony Liguori 已提交
263 264
    GdkWindow *window;

265 266 267
    if (vc->type != GD_VC_GFX) {
        return;
    }
A
Anthony Liguori 已提交
268

269
    window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
G
Gerd Hoffmann 已提交
270
    if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) {
A
Anthony Liguori 已提交
271 272 273 274 275 276 277 278 279
        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 已提交
280
    gchar *prefix;
A
Anthony Liguori 已提交
281
    gchar *title;
282
    const char *grab = "";
283
    bool is_paused = !runstate_is_running();
G
Gerd Hoffmann 已提交
284 285 286 287 288 289 290
    int i;

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

G
Gerd Hoffmann 已提交
292 293
    if (s->ptr_owner != NULL &&
        s->ptr_owner->window == NULL) {
294
        grab = _(" - Press Ctrl+Alt+G to release grab");
295
    }
A
Anthony Liguori 已提交
296

297
    if (is_paused) {
298
        status = _(" [Paused]");
A
Anthony Liguori 已提交
299
    }
300 301 302 303
    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 已提交
304

G
Gerd Hoffmann 已提交
305
    title = g_strdup_printf("%s%s%s", prefix, status, grab);
A
Anthony Liguori 已提交
306 307
    gtk_window_set_title(GTK_WINDOW(s->window), title);
    g_free(title);
G
Gerd Hoffmann 已提交
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322

    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 已提交
323 324
}

325
static void gd_update_windowsize(VirtualConsole *vc)
G
Gerd Hoffmann 已提交
326
{
327
    GtkDisplayState *s = vc->s;
G
Gerd Hoffmann 已提交
328
    double sx, sy;
329

G
Gerd Hoffmann 已提交
330 331 332
    if (vc->type != GD_VC_GFX || s->full_screen) {
        return;
    }
G
Gerd Hoffmann 已提交
333

G
Gerd Hoffmann 已提交
334 335 336 337 338 339
    if (s->free_scale) {
        sx = 1.0;
        sy = 1.0;
    } else {
        sx = vc->gfx.scale_x;
        sy = vc->gfx.scale_y;
G
Gerd Hoffmann 已提交
340
    }
G
Gerd Hoffmann 已提交
341 342 343
    gtk_widget_set_size_request(vc->gfx.drawing_area,
                                surface_width(vc->gfx.ds) * sx,
                                surface_height(vc->gfx.ds) * sy);
G
Gerd Hoffmann 已提交
344 345 346 347 348
    if (vc->window) {
        gtk_window_resize(GTK_WINDOW(vc->window), 320, 240);
    } else {
        gtk_window_resize(GTK_WINDOW(s->window), 320, 240);
    }
G
Gerd Hoffmann 已提交
349 350
}

351
static void gd_update_full_redraw(VirtualConsole *vc)
G
Gerd Hoffmann 已提交
352
{
353
    GtkWidget *area = vc->gfx.drawing_area;
G
Gerd Hoffmann 已提交
354
    int ww, wh;
355 356
    gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh);
    gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
G
Gerd Hoffmann 已提交
357 358
}

359 360
static void gtk_release_modifiers(GtkDisplayState *s)
{
361
    VirtualConsole *vc = gd_vc_find_current(s);
362 363
    int i, keycode;

364
    if (vc->type != GD_VC_GFX) {
365 366 367 368 369 370 371
        return;
    }
    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
        keycode = modifier_keycode[i];
        if (!s->modifier_pressed[i]) {
            continue;
        }
372
        qemu_input_event_send_key_number(vc->gfx.dcl.con, keycode, false);
373 374 375 376
        s->modifier_pressed[i] = false;
    }
}

A
Anthony Liguori 已提交
377 378
/** DisplayState Callbacks **/

379
static void gd_update(DisplayChangeListener *dcl,
380
                      int x, int y, int w, int h)
A
Anthony Liguori 已提交
381
{
382
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
A
Anthony Liguori 已提交
383
    int x1, x2, y1, y2;
384 385 386
    int mx, my;
    int fbw, fbh;
    int ww, wh;
A
Anthony Liguori 已提交
387

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

390 391 392
    if (vc->gfx.convert) {
        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
                               NULL, vc->gfx.convert,
393 394 395
                               x, y, 0, 0, x, y, w, h);
    }

396 397
    x1 = floor(x * vc->gfx.scale_x);
    y1 = floor(y * vc->gfx.scale_y);
A
Anthony Liguori 已提交
398

399 400
    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 已提交
401

402 403
    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
404

405 406
    gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
                          &ww, &wh);
407 408 409 410 411 412 413 414 415

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

416 417
    gtk_widget_queue_draw_area(vc->gfx.drawing_area,
                               mx + x1, my + y1, (x2 - x1), (y2 - y1));
A
Anthony Liguori 已提交
418 419
}

420
static void gd_refresh(DisplayChangeListener *dcl)
A
Anthony Liguori 已提交
421
{
422
    graphic_hw_update(dcl->con);
A
Anthony Liguori 已提交
423 424
}

425 426 427 428
#if GTK_CHECK_VERSION(3, 0, 0)
static void gd_mouse_set(DisplayChangeListener *dcl,
                         int x, int y, int visible)
{
429
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
430 431 432 433
    GdkDisplay *dpy;
    GdkDeviceManager *mgr;
    gint x_root, y_root;

C
Cole Robinson 已提交
434 435 436 437
    if (qemu_input_is_absolute()) {
        return;
    }

438
    dpy = gtk_widget_get_display(vc->gfx.drawing_area);
439
    mgr = gdk_display_get_device_manager(dpy);
440
    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
441 442
                               x, y, &x_root, &y_root);
    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
443
                    gtk_widget_get_screen(vc->gfx.drawing_area),
C
Cole Robinson 已提交
444
                    x_root, y_root);
445 446
}
#else
G
Gerd Hoffmann 已提交
447 448 449
static void gd_mouse_set(DisplayChangeListener *dcl,
                         int x, int y, int visible)
{
450
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
451 452
    gint x_root, y_root;

C
Cole Robinson 已提交
453 454 455 456
    if (qemu_input_is_absolute()) {
        return;
    }

457
    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
G
Gerd Hoffmann 已提交
458
                               x, y, &x_root, &y_root);
459 460
    gdk_display_warp_pointer(gtk_widget_get_display(vc->gfx.drawing_area),
                             gtk_widget_get_screen(vc->gfx.drawing_area),
G
Gerd Hoffmann 已提交
461 462
                             x_root, y_root);
}
463
#endif
G
Gerd Hoffmann 已提交
464 465 466 467

static void gd_cursor_define(DisplayChangeListener *dcl,
                             QEMUCursor *c)
{
468
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
469 470 471 472 473 474 475
    GdkPixbuf *pixbuf;
    GdkCursor *cursor;

    pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
                                      GDK_COLORSPACE_RGB, true, 8,
                                      c->width, c->height, c->width * 4,
                                      NULL, NULL);
476 477 478 479
    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 已提交
480
    g_object_unref(pixbuf);
481
#if !GTK_CHECK_VERSION(3, 0, 0)
482
    gdk_cursor_unref(cursor);
483 484 485
#else
    g_object_unref(cursor);
#endif
G
Gerd Hoffmann 已提交
486 487
}

488 489
static void gd_switch(DisplayChangeListener *dcl,
                      DisplaySurface *surface)
A
Anthony Liguori 已提交
490
{
491
    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
G
Gerd Hoffmann 已提交
492
    bool resized = true;
A
Anthony Liguori 已提交
493

G
Gerd Hoffmann 已提交
494
    trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
A
Anthony Liguori 已提交
495

496 497
    if (vc->gfx.surface) {
        cairo_surface_destroy(vc->gfx.surface);
A
Anthony Liguori 已提交
498 499
    }

500 501 502
    if (vc->gfx.ds &&
        surface_width(vc->gfx.ds) == surface_width(surface) &&
        surface_height(vc->gfx.ds) == surface_height(surface)) {
G
Gerd Hoffmann 已提交
503 504
        resized = false;
    }
505
    vc->gfx.ds = surface;
A
Anthony Liguori 已提交
506

507 508 509
    if (vc->gfx.convert) {
        pixman_image_unref(vc->gfx.convert);
        vc->gfx.convert = NULL;
510
    }
A
Anthony Liguori 已提交
511

512 513 514 515 516 517 518
    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.
         */
519
        vc->gfx.surface = cairo_image_surface_create_for_data
520 521 522 523 524 525 526
            (surface_data(surface),
             CAIRO_FORMAT_RGB24,
             surface_width(surface),
             surface_height(surface),
             surface_stride(surface));
    } else {
        /* Must convert surface, use pixman to do it. */
527 528 529 530 531 532
        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),
533
             CAIRO_FORMAT_RGB24,
534 535 536 537 538
             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,
539
                               0, 0, 0, 0, 0, 0,
540 541
                               pixman_image_get_width(vc->gfx.convert),
                               pixman_image_get_height(vc->gfx.convert));
542
    }
543

G
Gerd Hoffmann 已提交
544
    if (resized) {
545
        gd_update_windowsize(vc);
G
Gerd Hoffmann 已提交
546
    } else {
547
        gd_update_full_redraw(vc);
548
    }
A
Anthony Liguori 已提交
549 550 551 552 553 554 555 556 557 558 559 560 561
}

/** 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)
{
562
    GtkDisplayState *s;
563
    int i;
564 565 566 567 568 569 570

    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);
    }
571 572 573 574
    for (i = 0; i < s->nb_vcs; i++) {
        VirtualConsole *vc = &s->vc[i];
        gd_update_cursor(vc);
    }
A
Anthony Liguori 已提交
575 576 577 578 579 580 581 582
}

/** GTK Events **/

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

    if (!no_quit) {
586 587 588 589 590 591
        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 已提交
592 593 594 595 596 597 598 599 600
        qmp_quit(NULL);
        return FALSE;
    }

    return TRUE;
}

static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
{
601 602
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
603
    int mx, my;
A
Anthony Liguori 已提交
604 605 606
    int ww, wh;
    int fbw, fbh;

607 608 609 610
    if (!gtk_widget_get_realized(widget)) {
        return FALSE;
    }

611 612
    fbw = surface_width(vc->gfx.ds);
    fbh = surface_height(vc->gfx.ds);
A
Anthony Liguori 已提交
613 614 615

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

616
    if (s->full_screen) {
617 618
        vc->gfx.scale_x = (double)ww / fbw;
        vc->gfx.scale_y = (double)wh / fbh;
619 620 621 622 623 624
    } else if (s->free_scale) {
        double sx, sy;

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

625
        vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
A
Anthony Liguori 已提交
626 627
    }

628 629
    fbw *= vc->gfx.scale_x;
    fbh *= vc->gfx.scale_y;
630

631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
    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);

650 651 652
    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 已提交
653 654 655 656 657
    cairo_paint(cr);

    return TRUE;
}

658
#if !GTK_CHECK_VERSION(3, 0, 0)
A
Anthony Liguori 已提交
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
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;
}
679
#endif
A
Anthony Liguori 已提交
680 681 682 683

static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
                                void *opaque)
{
684 685
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
A
Anthony Liguori 已提交
686
    int x, y;
687 688 689 690
    int mx, my;
    int fbh, fbw;
    int ww, wh;

691 692
    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
A
Anthony Liguori 已提交
693

694 695
    gdk_drawable_get_size(gtk_widget_get_window(vc->gfx.drawing_area),
                          &ww, &wh);
696 697 698 699 700 701 702 703 704

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

705 706
    x = (motion->x - mx) / vc->gfx.scale_x;
    y = (motion->y - my) / vc->gfx.scale_y;
707

708
    if (qemu_input_is_absolute()) {
709
        if (x < 0 || y < 0 ||
710 711
            x >= surface_width(vc->gfx.ds) ||
            y >= surface_height(vc->gfx.ds)) {
712 713
            return TRUE;
        }
714 715 716 717
        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));
718
        qemu_input_event_sync();
G
Gerd Hoffmann 已提交
719
    } else if (s->last_set && s->ptr_owner == vc) {
720 721
        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);
722
        qemu_input_event_sync();
A
Anthony Liguori 已提交
723 724 725
    }
    s->last_x = x;
    s->last_y = y;
726
    s->last_set = TRUE;
A
Anthony Liguori 已提交
727

G
Gerd Hoffmann 已提交
728
    if (!qemu_input_is_absolute() && s->ptr_owner == vc) {
729
        GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
        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) {
754 755 756 757 758
#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);
759
            gdk_display_warp_pointer(display, screen, x, y);
760
#endif
761
            s->last_set = FALSE;
762 763 764
            return FALSE;
        }
    }
A
Anthony Liguori 已提交
765 766 767 768 769 770
    return TRUE;
}

static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
                                void *opaque)
{
771 772
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
773
    InputButton btn;
A
Anthony Liguori 已提交
774

775 776
    /* implicitly grab the input at the first click in the relative mode */
    if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
G
Gerd Hoffmann 已提交
777 778 779 780 781 782 783 784 785
        !qemu_input_is_absolute() && s->ptr_owner != vc) {
        gd_ungrab_pointer(s);
        if (!vc->window) {
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                           TRUE);
        } else {
            gd_grab_pointer(vc);
            gd_update_caption(s);
        }
786 787 788
        return TRUE;
    }

A
Anthony Liguori 已提交
789
    if (button->button == 1) {
790
        btn = INPUT_BUTTON_LEFT;
A
Anthony Liguori 已提交
791
    } else if (button->button == 2) {
792
        btn = INPUT_BUTTON_MIDDLE;
A
Anthony Liguori 已提交
793
    } else if (button->button == 3) {
794
        btn = INPUT_BUTTON_RIGHT;
A
Anthony Liguori 已提交
795
    } else {
796
        return TRUE;
A
Anthony Liguori 已提交
797 798
    }

799 800
    qemu_input_queue_btn(vc->gfx.dcl.con, btn,
                         button->type == GDK_BUTTON_PRESS);
801
    qemu_input_event_sync();
A
Anthony Liguori 已提交
802 803 804
    return TRUE;
}

J
Jan Kiszka 已提交
805 806 807
static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
                                void *opaque)
{
808
    VirtualConsole *vc = opaque;
J
Jan Kiszka 已提交
809 810 811 812 813 814 815 816 817 818
    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;
    }

819
    qemu_input_queue_btn(vc->gfx.dcl.con, btn, true);
J
Jan Kiszka 已提交
820
    qemu_input_event_sync();
821
    qemu_input_queue_btn(vc->gfx.dcl.con, btn, false);
J
Jan Kiszka 已提交
822 823 824 825
    qemu_input_event_sync();
    return TRUE;
}

A
Anthony Liguori 已提交
826 827
static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
{
828 829
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
830
    int gdk_keycode = key->hardware_keycode;
831
    int i;
A
Anthony Liguori 已提交
832

833 834 835 836 837 838 839 840 841
#ifdef _WIN32
    UINT qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC);
    switch (qemu_keycode) {
    case 103:   /* alt gr */
        qemu_keycode = 56 | SCANCODE_GREY;
        break;
    }
#else
    int qemu_keycode;
A
Anthony Liguori 已提交
842 843 844 845 846 847

    if (gdk_keycode < 9) {
        qemu_keycode = 0;
    } else if (gdk_keycode < 97) {
        qemu_keycode = gdk_keycode - 8;
    } else if (gdk_keycode < 158) {
848 849 850 851 852
        if (s->has_evdev) {
            qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
        } else {
            qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97);
        }
A
Anthony Liguori 已提交
853 854 855 856 857 858 859
    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
        qemu_keycode = 0x70;
    } else if (gdk_keycode == 211) { /* backslash */
        qemu_keycode = 0x73;
    } else {
        qemu_keycode = 0;
    }
860
#endif
A
Anthony Liguori 已提交
861

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

865 866 867 868 869 870
    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
        if (qemu_keycode == modifier_keycode[i]) {
            s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS);
        }
    }

871
    qemu_input_event_send_key_number(vc->gfx.dcl.con, qemu_keycode,
872
                                     key->type == GDK_KEY_PRESS);
A
Anthony Liguori 已提交
873 874 875 876

    return TRUE;
}

877 878 879 880 881 882 883 884
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 已提交
885 886
/** Window Menu Actions **/

887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
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 已提交
911 912 913 914 915 916 917 918
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;
919
    VirtualConsole *vc = gd_vc_find_by_menu(s);
920
    gint page;
A
Anthony Liguori 已提交
921

922 923
    gtk_release_modifiers(s);
    if (vc) {
924
        page = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook),
925
                                     vc->tab_item);
926
        gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), page);
A
Anthony Liguori 已提交
927 928 929 930 931 932 933 934 935 936 937 938 939 940
    }
}

static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;

    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);
    }
}

941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
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);
    gtk_widget_reparent(vc->tab_item, s->notebook);
    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;
}

956 957 958 959 960 961 962 963 964 965 966 967 968 969
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 {
        gd_grab_pointer(vc);
    }
    gd_update_caption(vc->s);
    return TRUE;
}

970 971 972 973 974 975
static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
    VirtualConsole *vc = gd_vc_find_current(s);

    if (vc->type == GD_VC_GFX) {
G
Gerd Hoffmann 已提交
976 977
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       FALSE);
978 979 980 981 982 983 984 985 986
    }
    if (!vc->window) {
        gtk_widget_set_sensitive(vc->menu_item, false);
        vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_widget_reparent(vc->tab_item, vc->window);

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

988 989 990 991 992 993 994
        GtkAccelGroup *ag = gtk_accel_group_new();
        gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);

        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);

        fprintf(stderr, "%s: %p\n", __func__, vc);
G
Gerd Hoffmann 已提交
995
        gd_update_caption(s);
996 997 998
    }
}

999 1000 1001
static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1002
    VirtualConsole *vc = gd_vc_find_current(s);
1003

1004
    if (!s->full_screen) {
1005 1006
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
        gtk_widget_set_size_request(s->menu_bar, 0, 0);
1007 1008 1009 1010
        if (vc->type == GD_VC_GFX) {
            gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                           TRUE);
1011
        }
1012
        gtk_window_fullscreen(GTK_WINDOW(s->window));
1013 1014 1015 1016 1017 1018
        s->full_screen = TRUE;
    } else {
        gtk_window_unfullscreen(GTK_WINDOW(s->window));
        gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
        gtk_widget_set_size_request(s->menu_bar, -1, -1);
        s->full_screen = FALSE;
1019 1020 1021 1022 1023 1024 1025 1026 1027
        if (vc->type == GD_VC_GFX) {
            vc->gfx.scale_x = 1.0;
            vc->gfx.scale_y = 1.0;
            gtk_widget_set_size_request(vc->gfx.drawing_area,
                                        surface_width(vc->gfx.ds),
                                        surface_height(vc->gfx.ds));
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                           FALSE);
        }
1028 1029
    }

1030
    gd_update_cursor(vc);
1031 1032 1033 1034 1035
}

static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1036
    VirtualConsole *vc = gd_vc_find_current(s);
1037 1038 1039 1040

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

1041 1042
    vc->gfx.scale_x += .25;
    vc->gfx.scale_y += .25;
1043

1044
    gd_update_windowsize(vc);
1045 1046 1047 1048 1049
}

static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1050
    VirtualConsole *vc = gd_vc_find_current(s);
1051 1052 1053 1054

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

1055 1056
    vc->gfx.scale_x -= .25;
    vc->gfx.scale_y -= .25;
1057

1058 1059
    vc->gfx.scale_x = MAX(vc->gfx.scale_x, .25);
    vc->gfx.scale_y = MAX(vc->gfx.scale_y, .25);
1060

1061
    gd_update_windowsize(vc);
1062 1063 1064 1065 1066
}

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

1069 1070
    vc->gfx.scale_x = 1.0;
    vc->gfx.scale_y = 1.0;
1071

1072
    gd_update_windowsize(vc);
1073 1074 1075 1076 1077
}

static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1078
    VirtualConsole *vc = gd_vc_find_current(s);
1079 1080 1081 1082 1083

    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
        s->free_scale = TRUE;
    } else {
        s->free_scale = FALSE;
1084 1085 1086
        vc->gfx.scale_x = 1.0;
        vc->gfx.scale_y = 1.0;
        gd_update_windowsize(vc);
1087 1088
    }

1089
    gd_update_full_redraw(vc);
1090 1091
}

1092
static void gd_grab_keyboard(VirtualConsole *vc)
1093
{
1094
#if GTK_CHECK_VERSION(3, 0, 0)
1095
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1096 1097 1098 1099 1100 1101 1102 1103
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
    GList *devices = gdk_device_manager_list_devices(mgr,
                                                     GDK_DEVICE_TYPE_MASTER);
    GList *tmp = devices;
    while (tmp) {
        GdkDevice *dev = tmp->data;
        if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
            gdk_device_grab(dev,
1104
                            gtk_widget_get_window(vc->gfx.drawing_area),
1105 1106 1107 1108 1109 1110 1111 1112 1113 1114
                            GDK_OWNERSHIP_NONE,
                            FALSE,
                            GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
                            NULL,
                            GDK_CURRENT_TIME);
        }
        tmp = tmp->next;
    }
    g_list_free(devices);
#else
1115
    gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1116 1117
                      FALSE,
                      GDK_CURRENT_TIME);
1118
#endif
G
Gerd Hoffmann 已提交
1119
    vc->s->kbd_owner = vc;
G
Gerd Hoffmann 已提交
1120
    trace_gd_grab(vc->label, "kbd", true);
1121 1122
}

G
Gerd Hoffmann 已提交
1123
static void gd_ungrab_keyboard(GtkDisplayState *s)
1124
{
G
Gerd Hoffmann 已提交
1125 1126 1127 1128 1129 1130 1131
    VirtualConsole *vc = s->kbd_owner;

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

1132
#if GTK_CHECK_VERSION(3, 0, 0)
1133
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
    GList *devices = gdk_device_manager_list_devices(mgr,
                                                     GDK_DEVICE_TYPE_MASTER);
    GList *tmp = devices;
    while (tmp) {
        GdkDevice *dev = tmp->data;
        if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
            gdk_device_ungrab(dev,
                              GDK_CURRENT_TIME);
        }
        tmp = tmp->next;
    }
    g_list_free(devices);
#else
1148
    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
1149
#endif
G
Gerd Hoffmann 已提交
1150
    trace_gd_grab(vc->label, "kbd", false);
1151 1152
}

1153
static void gd_grab_pointer(VirtualConsole *vc)
1154
{
1155
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1156
#if GTK_CHECK_VERSION(3, 0, 0)
1157 1158 1159 1160 1161 1162 1163 1164
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
    GList *devices = gdk_device_manager_list_devices(mgr,
                                                     GDK_DEVICE_TYPE_MASTER);
    GList *tmp = devices;
    while (tmp) {
        GdkDevice *dev = tmp->data;
        if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
            gdk_device_grab(dev,
1165
                            gtk_widget_get_window(vc->gfx.drawing_area),
1166 1167 1168 1169 1170 1171 1172 1173
                            GDK_OWNERSHIP_NONE,
                            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,
1174
                            vc->s->null_cursor,
1175 1176 1177 1178 1179
                            GDK_CURRENT_TIME);
        }
        tmp = tmp->next;
    }
    g_list_free(devices);
1180
    gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr),
1181
                            NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
1182
#else
1183
    gdk_pointer_grab(gtk_widget_get_window(vc->gfx.drawing_area),
1184 1185 1186 1187 1188 1189 1190
                     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 */
1191
                     vc->s->null_cursor,
1192
                     GDK_CURRENT_TIME);
1193
    gdk_display_get_pointer(display, NULL,
1194
                            &vc->s->grab_x_root, &vc->s->grab_y_root, NULL);
1195
#endif
G
Gerd Hoffmann 已提交
1196
    vc->s->ptr_owner = vc;
G
Gerd Hoffmann 已提交
1197
    trace_gd_grab(vc->label, "ptr", true);
1198 1199
}

G
Gerd Hoffmann 已提交
1200
static void gd_ungrab_pointer(GtkDisplayState *s)
1201
{
G
Gerd Hoffmann 已提交
1202 1203 1204 1205 1206 1207 1208
    VirtualConsole *vc = s->ptr_owner;

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

1209
    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
1210
#if GTK_CHECK_VERSION(3, 0, 0)
1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223
    GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
    GList *devices = gdk_device_manager_list_devices(mgr,
                                                     GDK_DEVICE_TYPE_MASTER);
    GList *tmp = devices;
    while (tmp) {
        GdkDevice *dev = tmp->data;
        if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
            gdk_device_ungrab(dev,
                              GDK_CURRENT_TIME);
        }
        tmp = tmp->next;
    }
    g_list_free(devices);
1224
    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
1225 1226
                    gtk_widget_get_screen(vc->gfx.drawing_area),
                    vc->s->grab_x_root, vc->s->grab_y_root);
1227 1228
#else
    gdk_pointer_ungrab(GDK_CURRENT_TIME);
1229
    gdk_display_warp_pointer(display,
1230 1231
                             gtk_widget_get_screen(vc->gfx.drawing_area),
                             vc->s->grab_x_root, vc->s->grab_y_root);
1232
#endif
G
Gerd Hoffmann 已提交
1233
    trace_gd_grab(vc->label, "ptr", false);
1234 1235
}

1236 1237 1238
static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
{
    GtkDisplayState *s = opaque;
1239
    VirtualConsole *vc = gd_vc_find_current(s);
1240 1241

    if (gd_is_grab_active(s)) {
1242 1243 1244
        if (!gd_grab_on_hover(s)) {
            gd_grab_keyboard(vc);
        }
1245
        gd_grab_pointer(vc);
1246
    } else {
G
Gerd Hoffmann 已提交
1247 1248
        gd_ungrab_keyboard(s);
        gd_ungrab_pointer(s);
1249 1250 1251
    }

    gd_update_caption(s);
1252
    gd_update_cursor(vc);
1253 1254
}

A
Anthony Liguori 已提交
1255 1256 1257 1258
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
                           gpointer data)
{
    GtkDisplayState *s = data;
1259
    VirtualConsole *vc;
1260
    gboolean on_vga;
A
Anthony Liguori 已提交
1261 1262 1263 1264 1265

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

1266 1267 1268 1269 1270 1271 1272
    vc = gd_vc_find_by_page(s, arg2);
    if (!vc) {
        return;
    }
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
                                   TRUE);
    on_vga = (vc->type == GD_VC_GFX);
1273 1274 1275
    if (!on_vga) {
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       FALSE);
1276 1277 1278
    } else if (s->full_screen) {
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
                                       TRUE);
1279 1280 1281
    }
    gtk_widget_set_sensitive(s->grab_item, on_vga);

1282
    gd_update_cursor(vc);
A
Anthony Liguori 已提交
1283 1284
}

1285 1286
static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
                               gpointer opaque)
1287
{
1288 1289
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1290

G
Gerd Hoffmann 已提交
1291 1292
    if (gd_grab_on_hover(s)) {
        gd_ungrab_keyboard(s);
1293
        gd_grab_keyboard(vc);
G
Gerd Hoffmann 已提交
1294
        gd_update_caption(s);
1295 1296 1297 1298
    }
    return TRUE;
}

1299 1300
static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
                               gpointer opaque)
1301
{
1302 1303
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1304

G
Gerd Hoffmann 已提交
1305
    if (gd_grab_on_hover(s)) {
G
Gerd Hoffmann 已提交
1306
        gd_ungrab_keyboard(s);
G
Gerd Hoffmann 已提交
1307
        gd_update_caption(s);
1308 1309 1310 1311
    }
    return TRUE;
}

1312
static gboolean gd_focus_out_event(GtkWidget *widget,
1313
                                   GdkEventCrossing *crossing, gpointer opaque)
1314
{
1315 1316
    VirtualConsole *vc = opaque;
    GtkDisplayState *s = vc->s;
1317 1318 1319 1320 1321

    gtk_release_modifiers(s);
    return TRUE;
}

1322 1323
/** Virtual Console Callbacks **/

1324
static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
1325
                               int idx, GSList *group, GtkWidget *view_menu)
1326 1327 1328 1329 1330
{
    char path[32];

    snprintf(path, sizeof(path), "<QEMU>/View/VC%d", idx);

1331
    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
    gtk_accel_map_add_entry(path, GDK_KEY_1 + idx, HOTKEY_MODIFIERS);

    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);

    return group;
}

1343
#if defined(CONFIG_VTE)
1344 1345 1346 1347 1348 1349
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 已提交
1350
        gtk_widget_show(vc->vte.scrollbar);
1351
    } else {
G
Gerd Hoffmann 已提交
1352
        gtk_widget_hide(vc->vte.scrollbar);
1353 1354 1355
    }
}

1356 1357 1358 1359
static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
{
    VirtualConsole *vc = chr->opaque;

G
Gerd Hoffmann 已提交
1360
    vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
C
Cole Robinson 已提交
1361
    return len;
1362 1363 1364 1365 1366
}

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

G
Gerd Hoffmann 已提交
1367
static CharDriverState *gd_vc_handler(ChardevVC *unused)
1368 1369 1370 1371 1372
{
    CharDriverState *chr;

    chr = g_malloc0(sizeof(*chr));
    chr->chr_write = gd_vc_chr_write;
1373 1374
    /* defer OPENED events until our vc is fully initialized */
    chr->explicit_be_open = true;
1375 1376 1377 1378 1379 1380

    vcs[nb_vcs++] = chr;

    return chr;
}

C
Cole Robinson 已提交
1381 1382
static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
                         gpointer user_data)
1383
{
C
Cole Robinson 已提交
1384
    VirtualConsole *vc = user_data;
1385

G
Gerd Hoffmann 已提交
1386
    qemu_chr_be_write(vc->vte.chr, (uint8_t  *)text, (unsigned int)size);
1387 1388 1389
    return TRUE;
}

1390 1391
static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
                              CharDriverState *chr, int idx,
1392
                              GSList *group, GtkWidget *view_menu)
1393 1394
{
    char buffer[32];
1395 1396 1397
    GtkWidget *box;
    GtkWidget *scrollbar;
    GtkAdjustment *vadjustment;
1398

1399
    vc->s = s;
1400
    vc->vte.chr = chr;
1401

1402
    snprintf(buffer, sizeof(buffer), "vc%d", idx);
1403 1404 1405
    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);
1406

G
Gerd Hoffmann 已提交
1407 1408
    vc->vte.terminal = vte_terminal_new();
    g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
1409

G
Gerd Hoffmann 已提交
1410 1411
    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
    vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal), 80, 25);
1412

1413
#if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0)
G
Gerd Hoffmann 已提交
1414 1415
    vadjustment = gtk_scrollable_get_vadjustment
        (GTK_SCROLLABLE(vc->vte.terminal));
1416
#else
G
Gerd Hoffmann 已提交
1417
    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
1418 1419 1420 1421 1422 1423 1424 1425 1426 1427
#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 已提交
1428
    gtk_box_pack_start(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
1429 1430
    gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);

G
Gerd Hoffmann 已提交
1431 1432 1433
    vc->vte.chr->opaque = vc;
    vc->vte.box = box;
    vc->vte.scrollbar = scrollbar;
1434 1435 1436

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

1438
    vc->type = GD_VC_VTE;
G
Gerd Hoffmann 已提交
1439 1440
    vc->tab_item = box;
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
1441
                             gtk_label_new(vc->label));
1442

G
Gerd Hoffmann 已提交
1443 1444 1445
    qemu_chr_be_generic_open(vc->vte.chr);
    if (vc->vte.chr->init) {
        vc->vte.chr->init(vc->vte.chr);
1446 1447 1448
    }

    return group;
A
Anthony Liguori 已提交
1449 1450
}

1451
static void gd_vcs_init(GtkDisplayState *s, GSList *group,
1452 1453 1454 1455 1456
                        GtkWidget *view_menu)
{
    int i;

    for (i = 0; i < nb_vcs; i++) {
1457 1458
        VirtualConsole *vc = &s->vc[s->nb_vcs];
        group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
1459 1460 1461 1462 1463
        s->nb_vcs++;
    }
}
#endif /* CONFIG_VTE */

A
Anthony Liguori 已提交
1464 1465
/** Window Creation **/

1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495
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
    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);
}

A
Anthony Liguori 已提交
1496 1497 1498 1499
static void gd_connect_signals(GtkDisplayState *s)
{
    g_signal_connect(s->show_tabs_item, "activate",
                     G_CALLBACK(gd_menu_show_tabs), s);
1500 1501
    g_signal_connect(s->untabify_item, "activate",
                     G_CALLBACK(gd_menu_untabify), s);
A
Anthony Liguori 已提交
1502 1503 1504 1505

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

1506 1507 1508 1509 1510 1511
    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 已提交
1512 1513
    g_signal_connect(s->quit_item, "activate",
                     G_CALLBACK(gd_menu_quit), s);
1514 1515 1516 1517 1518 1519 1520 1521 1522 1523
    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);
1524 1525
    g_signal_connect(s->grab_item, "activate",
                     G_CALLBACK(gd_menu_grab_input), s);
A
Anthony Liguori 已提交
1526 1527 1528 1529
    g_signal_connect(s->notebook, "switch-page",
                     G_CALLBACK(gd_change_page), s);
}

A
Anthony Liguori 已提交
1530
static GtkWidget *gd_create_menu_machine(GtkDisplayState *s, GtkAccelGroup *accel_group)
A
Anthony Liguori 已提交
1531
{
A
Anthony Liguori 已提交
1532
    GtkWidget *machine_menu;
A
Anthony Liguori 已提交
1533 1534
    GtkWidget *separator;

A
Anthony Liguori 已提交
1535 1536
    machine_menu = gtk_menu_new();
    gtk_menu_set_accel_group(GTK_MENU(machine_menu), accel_group);
1537 1538

    s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
A
Anthony Liguori 已提交
1539
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
1540 1541

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

1544
    s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
A
Anthony Liguori 已提交
1545
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
1546

1547
    s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
A
Anthony Liguori 已提交
1548
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
1549 1550

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

1553
    s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
A
Anthony Liguori 已提交
1554
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
1555
                                 "<QEMU>/Machine/Quit");
1556
    gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
1557
                            GDK_KEY_q, HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1558
    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
A
Anthony Liguori 已提交
1559

A
Anthony Liguori 已提交
1560 1561 1562
    return machine_menu;
}

1563 1564 1565 1566 1567 1568 1569 1570 1571 1572
static const DisplayChangeListenerOps dcl_ops = {
    .dpy_name          = "gtk",
    .dpy_gfx_update    = gd_update,
    .dpy_gfx_switch    = gd_switch,
    .dpy_refresh       = gd_refresh,
    .dpy_mouse_set     = gd_mouse_set,
    .dpy_cursor_define = gd_cursor_define,
};

static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
1573
                              QemuConsole *con, int idx,
1574 1575
                              GSList *group, GtkWidget *view_menu)
{
G
Gerd Hoffmann 已提交
1576 1577 1578 1579 1580
    Error *local_err = NULL;
    Object *obj;

    obj = object_property_get_link(OBJECT(con), "device", &local_err);
    if (obj) {
1581 1582 1583
        vc->label = g_strdup_printf("%s", object_get_typename(obj));
    } else {
        vc->label = g_strdup_printf("VGA");
G
Gerd Hoffmann 已提交
1584 1585
    }

1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605
    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_double_buffered(vc->gfx.drawing_area, FALSE);
    gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);

    vc->type = GD_VC_GFX;
    vc->tab_item = vc->gfx.drawing_area;
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
1606
                             vc->tab_item, gtk_label_new(vc->label));
1607 1608
    gd_connect_vc_gfx_signals(vc);

1609
    group = gd_vc_menu_init(s, vc, idx, group, view_menu);
1610 1611 1612 1613 1614 1615 1616 1617

    vc->gfx.dcl.ops = &dcl_ops;
    vc->gfx.dcl.con = con;
    register_displaychangelistener(&vc->gfx.dcl);

    return group;
}

A
Anthony Liguori 已提交
1618 1619 1620 1621 1622
static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_group)
{
    GSList *group = NULL;
    GtkWidget *view_menu;
    GtkWidget *separator;
1623 1624
    QemuConsole *con;
    int vc;
A
Anthony Liguori 已提交
1625 1626 1627

    view_menu = gtk_menu_new();
    gtk_menu_set_accel_group(GTK_MENU(view_menu), accel_group);
A
Anthony Liguori 已提交
1628

1629
    s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
1630 1631
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item),
                                 "<QEMU>/View/Full Screen");
J
Jan Kiszka 已提交
1632 1633
    gtk_accel_map_add_entry("<QEMU>/View/Full Screen", GDK_KEY_f,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1634
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
1635 1636

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

1639
    s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
1640 1641
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
                                 "<QEMU>/View/Zoom In");
J
Jan Kiszka 已提交
1642 1643
    gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1644
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
1645

1646
    s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
1647 1648
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
                                 "<QEMU>/View/Zoom Out");
J
Jan Kiszka 已提交
1649 1650
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1651
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
1652

1653
    s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
1654 1655
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
                                 "<QEMU>/View/Zoom Fixed");
J
Jan Kiszka 已提交
1656 1657
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1658
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
1659

1660
    s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
A
Anthony Liguori 已提交
1661
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
1662 1663

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

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

1669
    s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
1670 1671
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
                                 "<QEMU>/View/Grab Input");
J
Jan Kiszka 已提交
1672 1673
    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
                            HOTKEY_MODIFIERS);
A
Anthony Liguori 已提交
1674
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
1675

A
Anthony Liguori 已提交
1676
    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1677
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
A
Anthony Liguori 已提交
1678

1679
    /* gfx */
1680 1681 1682 1683 1684 1685 1686 1687 1688
    for (vc = 0;; vc++) {
        con = qemu_console_lookup_by_index(vc);
        if (!con || !qemu_console_is_graphic(con)) {
            break;
        }
        group = gd_vc_gfx_init(s, &s->vc[vc], con,
                               vc, group, view_menu);
        s->nb_vcs++;
    }
A
Anthony Liguori 已提交
1689

1690
#if defined(CONFIG_VTE)
1691
    /* vte */
1692
    gd_vcs_init(s, group, view_menu);
1693
#endif
1694

A
Anthony Liguori 已提交
1695
    separator = gtk_separator_menu_item_new();
A
Anthony Liguori 已提交
1696
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
A
Anthony Liguori 已提交
1697

1698
    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
A
Anthony Liguori 已提交
1699
    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
A
Anthony Liguori 已提交
1700

1701 1702 1703
    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 已提交
1704 1705 1706 1707 1708 1709
    return view_menu;
}

static void gd_create_menus(GtkDisplayState *s)
{
    GtkAccelGroup *accel_group;
A
Anthony Liguori 已提交
1710

A
Anthony Liguori 已提交
1711 1712 1713 1714 1715
    accel_group = gtk_accel_group_new();
    s->machine_menu = gd_create_menu_machine(s, accel_group);
    s->view_menu = gd_create_menu_view(s, accel_group);

    s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
1716 1717 1718
    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 已提交
1719

A
Anthony Liguori 已提交
1720
    s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
A
Anthony Liguori 已提交
1721 1722
    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 已提交
1723 1724 1725 1726

    g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
    gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
    s->accel_group = accel_group;
A
Anthony Liguori 已提交
1727 1728
}

1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751
static void gd_set_keycode_type(GtkDisplayState *s)
{
#ifndef _WIN32
    char *keycodes = NULL;
    GdkDisplay *display = gtk_widget_get_display(s->window);
    Display *x11_display = gdk_x11_display_get_xdisplay(display);
    XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask,
                                     XkbUseCoreKbd);

    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);
    }
#endif
}

1752
void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover)
A
Anthony Liguori 已提交
1753 1754
{
    GtkDisplayState *s = g_malloc0(sizeof(*s));
S
Stefan Weil 已提交
1755
    char *filename;
A
Anthony Liguori 已提交
1756 1757 1758 1759

    gtk_init(NULL, NULL);

    s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1760 1761 1762
#if GTK_CHECK_VERSION(3, 2, 0)
    s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
A
Anthony Liguori 已提交
1763
    s->vbox = gtk_vbox_new(FALSE, 0);
1764
#endif
A
Anthony Liguori 已提交
1765 1766 1767
    s->notebook = gtk_notebook_new();
    s->menu_bar = gtk_menu_bar_new();

1768
    s->free_scale = FALSE;
A
Anthony Liguori 已提交
1769

1770 1771 1772 1773
    setlocale(LC_ALL, "");
    bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
    textdomain("qemu");

A
Anthony Liguori 已提交
1774 1775 1776 1777 1778 1779
    s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);

    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 已提交
1780
    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu_logo_no_text.svg");
S
Stefan Weil 已提交
1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791
    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 已提交
1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807
    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);

P
Peter Wu 已提交
1808 1809 1810
    if (full_screen) {
        gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
    }
1811 1812 1813
    if (grab_on_hover) {
        gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
    }
P
Peter Wu 已提交
1814

1815
    gd_set_keycode_type(s);
A
Anthony Liguori 已提交
1816
}
1817 1818 1819 1820 1821 1822 1823

void early_gtk_display_init(void)
{
#if defined(CONFIG_VTE)
    register_vc_handler(gd_vc_handler);
#endif
}