Statistics
| Branch: | Revision:

root / ui / gtk.c @ c6158483

History | View | Annotate | Download (31.4 kB)

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

    
34
#include <gtk/gtk.h>
35
#include <gdk/gdkkeysyms.h>
36
#include <vte/vte.h>
37
#include <sys/types.h>
38
#include <sys/socket.h>
39
#include <sys/un.h>
40
#include <sys/wait.h>
41
#include <pty.h>
42
#include <math.h>
43

    
44
#include "qemu-common.h"
45
#include "ui/console.h"
46
#include "sysemu/sysemu.h"
47
#include "qmp-commands.h"
48
#include "x_keymap.h"
49
#include "keymaps.h"
50
#include "char/char.h"
51

    
52
//#define DEBUG_GTK
53

    
54
#ifdef DEBUG_GTK
55
#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
56
#else
57
#define DPRINTF(fmt, ...) do { } while (0)
58
#endif
59

    
60
#define MAX_VCS 10
61

    
62
typedef struct VirtualConsole
63
{
64
    GtkWidget *menu_item;
65
    GtkWidget *terminal;
66
    GtkWidget *scrolled_window;
67
    CharDriverState *chr;
68
    int fd;
69
} VirtualConsole;
70

    
71
typedef struct GtkDisplayState
72
{
73
    GtkWidget *window;
74

    
75
    GtkWidget *menu_bar;
76

    
77
    GtkWidget *file_menu_item;
78
    GtkWidget *file_menu;
79
    GtkWidget *quit_item;
80

    
81
    GtkWidget *view_menu_item;
82
    GtkWidget *view_menu;
83
    GtkWidget *full_screen_item;
84
    GtkWidget *zoom_in_item;
85
    GtkWidget *zoom_out_item;
86
    GtkWidget *zoom_fixed_item;
87
    GtkWidget *zoom_fit_item;
88
    GtkWidget *grab_item;
89
    GtkWidget *grab_on_hover_item;
90
    GtkWidget *vga_item;
91

    
92
    int nb_vcs;
93
    VirtualConsole vc[MAX_VCS];
94

    
95
    GtkWidget *show_tabs_item;
96

    
97
    GtkWidget *vbox;
98
    GtkWidget *notebook;
99
    GtkWidget *drawing_area;
100
    cairo_surface_t *surface;
101
    DisplayChangeListener dcl;
102
    DisplayState *ds;
103
    int button_mask;
104
    int last_x;
105
    int last_y;
106

    
107
    double scale_x;
108
    double scale_y;
109
    gboolean full_screen;
110

    
111
    GdkCursor *null_cursor;
112
    Notifier mouse_mode_notifier;
113
    gboolean free_scale;
114
} GtkDisplayState;
115

    
116
static GtkDisplayState *global_state;
117

    
118
/** Utility Functions **/
119

    
120
static bool gd_is_grab_active(GtkDisplayState *s)
121
{
122
    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
123
}
124

    
125
static bool gd_grab_on_hover(GtkDisplayState *s)
126
{
127
    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
128
}
129

    
130
static bool gd_on_vga(GtkDisplayState *s)
131
{
132
    return gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0;
133
}
134

    
135
static void gd_update_cursor(GtkDisplayState *s, gboolean override)
136
{
137
    GdkWindow *window;
138
    bool on_vga;
139

    
140
    window = gtk_widget_get_window(GTK_WIDGET(s->drawing_area));
141

    
142
    on_vga = gd_on_vga(s);
143

    
144
    if ((override || on_vga) &&
145
        (s->full_screen || kbd_mouse_is_absolute() || gd_is_grab_active(s))) {
146
        gdk_window_set_cursor(window, s->null_cursor);
147
    } else {
148
        gdk_window_set_cursor(window, NULL);
149
    }
150
}
151

    
152
static void gd_update_caption(GtkDisplayState *s)
153
{
154
    const char *status = "";
155
    gchar *title;
156
    const char *grab = "";
157

    
158
    if (gd_is_grab_active(s)) {
159
        grab = " - Press Ctrl+Alt+G to release grab";
160
    }
161

    
162
    if (!runstate_is_running()) {
163
        status = " [Stopped]";
164
    }
165

    
166
    if (qemu_name) {
167
        title = g_strdup_printf("QEMU (%s)%s%s", qemu_name, status, grab);
168
    } else {
169
        title = g_strdup_printf("QEMU%s%s", status, grab);
170
    }
171

    
172
    gtk_window_set_title(GTK_WINDOW(s->window), title);
173

    
174
    g_free(title);
175
}
176

    
177
/** DisplayState Callbacks **/
178

    
179
static void gd_update(DisplayState *ds, int x, int y, int w, int h)
180
{
181
    GtkDisplayState *s = ds->opaque;
182
    int x1, x2, y1, y2;
183
    int mx, my;
184
    int fbw, fbh;
185
    int ww, wh;
186

    
187
    DPRINTF("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h);
188

    
189
    x1 = floor(x * s->scale_x);
190
    y1 = floor(y * s->scale_y);
191

    
192
    x2 = ceil(x * s->scale_x + w * s->scale_x);
193
    y2 = ceil(y * s->scale_y + h * s->scale_y);
194

    
195
    fbw = ds_get_width(s->ds) * s->scale_x;
196
    fbh = ds_get_height(s->ds) * s->scale_y;
197

    
198
    gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
199

    
200
    mx = my = 0;
201
    if (ww > fbw) {
202
        mx = (ww - fbw) / 2;
203
    }
204
    if (wh > fbh) {
205
        my = (wh - fbh) / 2;
206
    }
207

    
208
    gtk_widget_queue_draw_area(s->drawing_area, mx + x1, my + y1, (x2 - x1), (y2 - y1));
209
}
210

    
211
static void gd_refresh(DisplayState *ds)
212
{
213
    vga_hw_update();
214
}
215

    
216
static void gd_resize(DisplayState *ds)
217
{
218
    GtkDisplayState *s = ds->opaque;
219
    cairo_format_t kind;
220
    int stride;
221

    
222
    DPRINTF("resize(width=%d, height=%d)\n",
223
            ds_get_width(ds), ds_get_height(ds));
224

    
225
    if (s->surface) {
226
        cairo_surface_destroy(s->surface);
227
    }
228

    
229
    switch (ds->surface->pf.bits_per_pixel) {
230
    case 8:
231
        kind = CAIRO_FORMAT_A8;
232
        break;
233
    case 16:
234
        kind = CAIRO_FORMAT_RGB16_565;
235
        break;
236
    case 32:
237
        kind = CAIRO_FORMAT_RGB24;
238
        break;
239
    default:
240
        g_assert_not_reached();
241
        break;
242
    }
243

    
244
    stride = cairo_format_stride_for_width(kind, ds_get_width(ds));
245
    g_assert(ds_get_linesize(ds) == stride);
246

    
247
    s->surface = cairo_image_surface_create_for_data(ds_get_data(ds),
248
                                                     kind,
249
                                                     ds_get_width(ds),
250
                                                     ds_get_height(ds),
251
                                                     ds_get_linesize(ds));
252

    
253
    if (!s->full_screen) {
254
        GtkRequisition req;
255
        double sx, sy;
256

    
257
        if (s->free_scale) {
258
            sx = s->scale_x;
259
            sy = s->scale_y;
260

    
261
            s->scale_y = 1.0;
262
            s->scale_x = 1.0;
263
        } else {
264
            sx = 1.0;
265
            sy = 1.0;
266
        }
267

    
268
        gtk_widget_set_size_request(s->drawing_area,
269
                                    ds_get_width(ds) * s->scale_x,
270
                                    ds_get_height(ds) * s->scale_y);
271
        gtk_widget_size_request(s->vbox, &req);
272

    
273
        gtk_window_resize(GTK_WINDOW(s->window),
274
                          req.width * sx, req.height * sy);
275
    }
276
}
277

    
278
/** QEMU Events **/
279

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

    
284
    gd_update_caption(s);
285
}
286

    
287
static void gd_mouse_mode_change(Notifier *notify, void *data)
288
{
289
    gd_update_cursor(container_of(notify, GtkDisplayState, mouse_mode_notifier),
290
                     FALSE);
291
}
292

    
293
/** GTK Events **/
294

    
295
static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
296
                                void *opaque)
297
{
298
    GtkDisplayState *s = opaque;
299

    
300
    if (!no_quit) {
301
        unregister_displaychangelistener(s->ds, &s->dcl);
302
        qmp_quit(NULL);
303
        return FALSE;
304
    }
305

    
306
    return TRUE;
307
}
308

    
309
static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
310
{
311
    GtkDisplayState *s = opaque;
312
    int mx, my;
313
    int ww, wh;
314
    int fbw, fbh;
315

    
316
    if (!gtk_widget_get_realized(widget)) {
317
        return FALSE;
318
    }
319

    
320
    fbw = ds_get_width(s->ds);
321
    fbh = ds_get_height(s->ds);
322

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

    
325
    if (s->full_screen) {
326
        s->scale_x = (double)ww / fbw;
327
        s->scale_y = (double)wh / fbh;
328
    } else if (s->free_scale) {
329
        double sx, sy;
330

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

    
334
        s->scale_x = s->scale_y = MIN(sx, sy);
335
    }
336

    
337
    fbw *= s->scale_x;
338
    fbh *= s->scale_y;
339

    
340
    mx = my = 0;
341
    if (ww > fbw) {
342
        mx = (ww - fbw) / 2;
343
    }
344
    if (wh > fbh) {
345
        my = (wh - fbh) / 2;
346
    }
347

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

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

    
359
    cairo_scale(cr, s->scale_x, s->scale_y);
360
    cairo_set_source_surface(cr, s->surface, mx / s->scale_x, my / s->scale_y);
361
    cairo_paint(cr);
362

    
363
    return TRUE;
364
}
365

    
366
static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
367
                                void *opaque)
368
{
369
    cairo_t *cr;
370
    gboolean ret;
371

    
372
    cr = gdk_cairo_create(gtk_widget_get_window(widget));
373
    cairo_rectangle(cr,
374
                    expose->area.x,
375
                    expose->area.y,
376
                    expose->area.width,
377
                    expose->area.height);
378
    cairo_clip(cr);
379

    
380
    ret = gd_draw_event(widget, cr, opaque);
381

    
382
    cairo_destroy(cr);
383

    
384
    return ret;
385
}
386

    
387
static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
388
                                void *opaque)
389
{
390
    GtkDisplayState *s = opaque;
391
    int dx, dy;
392
    int x, y;
393
    int mx, my;
394
    int fbh, fbw;
395
    int ww, wh;
396

    
397
    fbw = ds_get_width(s->ds) * s->scale_x;
398
    fbh = ds_get_height(s->ds) * s->scale_y;
399

    
400
    gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
401

    
402
    mx = my = 0;
403
    if (ww > fbw) {
404
        mx = (ww - fbw) / 2;
405
    }
406
    if (wh > fbh) {
407
        my = (wh - fbh) / 2;
408
    }
409

    
410
    x = (motion->x - mx) / s->scale_x;
411
    y = (motion->y - my) / s->scale_y;
412

    
413
    if (x < 0 || y < 0 ||
414
        x >= ds_get_width(s->ds) ||
415
        y >= ds_get_height(s->ds)) {
416
        return TRUE;
417
    }
418

    
419
    if (kbd_mouse_is_absolute()) {
420
        dx = x * 0x7FFF / (ds_get_width(s->ds) - 1);
421
        dy = y * 0x7FFF / (ds_get_height(s->ds) - 1);
422
    } else if (s->last_x == -1 || s->last_y == -1) {
423
        dx = 0;
424
        dy = 0;
425
    } else {
426
        dx = x - s->last_x;
427
        dy = y - s->last_y;
428
    }
429

    
430
    s->last_x = x;
431
    s->last_y = y;
432

    
433
    if (kbd_mouse_is_absolute() || gd_is_grab_active(s)) {
434
        kbd_mouse_event(dx, dy, 0, s->button_mask);
435
    }
436

    
437
    if (!kbd_mouse_is_absolute() && gd_is_grab_active(s)) {
438
        GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(s->drawing_area));
439
        GdkDisplay *display = gdk_drawable_get_display(drawable);
440
        GdkScreen *screen = gdk_drawable_get_screen(drawable);
441
        int x = (int)motion->x_root;
442
        int y = (int)motion->y_root;
443

    
444
        /* In relative mode check to see if client pointer hit
445
         * one of the screen edges, and if so move it back by
446
         * 200 pixels. This is important because the pointer
447
         * in the server doesn't correspond 1-for-1, and so
448
         * may still be only half way across the screen. Without
449
         * this warp, the server pointer would thus appear to hit
450
         * an invisible wall */
451
        if (x == 0) {
452
            x += 200;
453
        }
454
        if (y == 0) {
455
            y += 200;
456
        }
457
        if (x == (gdk_screen_get_width(screen) - 1)) {
458
            x -= 200;
459
        }
460
        if (y == (gdk_screen_get_height(screen) - 1)) {
461
            y -= 200;
462
        }
463

    
464
        if (x != (int)motion->x_root || y != (int)motion->y_root) {
465
            gdk_display_warp_pointer(display, screen, x, y);
466
            s->last_x = -1;
467
            s->last_y = -1;
468
            return FALSE;
469
        }
470
    }
471
    return TRUE;
472
}
473

    
474
static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
475
                                void *opaque)
476
{
477
    GtkDisplayState *s = opaque;
478
    int dx, dy;
479
    int n;
480

    
481
    if (button->button == 1) {
482
        n = 0x01;
483
    } else if (button->button == 2) {
484
        n = 0x04;
485
    } else if (button->button == 3) {
486
        n = 0x02;
487
    } else {
488
        n = 0x00;
489
    }
490

    
491
    if (button->type == GDK_BUTTON_PRESS) {
492
        s->button_mask |= n;
493
    } else if (button->type == GDK_BUTTON_RELEASE) {
494
        s->button_mask &= ~n;
495
    }
496

    
497
    if (kbd_mouse_is_absolute()) {
498
        dx = s->last_x * 0x7FFF / (ds_get_width(s->ds) - 1);
499
        dy = s->last_y * 0x7FFF / (ds_get_height(s->ds) - 1);
500
    } else {
501
        dx = 0;
502
        dy = 0;
503
    }
504

    
505
    kbd_mouse_event(dx, dy, 0, s->button_mask);
506
        
507
    return TRUE;
508
}
509

    
510
static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
511
{
512
    int gdk_keycode;
513
    int qemu_keycode;
514

    
515
    gdk_keycode = key->hardware_keycode;
516

    
517
    if (gdk_keycode < 9) {
518
        qemu_keycode = 0;
519
    } else if (gdk_keycode < 97) {
520
        qemu_keycode = gdk_keycode - 8;
521
    } else if (gdk_keycode < 158) {
522
        qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
523
    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
524
        qemu_keycode = 0x70;
525
    } else if (gdk_keycode == 211) { /* backslash */
526
        qemu_keycode = 0x73;
527
    } else {
528
        qemu_keycode = 0;
529
    }
530

    
531
    DPRINTF("translated GDK keycode %d to QEMU keycode %d (%s)\n",
532
            gdk_keycode, qemu_keycode,
533
            (key->type == GDK_KEY_PRESS) ? "down" : "up");
534

    
535
    if (qemu_keycode & SCANCODE_GREY) {
536
        kbd_put_keycode(SCANCODE_EMUL0);
537
    }
538

    
539
    if (key->type == GDK_KEY_PRESS) {
540
        kbd_put_keycode(qemu_keycode & SCANCODE_KEYCODEMASK);
541
    } else if (key->type == GDK_KEY_RELEASE) {
542
        kbd_put_keycode(qemu_keycode | SCANCODE_UP);
543
    } else {
544
        g_assert_not_reached();
545
    }
546

    
547
    return TRUE;
548
}
549

    
550
/** Window Menu Actions **/
551

    
552
static void gd_menu_quit(GtkMenuItem *item, void *opaque)
553
{
554
    qmp_quit(NULL);
555
}
556

    
557
static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
558
{
559
    GtkDisplayState *s = opaque;
560

    
561
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
562
        gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
563
    } else {
564
        int i;
565

    
566
        for (i = 0; i < s->nb_vcs; i++) {
567
            if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vc[i].menu_item))) {
568
                gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), i + 1);
569
                break;
570
            }
571
        }
572
    }
573
}
574

    
575
static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
576
{
577
    GtkDisplayState *s = opaque;
578

    
579
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
580
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
581
    } else {
582
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
583
    }
584
}
585

    
586
static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
587
{
588
    GtkDisplayState *s = opaque;
589

    
590
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->full_screen_item))) {
591
        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
592
        gtk_widget_set_size_request(s->menu_bar, 0, 0);
593
        gtk_widget_set_size_request(s->drawing_area, -1, -1);
594
        gtk_window_fullscreen(GTK_WINDOW(s->window));
595
        if (gd_on_vga(s)) {
596
            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), TRUE);
597
        }
598
        s->full_screen = TRUE;
599
    } else {
600
        gtk_window_unfullscreen(GTK_WINDOW(s->window));
601
        gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
602
        gtk_widget_set_size_request(s->menu_bar, -1, -1);
603
        gtk_widget_set_size_request(s->drawing_area,
604
                                    ds_get_width(s->ds), ds_get_height(s->ds));
605
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE);
606
        s->full_screen = FALSE;
607
        s->scale_x = 1.0;
608
        s->scale_y = 1.0;
609
    }
610

    
611
    gd_update_cursor(s, FALSE);
612
}
613

    
614
static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
615
{
616
    GtkDisplayState *s = opaque;
617

    
618
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
619
                                   FALSE);
620

    
621
    s->scale_x += .25;
622
    s->scale_y += .25;
623

    
624
    gd_resize(s->ds);
625
}
626

    
627
static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
628
{
629
    GtkDisplayState *s = opaque;
630

    
631
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
632
                                   FALSE);
633

    
634
    s->scale_x -= .25;
635
    s->scale_y -= .25;
636

    
637
    s->scale_x = MAX(s->scale_x, .25);
638
    s->scale_y = MAX(s->scale_y, .25);
639

    
640
    gd_resize(s->ds);
641
}
642

    
643
static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
644
{
645
    GtkDisplayState *s = opaque;
646

    
647
    s->scale_x = 1.0;
648
    s->scale_y = 1.0;
649

    
650
    gd_resize(s->ds);
651
}
652

    
653
static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
654
{
655
    GtkDisplayState *s = opaque;
656
    int ww, wh;
657

    
658
    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
659
        s->free_scale = TRUE;
660
    } else {
661
        s->free_scale = FALSE;
662
    }
663

    
664
    gd_resize(s->ds);
665

    
666
    gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
667
    gtk_widget_queue_draw_area(s->drawing_area, 0, 0, ww, wh);
668
}
669

    
670
static void gd_grab_keyboard(GtkDisplayState *s)
671
{
672
    gdk_keyboard_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
673
                      FALSE,
674
                      GDK_CURRENT_TIME);
675
}
676

    
677
static void gd_ungrab_keyboard(GtkDisplayState *s)
678
{
679
    gdk_keyboard_ungrab(GDK_CURRENT_TIME);
680
}
681

    
682
static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
683
{
684
    GtkDisplayState *s = opaque;
685

    
686
    if (gd_is_grab_active(s)) {
687
        gd_grab_keyboard(s);
688
        gdk_pointer_grab(gtk_widget_get_window(GTK_WIDGET(s->drawing_area)),
689
                         FALSE, /* All events to come to our window directly */
690
                         GDK_POINTER_MOTION_MASK |
691
                         GDK_BUTTON_PRESS_MASK |
692
                         GDK_BUTTON_RELEASE_MASK |
693
                         GDK_BUTTON_MOTION_MASK |
694
                         GDK_SCROLL_MASK,
695
                         NULL, /* Allow cursor to move over entire desktop */
696
                         s->null_cursor,
697
                         GDK_CURRENT_TIME);
698
    } else {
699
        gd_ungrab_keyboard(s);
700
        gdk_pointer_ungrab(GDK_CURRENT_TIME);
701
    }
702

    
703
    gd_update_caption(s);
704
    gd_update_cursor(s, FALSE);
705
}
706

    
707
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
708
                           gpointer data)
709
{
710
    GtkDisplayState *s = data;
711
    guint last_page;
712
    gboolean on_vga;
713

    
714
    if (!gtk_widget_get_realized(s->notebook)) {
715
        return;
716
    }
717

    
718
    last_page = gtk_notebook_get_current_page(nb);
719

    
720
    if (last_page) {
721
        gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
722
    }
723

    
724
    on_vga = arg2 == 0;
725

    
726
    if (!on_vga) {
727
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
728
                                       FALSE);
729
    } else if (s->full_screen) {
730
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
731
                                       TRUE);
732
    }
733

    
734
    if (arg2 == 0) {
735
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
736
    } else {
737
        VirtualConsole *vc = &s->vc[arg2 - 1];
738
        VteTerminal *term = VTE_TERMINAL(vc->terminal);
739
        int width, height;
740

    
741
        width = 80 * vte_terminal_get_char_width(term);
742
        height = 25 * vte_terminal_get_char_height(term);
743

    
744
        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
745
        gtk_widget_set_size_request(vc->terminal, width, height);
746
    }
747

    
748
    gtk_widget_set_sensitive(s->grab_item, on_vga);
749

    
750
    gd_update_cursor(s, TRUE);
751
}
752

    
753
static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data)
754
{
755
    GtkDisplayState *s = data;
756

    
757
    if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) {
758
        gd_grab_keyboard(s);
759
    }
760

    
761
    return TRUE;
762
}
763

    
764
static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data)
765
{
766
    GtkDisplayState *s = data;
767

    
768
    if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) {
769
        gd_ungrab_keyboard(s);
770
    }
771

    
772
    return TRUE;
773
}
774

    
775
/** Virtual Console Callbacks **/
776

    
777
static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
778
{
779
    VirtualConsole *vc = chr->opaque;
780

    
781
    return write(vc->fd, buf, len);
782
}
783

    
784
static int nb_vcs;
785
static CharDriverState *vcs[MAX_VCS];
786

    
787
static CharDriverState *gd_vc_handler(QemuOpts *opts)
788
{
789
    CharDriverState *chr;
790

    
791
    chr = g_malloc0(sizeof(*chr));
792
    chr->chr_write = gd_vc_chr_write;
793

    
794
    vcs[nb_vcs++] = chr;
795

    
796
    return chr;
797
}
798

    
799
void early_gtk_display_init(void)
800
{
801
    register_vc_handler(gd_vc_handler);
802
}
803

    
804
static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque)
805
{
806
    VirtualConsole *vc = opaque;
807
    uint8_t buffer[1024];
808
    ssize_t len;
809

    
810
    len = read(vc->fd, buffer, sizeof(buffer));
811
    if (len <= 0) {
812
        return FALSE;
813
    }
814

    
815
    qemu_chr_be_write(vc->chr, buffer, len);
816

    
817
    return TRUE;
818
}
819

    
820
static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group)
821
{
822
    const char *label;
823
    char buffer[32];
824
    char path[32];
825
    VtePty *pty;
826
    GIOChannel *chan;
827
    GtkWidget *scrolled_window;
828
    GtkAdjustment *vadjustment;
829
    int master_fd, slave_fd, ret;
830
    struct termios tty;
831

    
832
    snprintf(buffer, sizeof(buffer), "vc%d", index);
833
    snprintf(path, sizeof(path), "<QEMU>/View/VC%d", index);
834

    
835
    vc->chr = vcs[index];
836

    
837
    if (vc->chr->label) {
838
        label = vc->chr->label;
839
    } else {
840
        label = buffer;
841
    }
842

    
843
    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, label);
844
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
845
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
846
    gtk_accel_map_add_entry(path, GDK_KEY_2 + index, GDK_CONTROL_MASK | GDK_MOD1_MASK);
847

    
848
    vc->terminal = vte_terminal_new();
849

    
850
    ret = openpty(&master_fd, &slave_fd, NULL, NULL, NULL);
851
    g_assert(ret != -1);
852

    
853
    /* Set raw attributes on the pty. */
854
    tcgetattr(slave_fd, &tty);
855
    cfmakeraw(&tty);
856
    tcsetattr(slave_fd, TCSAFLUSH, &tty);
857

    
858
    pty = vte_pty_new_foreign(master_fd, NULL);
859

    
860
    vte_terminal_set_pty_object(VTE_TERMINAL(vc->terminal), pty);
861

    
862
    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->terminal), -1);
863

    
864
    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->terminal));
865

    
866
    scrolled_window = gtk_scrolled_window_new(NULL, vadjustment);
867
    gtk_container_add(GTK_CONTAINER(scrolled_window), vc->terminal);
868

    
869
    vte_terminal_set_size(VTE_TERMINAL(vc->terminal), 80, 25);
870

    
871
    vc->fd = slave_fd;
872
    vc->chr->opaque = vc;
873
    vc->scrolled_window = scrolled_window;
874

    
875
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vc->scrolled_window),
876
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
877

    
878
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), scrolled_window, gtk_label_new(label));
879
    g_signal_connect(vc->menu_item, "activate",
880
                     G_CALLBACK(gd_menu_switch_vc), s);
881

    
882
    gtk_menu_append(GTK_MENU(s->view_menu), vc->menu_item);
883

    
884
    qemu_chr_generic_open(vc->chr);
885
    if (vc->chr->init) {
886
        vc->chr->init(vc->chr);
887
    }
888

    
889
    chan = g_io_channel_unix_new(vc->fd);
890
    g_io_add_watch(chan, G_IO_IN, gd_vc_in, vc);
891

    
892
    return group;
893
}
894

    
895
/** Window Creation **/
896

    
897
static void gd_connect_signals(GtkDisplayState *s)
898
{
899
    g_signal_connect(s->show_tabs_item, "activate",
900
                     G_CALLBACK(gd_menu_show_tabs), s);
901

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

    
905
    g_signal_connect(s->drawing_area, "expose-event",
906
                     G_CALLBACK(gd_expose_event), s);
907
    g_signal_connect(s->drawing_area, "motion-notify-event",
908
                     G_CALLBACK(gd_motion_event), s);
909
    g_signal_connect(s->drawing_area, "button-press-event",
910
                     G_CALLBACK(gd_button_event), s);
911
    g_signal_connect(s->drawing_area, "button-release-event",
912
                     G_CALLBACK(gd_button_event), s);
913
    g_signal_connect(s->drawing_area, "key-press-event",
914
                     G_CALLBACK(gd_key_event), s);
915
    g_signal_connect(s->drawing_area, "key-release-event",
916
                     G_CALLBACK(gd_key_event), s);
917

    
918
    g_signal_connect(s->quit_item, "activate",
919
                     G_CALLBACK(gd_menu_quit), s);
920
    g_signal_connect(s->full_screen_item, "activate",
921
                     G_CALLBACK(gd_menu_full_screen), s);
922
    g_signal_connect(s->zoom_in_item, "activate",
923
                     G_CALLBACK(gd_menu_zoom_in), s);
924
    g_signal_connect(s->zoom_out_item, "activate",
925
                     G_CALLBACK(gd_menu_zoom_out), s);
926
    g_signal_connect(s->zoom_fixed_item, "activate",
927
                     G_CALLBACK(gd_menu_zoom_fixed), s);
928
    g_signal_connect(s->zoom_fit_item, "activate",
929
                     G_CALLBACK(gd_menu_zoom_fit), s);
930
    g_signal_connect(s->vga_item, "activate",
931
                     G_CALLBACK(gd_menu_switch_vc), s);
932
    g_signal_connect(s->grab_item, "activate",
933
                     G_CALLBACK(gd_menu_grab_input), s);
934
    g_signal_connect(s->notebook, "switch-page",
935
                     G_CALLBACK(gd_change_page), s);
936
    g_signal_connect(s->drawing_area, "enter-notify-event",
937
                     G_CALLBACK(gd_enter_event), s);
938
    g_signal_connect(s->drawing_area, "leave-notify-event",
939
                     G_CALLBACK(gd_leave_event), s);
940
}
941

    
942
static void gd_create_menus(GtkDisplayState *s)
943
{
944
    GtkStockItem item;
945
    GtkAccelGroup *accel_group;
946
    GSList *group = NULL;
947
    GtkWidget *separator;
948
    int i;
949

    
950
    accel_group = gtk_accel_group_new();
951
    s->file_menu = gtk_menu_new();
952
    gtk_menu_set_accel_group(GTK_MENU(s->file_menu), accel_group);
953
    s->file_menu_item = gtk_menu_item_new_with_mnemonic("_File");
954

    
955
    s->quit_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
956
    gtk_stock_lookup(GTK_STOCK_QUIT, &item);
957
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
958
                                 "<QEMU>/File/Quit");
959
    gtk_accel_map_add_entry("<QEMU>/File/Quit", item.keyval, item.modifier);
960

    
961
    s->view_menu = gtk_menu_new();
962
    gtk_menu_set_accel_group(GTK_MENU(s->view_menu), accel_group);
963
    s->view_menu_item = gtk_menu_item_new_with_mnemonic("_View");
964

    
965
    s->full_screen_item = gtk_check_menu_item_new_with_mnemonic("_Full Screen");
966
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item),
967
                                 "<QEMU>/View/Full Screen");
968
    gtk_accel_map_add_entry("<QEMU>/View/Full Screen", GDK_KEY_f, GDK_CONTROL_MASK | GDK_MOD1_MASK);
969
    gtk_menu_append(GTK_MENU(s->view_menu), s->full_screen_item);
970

    
971
    separator = gtk_separator_menu_item_new();
972
    gtk_menu_append(GTK_MENU(s->view_menu), separator);
973

    
974
    s->zoom_in_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_IN, NULL);
975
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
976
                                 "<QEMU>/View/Zoom In");
977
    gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus, GDK_CONTROL_MASK | GDK_MOD1_MASK);
978
    gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_in_item);
979

    
980
    s->zoom_out_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_OUT, NULL);
981
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
982
                                 "<QEMU>/View/Zoom Out");
983
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus, GDK_CONTROL_MASK | GDK_MOD1_MASK);
984
    gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_out_item);
985

    
986
    s->zoom_fixed_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_100, NULL);
987
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
988
                                 "<QEMU>/View/Zoom Fixed");
989
    gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0, GDK_CONTROL_MASK | GDK_MOD1_MASK);
990
    gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fixed_item);
991

    
992
    s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic("Zoom To _Fit");
993
    gtk_menu_append(GTK_MENU(s->view_menu), s->zoom_fit_item);
994

    
995
    separator = gtk_separator_menu_item_new();
996
    gtk_menu_append(GTK_MENU(s->view_menu), separator);
997

    
998
    s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic("Grab On _Hover");
999
    gtk_menu_append(GTK_MENU(s->view_menu), s->grab_on_hover_item);
1000

    
1001
    s->grab_item = gtk_check_menu_item_new_with_mnemonic("_Grab Input");
1002
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
1003
                                 "<QEMU>/View/Grab Input");
1004
    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1005
    gtk_menu_append(GTK_MENU(s->view_menu), s->grab_item);
1006

    
1007
    separator = gtk_separator_menu_item_new();
1008
    gtk_menu_append(GTK_MENU(s->view_menu), separator);
1009

    
1010
    s->vga_item = gtk_radio_menu_item_new_with_mnemonic(group, "_VGA");
1011
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(s->vga_item));
1012
    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->vga_item),
1013
                                 "<QEMU>/View/VGA");
1014
    gtk_accel_map_add_entry("<QEMU>/View/VGA", GDK_KEY_1, GDK_CONTROL_MASK | GDK_MOD1_MASK);
1015
    gtk_menu_append(GTK_MENU(s->view_menu), s->vga_item);
1016

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

    
1020
        group = gd_vc_init(s, vc, i, group);
1021
        s->nb_vcs++;
1022
    }
1023

    
1024
    separator = gtk_separator_menu_item_new();
1025
    gtk_menu_append(GTK_MENU(s->view_menu), separator);
1026

    
1027
    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic("Show _Tabs");
1028
    gtk_menu_append(GTK_MENU(s->view_menu), s->show_tabs_item);
1029

    
1030
    g_object_set_data(G_OBJECT(s->window), "accel_group", accel_group);
1031
    gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
1032

    
1033
    gtk_menu_append(GTK_MENU(s->file_menu), s->quit_item);
1034
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->file_menu_item), s->file_menu);
1035
    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->file_menu_item);
1036

    
1037
    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
1038
    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
1039
}
1040

    
1041
void gtk_display_init(DisplayState *ds)
1042
{
1043
    GtkDisplayState *s = g_malloc0(sizeof(*s));
1044

    
1045
    gtk_init(NULL, NULL);
1046

    
1047
    ds->opaque = s;
1048
    s->ds = ds;
1049
    s->dcl.dpy_gfx_update = gd_update;
1050
    s->dcl.dpy_gfx_resize = gd_resize;
1051
    s->dcl.dpy_refresh = gd_refresh;
1052

    
1053
    s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1054
    s->vbox = gtk_vbox_new(FALSE, 0);
1055
    s->notebook = gtk_notebook_new();
1056
    s->drawing_area = gtk_drawing_area_new();
1057
    s->menu_bar = gtk_menu_bar_new();
1058

    
1059
    s->scale_x = 1.0;
1060
    s->scale_y = 1.0;
1061
    s->free_scale = FALSE;
1062

    
1063
    s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
1064

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

    
1069
    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), s->drawing_area, gtk_label_new("VGA"));
1070

    
1071
    gd_create_menus(s);
1072

    
1073
    gd_connect_signals(s);
1074

    
1075
    gtk_widget_add_events(s->drawing_area,
1076
                          GDK_POINTER_MOTION_MASK |
1077
                          GDK_BUTTON_PRESS_MASK |
1078
                          GDK_BUTTON_RELEASE_MASK |
1079
                          GDK_BUTTON_MOTION_MASK |
1080
                          GDK_ENTER_NOTIFY_MASK |
1081
                          GDK_LEAVE_NOTIFY_MASK |
1082
                          GDK_SCROLL_MASK |
1083
                          GDK_KEY_PRESS_MASK);
1084
    gtk_widget_set_double_buffered(s->drawing_area, FALSE);
1085
    gtk_widget_set_can_focus(s->drawing_area, TRUE);
1086

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

    
1090
    gd_update_caption(s);
1091

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

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

    
1097
    gtk_widget_show_all(s->window);
1098

    
1099
    register_displaychangelistener(ds, &s->dcl);
1100

    
1101
    global_state = s;
1102
}