diff --git a/Makefile b/Makefile index 0c59faa..a0a0a9b 100644 --- a/Makefile +++ b/Makefile @@ -207,6 +207,15 @@ ifeq ($(call try-cc,$(SOURCE_BFD),$(FLAGS_BFD) -static),y) LIBS_STATOPT += -lbfd endif +FLAGS_GTK3 := $(CFLAGS) $(shell pkg-config --libs --cflags gtk+-3.0 2>/dev/null) +ifeq ($(call try-cc,$(SOURCE_GTK3),$(FLAGS_GTK3)),y) + OBJS_DYNOPT += ui/gtk3.o + CFLAGS_DYNOPT += -DCONFIG_HAS_GTK3 $(shell pkg-config --cflags gtk+-3.0 2>/dev/null) + LIBS_DYNOPT += $(shell pkg-config --libs gtk+-3.0 2>/dev/null) +else + $(warning warning GTK3 not found, disables GTK3 support. Please install gtk3-devel or libgtk3.0-dev) +endif + FLAGS_VNCSERVER := $(CFLAGS) -lvncserver ifeq ($(call try-cc,$(SOURCE_VNCSERVER),$(FLAGS_VNCSERVER)),y) OBJS_DYNOPT += ui/vnc.o diff --git a/builtin-run.c b/builtin-run.c index d0b876a..c6f5862 100644 --- a/builtin-run.c +++ b/builtin-run.c @@ -112,6 +112,7 @@ void kvm_run_set_wrapper_sandbox(void) OPT_BOOLEAN('\0', "balloon", &(cfg)->balloon, "Enable virtio" \ " balloon"), \ OPT_BOOLEAN('\0', "vnc", &(cfg)->vnc, "Enable VNC framebuffer"),\ + OPT_BOOLEAN('\0', "gtk", &(cfg)->gtk, "Enable GTK framebuffer"),\ OPT_BOOLEAN('\0', "sdl", &(cfg)->sdl, "Enable SDL framebuffer"),\ OPT_BOOLEAN('\0', "rng", &(cfg)->virtio_rng, "Enable virtio" \ " Random Number Generator"), \ @@ -604,7 +605,7 @@ static struct kvm *kvm_cmd_run_init(int argc, const char **argv) kvm->cfg.network = DEFAULT_NETWORK; memset(real_cmdline, 0, sizeof(real_cmdline)); - kvm__arch_set_cmdline(real_cmdline, kvm->cfg.vnc || kvm->cfg.sdl); + kvm__arch_set_cmdline(real_cmdline, kvm->cfg.vnc || kvm->cfg.sdl || kvm->cfg.gtk); if (strlen(real_cmdline) > 0) strcat(real_cmdline, " "); diff --git a/config/feature-tests.mak b/config/feature-tests.mak index 4a81f56..927ac54 100644 --- a/config/feature-tests.mak +++ b/config/feature-tests.mak @@ -175,3 +175,14 @@ int main(void) return 0; } endef + +define SOURCE_GTK3 +#include + +int main(void) +{ + gtk_main(); + + return 0; +} +endef diff --git a/hw/vesa.c b/hw/vesa.c index 33a675f..47e3a69 100644 --- a/hw/vesa.c +++ b/hw/vesa.c @@ -59,7 +59,7 @@ struct framebuffer *vesa__init(struct kvm *kvm) char *mem; int r; - if (!kvm->cfg.vnc && !kvm->cfg.sdl) + if (!kvm->cfg.vnc && !kvm->cfg.sdl && !kvm->cfg.gtk) return NULL; r = irq__register_device(PCI_DEVICE_ID_VESA, &pin, &line); diff --git a/include/kvm/gtk3.h b/include/kvm/gtk3.h new file mode 100644 index 0000000..b02dc13 --- /dev/null +++ b/include/kvm/gtk3.h @@ -0,0 +1,28 @@ +#ifndef KVM__GTK3_H +#define KVM__GTK3_H + +#include "kvm/util.h" + +struct framebuffer; + +#ifdef CONFIG_HAS_GTK3 +int kvm_gtk_init(struct kvm *kvm); +int kvm_gtk_exit(struct kvm *kvm); +#else +static inline int kvm_gtk_init(struct kvm *kvm) +{ + if (kvm->cfg.gtk) + die("GTK3 support not compiled in. (install the gtk3-devel or libgtk3.0-dev package)"); + + return 0; +} +static inline int kvm_gtk_exit(struct kvm *kvm) +{ + if (kvm->cfg.gtk) + die("GTK3 support not compiled in. (install the gtk3-devel or libgtk3.0-dev package)"); + + return 0; +} +#endif + +#endif /* KVM__GTK3_H */ diff --git a/include/kvm/kvm-config.h b/include/kvm/kvm-config.h index c66f481..386fa8c 100644 --- a/include/kvm/kvm-config.h +++ b/include/kvm/kvm-config.h @@ -48,6 +48,7 @@ struct kvm_config { struct virtio_net_params *net_params; bool single_step; bool vnc; + bool gtk; bool sdl; bool balloon; bool using_rootfs; diff --git a/ui/gtk3.c b/ui/gtk3.c new file mode 100644 index 0000000..b2335bc --- /dev/null +++ b/ui/gtk3.c @@ -0,0 +1,326 @@ +#include "kvm/gtk3.h" + +#include "kvm/framebuffer.h" +#include "kvm/kvm-cpu.h" +#include "kvm/i8042.h" +#include "kvm/vesa.h" +#include "kvm/kvm.h" + +#include +#include +#include + +#define FRAME_RATE 25 + +#define SCANCODE_UNKNOWN 0 +#define SCANCODE_NORMAL 1 +#define SCANCODE_ESCAPED 2 +#define SCANCODE_KEY_PAUSE 3 +#define SCANCODE_KEY_PRNTSCRN 4 + +struct set2_scancode { + u8 code; + u8 type; +}; + +#define DEFINE_SC(_code) { \ + .code = _code, \ + .type = SCANCODE_NORMAL, \ +} + +/* escaped scancodes */ +#define DEFINE_ESC(_code) { \ + .code = _code, \ + .type = SCANCODE_ESCAPED, \ +} + +static const struct set2_scancode const keymap[256] = { + [9] = DEFINE_SC(0x76), /* */ + [10] = DEFINE_SC(0x16), /* 1 */ + [11] = DEFINE_SC(0x1e), /* 2 */ + [12] = DEFINE_SC(0x26), /* 3 */ + [13] = DEFINE_SC(0x25), /* 4 */ + [14] = DEFINE_SC(0x2e), /* 5 */ + [15] = DEFINE_SC(0x36), /* 6 */ + [16] = DEFINE_SC(0x3d), /* 7 */ + [17] = DEFINE_SC(0x3e), /* 8 */ + [18] = DEFINE_SC(0x46), /* 9 */ + [19] = DEFINE_SC(0x45), /* 9 */ + [20] = DEFINE_SC(0x4e), /* - */ + [21] = DEFINE_SC(0x55), /* + */ + [22] = DEFINE_SC(0x66), /* */ + [23] = DEFINE_SC(0x0d), /* */ + [24] = DEFINE_SC(0x15), /* q */ + [25] = DEFINE_SC(0x1d), /* w */ + [26] = DEFINE_SC(0x24), /* e */ + [27] = DEFINE_SC(0x2d), /* r */ + [28] = DEFINE_SC(0x2c), /* t */ + [29] = DEFINE_SC(0x35), /* y */ + [30] = DEFINE_SC(0x3c), /* u */ + [31] = DEFINE_SC(0x43), /* i */ + [32] = DEFINE_SC(0x44), /* o */ + [33] = DEFINE_SC(0x4d), /* p */ + [34] = DEFINE_SC(0x54), /* [ */ + [35] = DEFINE_SC(0x5b), /* ] */ + [36] = DEFINE_SC(0x5a), /* */ + [37] = DEFINE_SC(0x14), /* */ + [38] = DEFINE_SC(0x1c), /* a */ + [39] = DEFINE_SC(0x1b), /* s */ + [40] = DEFINE_SC(0x23), /* d */ + [41] = DEFINE_SC(0x2b), /* f */ + [42] = DEFINE_SC(0x34), /* g */ + [43] = DEFINE_SC(0x33), /* h */ + [44] = DEFINE_SC(0x3b), /* j */ + [45] = DEFINE_SC(0x42), /* k */ + [46] = DEFINE_SC(0x4b), /* l */ + [47] = DEFINE_SC(0x4c), /* ; */ + [48] = DEFINE_SC(0x52), /* ' */ + [49] = DEFINE_SC(0x0e), /* ` */ + [50] = DEFINE_SC(0x12), /* */ + [51] = DEFINE_SC(0x5d), /* \ */ + [52] = DEFINE_SC(0x1a), /* z */ + [53] = DEFINE_SC(0x22), /* x */ + [54] = DEFINE_SC(0x21), /* c */ + [55] = DEFINE_SC(0x2a), /* v */ + [56] = DEFINE_SC(0x32), /* b */ + [57] = DEFINE_SC(0x31), /* n */ + [58] = DEFINE_SC(0x3a), /* m */ + [59] = DEFINE_SC(0x41), /* < */ + [60] = DEFINE_SC(0x49), /* > */ + [61] = DEFINE_SC(0x4a), /* / */ + [62] = DEFINE_SC(0x59), /* */ + [63] = DEFINE_SC(0x7c), /* keypad * */ + [64] = DEFINE_SC(0x11), /* */ + [65] = DEFINE_SC(0x29), /* */ + + [67] = DEFINE_SC(0x05), /* */ + [68] = DEFINE_SC(0x06), /* */ + [69] = DEFINE_SC(0x04), /* */ + [70] = DEFINE_SC(0x0c), /* */ + [71] = DEFINE_SC(0x03), /* */ + [72] = DEFINE_SC(0x0b), /* */ + [73] = DEFINE_SC(0x83), /* */ + [74] = DEFINE_SC(0x0a), /* */ + [75] = DEFINE_SC(0x01), /* */ + [76] = DEFINE_SC(0x09), /* */ + + [79] = DEFINE_SC(0x6c), /* keypad 7 */ + [80] = DEFINE_SC(0x75), /* keypad 8 */ + [81] = DEFINE_SC(0x7d), /* keypad 9 */ + [82] = DEFINE_SC(0x7b), /* keypad - */ + [83] = DEFINE_SC(0x6b), /* keypad 4 */ + [84] = DEFINE_SC(0x73), /* keypad 5 */ + [85] = DEFINE_SC(0x74), /* keypad 6 */ + [86] = DEFINE_SC(0x79), /* keypad + */ + [87] = DEFINE_SC(0x69), /* keypad 1 */ + [88] = DEFINE_SC(0x72), /* keypad 2 */ + [89] = DEFINE_SC(0x7a), /* keypad 3 */ + [90] = DEFINE_SC(0x70), /* keypad 0 */ + [91] = DEFINE_SC(0x71), /* keypad . */ + + [94] = DEFINE_SC(0x61), /* */ + [95] = DEFINE_SC(0x78), /* */ + [96] = DEFINE_SC(0x07), /* */ + + [104] = DEFINE_ESC(0x5a), /* keypad */ + [105] = DEFINE_ESC(0x14), /* */ + [106] = DEFINE_ESC(0x4a), /* keypad / */ + [108] = DEFINE_ESC(0x11), /* */ + [110] = DEFINE_ESC(0x6c), /* */ + [111] = DEFINE_ESC(0x75), /* */ + [112] = DEFINE_ESC(0x7d), /* */ + [113] = DEFINE_ESC(0x6b), /* */ + [114] = DEFINE_ESC(0x74), /* */ + [115] = DEFINE_ESC(0x69), /* */ + [116] = DEFINE_ESC(0x72), /* */ + [117] = DEFINE_ESC(0x7a), /* */ + [118] = DEFINE_ESC(0x70), /* */ + [119] = DEFINE_ESC(0x71), /* */ +}; + +static cairo_surface_t *surface; +static bool done; + +static const struct set2_scancode *to_code(u8 scancode) +{ + return &keymap[scancode]; +} + +static gboolean +kvm_gtk_configure_event(GtkWidget * widget, GdkEventConfigure * event, gpointer data) +{ + struct framebuffer *fb = data; + int stride; + + if (surface) + cairo_surface_destroy(surface); + + stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, fb->width); + + surface = + cairo_image_surface_create_for_data((void *) fb->mem, + CAIRO_FORMAT_RGB24, + fb->width, + fb->height, + stride); + + return TRUE; +} + +static gboolean kvm_gtk_draw(GtkWidget *widget, cairo_t *cr, gpointer data) +{ + cairo_set_source_surface(cr, surface, 0, 0); + + cairo_paint(cr); + + return FALSE; +} + +static void kvm_gtk_destroy(void) +{ + if (surface) + cairo_surface_destroy(surface); + + gtk_main_quit(); +} + +static gboolean kvm_gtk_redraw(GtkWidget * window) +{ + gtk_widget_queue_draw(window); + + return TRUE; +} + +static gboolean +kvm_gtk_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + const struct set2_scancode *sc = to_code(event->hardware_keycode); + + switch (sc->type) { + case SCANCODE_ESCAPED: + kbd_queue(0xe0); + /* fallthrough */ + case SCANCODE_NORMAL: + kbd_queue(sc->code); + break; + case SCANCODE_KEY_PAUSE: + kbd_queue(0xe1); + kbd_queue(0x14); + kbd_queue(0x77); + kbd_queue(0xe1); + kbd_queue(0xf0); + kbd_queue(0x14); + kbd_queue(0x77); + break; + case SCANCODE_KEY_PRNTSCRN: + kbd_queue(0xe0); + kbd_queue(0x12); + kbd_queue(0xe0); + kbd_queue(0x7c); + break; + } + + return TRUE; +} + +static void *kvm_gtk_thread(void *p) +{ + struct framebuffer *fb = p; + GtkWidget *window; + GtkWidget *frame; + GtkWidget *da; + + gtk_init(NULL, NULL); + + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + gtk_window_set_title(GTK_WINDOW(window), "VM"); + + g_signal_connect(window, "destroy", G_CALLBACK(kvm_gtk_destroy), NULL); + + gtk_container_set_border_width(GTK_CONTAINER(window), 8); + + frame = gtk_frame_new(NULL); + + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); + gtk_container_add(GTK_CONTAINER(window), frame); + + da = gtk_drawing_area_new(); + + gtk_widget_set_size_request(da, 100, 100); + + gtk_container_add(GTK_CONTAINER(frame), da); + + g_signal_connect(da, "draw", G_CALLBACK(kvm_gtk_draw), NULL); + g_signal_connect(da, "configure-event", + G_CALLBACK(kvm_gtk_configure_event), fb); + g_signal_connect(G_OBJECT (window), "key_press_event", G_CALLBACK(kvm_gtk_key_press), NULL); + + + gtk_widget_set_events(da, gtk_widget_get_events(da) + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); + + gtk_widget_show_all(window); + + g_timeout_add(1000 / FRAME_RATE, (GSourceFunc) kvm_gtk_redraw, window); + + gtk_main(); + + done = true; + + return NULL; +} + +static int kvm_gtk_start(struct framebuffer *fb) +{ + pthread_t thread; + + if (pthread_create(&thread, NULL, kvm_gtk_thread, fb) != 0) + return -1; + + return 0; +} + +static int kvm_gtk_stop(struct framebuffer *fb) +{ + gtk_main_quit(); + + while (!done) + sleep(0); + + return 0; +} + +static struct fb_target_operations kvm_gtk_ops = { + .start = kvm_gtk_start, + .stop = kvm_gtk_stop, +}; + +int kvm_gtk_init(struct kvm *kvm) +{ + struct framebuffer *fb; + + if (!kvm->cfg.gtk) + return 0; + + fb = vesa__init(kvm); + if (IS_ERR(fb)) { + pr_err("vesa__init() failed with error %ld\n", PTR_ERR(fb)); + return PTR_ERR(fb); + } + + return fb__attach(fb, &kvm_gtk_ops); +} + +int kvm_gtk_exit(struct kvm *kvm) +{ + if (kvm->cfg.gtk) + return kvm_gtk_stop(NULL); + + return 0; +} + +dev_init(kvm_gtk_init); +dev_exit(kvm_gtk_exit); diff --git a/x86/kvm.c b/x86/kvm.c index 2ba1db0..9b46772 100644 --- a/x86/kvm.c +++ b/x86/kvm.c @@ -296,7 +296,7 @@ bool load_bzimage(struct kvm *kvm, int fd_kernel, int fd_initrd, /* vidmode should be either specified or set by default */ - if (kvm->cfg.vnc || kvm->cfg.sdl) { + if (kvm->cfg.vnc || kvm->cfg.sdl || kvm->cfg.gtk) { if (!kvm->cfg.arch.vidmode) vidmode = 0x312; else