aboutsummaryrefslogtreecommitdiffstats
path: root/qemu-gtk.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu-gtk.c')
-rw-r--r--qemu-gtk.c528
1 files changed, 528 insertions, 0 deletions
diff --git a/qemu-gtk.c b/qemu-gtk.c
new file mode 100644
index 0000000..164e1c7
--- /dev/null
+++ b/qemu-gtk.c
@@ -0,0 +1,528 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <signal.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <vte/vte.h>
+#include <vncdisplay.h>
+
+#include "qemu-gtk.h"
+#include "tcp.h"
+
+/* ------------------------------------------------------------------ */
+
+static void vte_configure(GtkWidget *vte)
+{
+ vte_terminal_set_scrollback_lines(VTE_TERMINAL(vte), 4096);
+ vte_terminal_set_backspace_binding(VTE_TERMINAL(vte),
+ VTE_ERASE_ASCII_BACKSPACE);
+}
+
+static void tabs_configure(struct qemu_window *win)
+{
+ if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(win->tab)) == 1) {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->tab), 0);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(win->tab), 0);
+ } else {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->tab), 1);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(win->tab), 1);
+ }
+}
+
+static void tabs_add(struct qemu_window *win, GtkWidget *child,
+ const char *text, int pos)
+{
+ GtkWidget *label;
+
+ label = gtk_label_new(text);
+ gtk_notebook_insert_page(GTK_NOTEBOOK(win->tab), child, label, pos);
+ gtk_widget_show(child);
+ if (0 == pos)
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(win->tab), 0);
+ tabs_configure(win);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void menu_cb_scale_display(GtkToggleAction *action, gpointer userdata)
+{
+ struct qemu_window *win = userdata;
+ gboolean active;
+
+ active = gtk_toggle_action_get_active(action);
+ vnc_display_set_scaling(VNC_DISPLAY(win->vnc), active);
+}
+
+static void menu_cb_close(GtkAction *action, gpointer userdata)
+{
+ struct qemu_window *win = userdata;
+ gtk_widget_destroy(win->toplevel);
+}
+
+static void menu_cb_monitor_stop(GtkAction *action, gpointer userdata)
+{
+ struct qemu_window *win = userdata;
+ monitor_append(win, "stop");
+}
+
+static void menu_cb_monitor_cont(GtkAction *action, gpointer userdata)
+{
+ struct qemu_window *win = userdata;
+ monitor_append(win, "cont");
+}
+
+static void menu_cb_run_gdb(GtkAction *action, gpointer userdata)
+{
+ struct qemu_window *win = userdata;
+ char *argv[] = { "gdb", NULL };
+ char cmd[256];
+ int len;
+
+ if (win->gdb_vte)
+ return;
+ win->gdb_vte = vte_terminal_new();
+ vte_configure(win->gdb_vte);
+ tabs_add(win, win->gdb_vte, "gdb", -1);
+
+ monitor_append(win, "gdbserver 1234");
+ len = snprintf(cmd, sizeof(cmd), "target remote %s:1234\n", win->monitor.hostname);
+
+ win->gdb_pid = vte_terminal_fork_command(VTE_TERMINAL(win->gdb_vte),
+ argv[0], argv, NULL, NULL,
+ FALSE, FALSE, FALSE);
+ vte_terminal_feed_child(VTE_TERMINAL(win->gdb_vte), cmd, len);
+}
+
+void update_status(struct qemu_window *win)
+{
+ char msg[256];
+ int len = 0;
+
+ if (win->vnc_grab) {
+ len = snprintf(msg, sizeof(msg), "Press Ctrl-Alt to release input grab.");
+ goto out;
+ }
+
+ if (win->vnc_state == VNC_INITIALIZED) {
+ len += snprintf(msg+len, sizeof(msg)-len, "VNC display \"%s\", %dx%d",
+ win->vnc_display, win->vnc_width, win->vnc_height);
+ } else {
+ len += snprintf(msg+len, sizeof(msg)-len, "No VNC");
+ }
+
+ if (win->version && strlen(win->version))
+ len += snprintf(msg+len, sizeof(msg)-len, ", qemu %s", win->version);
+ if (win->name && strlen(win->name))
+ len += snprintf(msg+len, sizeof(msg)-len, ", name \"%s\"", win->name);
+
+ len += snprintf(msg+len, sizeof(msg)-len, ".");
+
+out:
+ gtk_label_set_text(GTK_LABEL(win->status), msg);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void vnc_connected(GtkWidget *vncdisplay, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_state = VNC_CONNECTED;
+ update_status(win);
+}
+
+static void vnc_initialized(GtkWidget *vncdisplay, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_state = VNC_INITIALIZED;
+ update_status(win);
+}
+
+static void vnc_disconnected(GtkWidget *vncdisplay, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_state = VNC_DISCONNECTED;
+ update_status(win);
+}
+
+static void vnc_grab(GtkWidget *vncdisplay, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_grab = 1;
+ update_status(win);
+}
+
+static void vnc_ungrab(GtkWidget *vncdisplay, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_grab = 0;
+ update_status(win);
+}
+
+static void vnc_desktop_resize(GtkWidget *vncdisplay, int x, int y, void *data)
+{
+ struct qemu_window *win = data;
+ win->vnc_width = x;
+ win->vnc_height = y;
+ update_status(win);
+}
+
+static void vnc_credential(GtkWidget *vncdisplay,
+ GValueArray *credList,
+ void *data)
+{
+// struct qemu_window *win = data;
+ fprintf(stderr, "%s: FIXME\n", __FUNCTION__);
+}
+
+void vnc_connect(struct qemu_window *win)
+{
+ int nr;
+
+ if (2 == sscanf(win->vnc_display, "%127[^:]:%d", win->vnc_hostname, &nr)) {
+ sprintf(win->vnc_tcpport, "%d", nr + 5900);
+ } else if (1 == sscanf(win->vnc_display, ":%d", &nr)) {
+ sprintf(win->vnc_hostname, "%s", win->monitor.hostname);
+ sprintf(win->vnc_tcpport, "%d", nr + 5900);
+ } else {
+ fprintf(stderr, "parse error: \"%s\"\n", win->vnc_display);
+ return;
+ }
+
+ qemu_vnc_tab(win);
+ vnc_display_open_host(VNC_DISPLAY(win->vnc),
+ win->vnc_hostname, win->vnc_tcpport);
+}
+
+/* ------------------------------------------------------------------ */
+
+int conn_init(struct qemu_conn *conn, char *name, char *dest)
+{
+ struct addrinfo ask;
+ char path[256];
+ char serv[33];
+
+ memset(&ask,0,sizeof(ask));
+ ask.ai_socktype = SOCK_STREAM;
+ ask.ai_family = PF_UNSPEC;
+ tcp_verbose = 1;
+
+ snprintf(conn->name, sizeof(conn->name), "%s", name);
+ strcpy(conn->hostname, "localhost");
+ if (2 == sscanf(dest, "tcp:%64[^:]:%32s", conn->hostname, serv)) {
+ conn->handle = tcp_connect(&ask, NULL, NULL, conn->hostname, serv);
+ } else if (1 == sscanf(dest, "tcp:%32s", serv)) {
+ conn->handle = tcp_connect(&ask, NULL, NULL, conn->hostname, serv);
+ } else if (1 == sscanf(dest, "unix:%255s", path)) {
+ conn->handle = unix_connect(path);
+ } else if (1 == sscanf(dest, "pipe:%255s", path)) {
+ conn->handle = pipe_connect(path);
+ } else {
+ fprintf(stderr, "can't parse \"%s\"\n", dest);
+ conn->handle = -1;
+ }
+ return conn->handle;
+}
+
+static void conn_user_input(VteTerminal *vte, gchar *buf, guint len,
+ gpointer data)
+{
+ struct qemu_conn *conn = data;
+
+ if (conn->handle != -1)
+ write(conn->handle, buf, len);
+}
+
+static gboolean conn_watch(GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct qemu_conn *conn = data;
+ char buf[256];
+ int rc;
+
+ rc = read(conn->handle, buf, sizeof(buf));
+ switch(rc) {
+ case -1:
+ if (EINTR == errno)
+ break;
+ perror("console: read");
+ goto close;
+ case 0:
+ fprintf(stderr, "console: EOF\n");
+ goto close;
+ default:
+ if (conn->vte)
+ vte_terminal_feed(VTE_TERMINAL(conn->vte), buf, rc);
+ break;
+ }
+ return TRUE;
+
+close:
+ if (conn->vte)
+ vte_terminal_feed(VTE_TERMINAL(conn->vte), "\r\n=== CLOSED ===", 16);
+ close(conn->handle);
+ conn->handle = -1;
+ return FALSE;
+}
+
+static int conn_connect(struct qemu_conn *conn, char *name, char *dest)
+{
+ int fd;
+
+ fd = conn_init(conn, name, dest);
+ if (-1 == fd)
+ return -1;
+
+ conn->ch = g_io_channel_unix_new(fd);
+ conn->id = g_io_add_watch(conn->ch, G_IO_IN, conn_watch, conn);
+ return fd;
+}
+
+/* ------------------------------------------------------------------ */
+
+static const GtkActionEntry entries[] = {
+ {
+ .name = "FileMenu",
+ .label = "_File",
+ },{
+ .name = "ViewMenu",
+ .label = "_View",
+ },{
+ .name = "ActionMenu",
+ .label = "_Actions",
+ },{
+ .name = "Close",
+ .stock_id = GTK_STOCK_CLOSE,
+ .label = "_Close",
+ .accelerator = "<control>Q",
+ .callback = G_CALLBACK(menu_cb_close),
+ },{
+ .name = "MonitorStop",
+ .label = "_Pause VM",
+ .callback = G_CALLBACK(menu_cb_monitor_stop),
+ },{
+ .name = "MonitorCont",
+ .label = "_Unpause VM",
+ .callback = G_CALLBACK(menu_cb_monitor_cont),
+ },{
+ .name = "RunGdb",
+ .label = "_Debug with gdb",
+ .callback = G_CALLBACK(menu_cb_run_gdb),
+ },
+};
+
+static const GtkToggleActionEntry tentries[] = {
+ {
+ .name = "ScaleDisplay",
+ .label = "_Scale Display",
+ .callback = G_CALLBACK(menu_cb_scale_display),
+ }
+};
+
+static char ui_xml[] =
+"<ui>"
+" <menubar name='MainMenu'>"
+" <menu action='FileMenu'>"
+" <menuitem action='Close'/>"
+" </menu>"
+" <menu action='ViewMenu'>"
+" <menuitem action='ScaleDisplay'/>"
+" </menu>"
+" <menu action='ActionMenu'>"
+" <menuitem action='MonitorStop'/>"
+" <menuitem action='MonitorCont'/>"
+" <menuitem action='RunGdb'/>"
+" </menu>"
+" </menubar>"
+#ifdef WITH_TOOLBAR
+" <toolbar action='ToolBar'>"
+" <toolitem action='Close'/>"
+" </toolbar>"
+#endif
+"</ui>";
+
+static void destroy(GtkWidget *widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+static struct qemu_window *qemu_create_window(void)
+{
+ struct qemu_window *win;
+ GtkWidget *vbox, *menubar, *toolbar, *frame;
+ GtkAccelGroup *accel;
+ GtkActionGroup *ag;
+ GtkUIManager *ui;
+ GError *err;
+
+ win = malloc(sizeof(*win));
+ if (NULL == win)
+ return NULL;
+ memset(win,0,sizeof(*win));
+
+ win->toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(win->toplevel), "qemu-gtk");
+ gtk_window_set_default_size(GTK_WINDOW(win->toplevel), 320, 280);
+ g_signal_connect(G_OBJECT(win->toplevel), "destroy",
+ G_CALLBACK(destroy), win);
+
+ /* menu + toolbar */
+ ui = gtk_ui_manager_new();
+ ag = gtk_action_group_new("MenuActions");
+ gtk_action_group_add_actions(ag, entries, G_N_ELEMENTS(entries), win);
+ gtk_action_group_add_toggle_actions(ag, tentries,
+ G_N_ELEMENTS(tentries), win);
+ gtk_ui_manager_insert_action_group(ui, ag, 0);
+ accel = gtk_ui_manager_get_accel_group(ui);
+ gtk_window_add_accel_group(GTK_WINDOW(win->toplevel), accel);
+
+ err = NULL;
+ if (!gtk_ui_manager_add_ui_from_string(ui, ui_xml, -1, &err)) {
+ g_message("building menus failed: %s", err->message);
+ g_error_free(err);
+ exit(1);
+ }
+
+ /* main area */
+ win->tab = gtk_notebook_new();
+
+
+ /* status line */
+ win->status = gtk_label_new("status line");
+ gtk_misc_set_alignment(GTK_MISC(win->status), 0, 0.5);
+ gtk_misc_set_padding(GTK_MISC(win->status), 3, 1);
+ update_status(win);
+
+ /* Make a vbox and put stuff in */
+ vbox = gtk_vbox_new(FALSE, 1);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 1);
+ gtk_container_add(GTK_CONTAINER(win->toplevel), vbox);
+ menubar = gtk_ui_manager_get_widget(ui, "/MainMenu");
+ gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+ toolbar = gtk_ui_manager_get_widget(ui, "/ToolBar");
+ if (toolbar)
+ gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), win->tab, TRUE, TRUE, 0);
+
+ frame = gtk_frame_new(NULL);
+ gtk_box_pack_end(GTK_BOX(vbox), frame, FALSE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(frame), win->status);
+
+ return win;
+}
+
+void qemu_vnc_tab(struct qemu_window *win)
+{
+ if (win->vnc)
+ return;
+ win->vnc = vnc_display_new();
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-connected",
+ GTK_SIGNAL_FUNC(vnc_connected), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-initialized",
+ GTK_SIGNAL_FUNC(vnc_initialized), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-disconnected",
+ GTK_SIGNAL_FUNC(vnc_disconnected), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-pointer-grab",
+ GTK_SIGNAL_FUNC(vnc_grab), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-pointer-ungrab",
+ GTK_SIGNAL_FUNC(vnc_ungrab), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-auth-credential",
+ GTK_SIGNAL_FUNC(vnc_credential), win);
+ gtk_signal_connect(GTK_OBJECT(win->vnc), "vnc-desktop-resize",
+ GTK_SIGNAL_FUNC(vnc_desktop_resize), win);
+ tabs_add(win, win->vnc, "vnc display", 0);
+}
+
+void qemu_conn_tab(struct qemu_window *win, struct qemu_conn *conn, int pos)
+{
+ if (conn->vte)
+ return;
+ conn->vte = vte_terminal_new();
+ vte_configure(conn->vte);
+ g_signal_connect(conn->vte, "commit",
+ G_CALLBACK(conn_user_input), conn);
+ tabs_add(win, conn->vte, conn->name, pos);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+ fprintf(fp,
+ "This is a simple qemu gui\n"
+ "\n"
+ "usage: qemu-gtk [ options ] monitor\n"
+ "options:\n"
+ " -h Print this text.\n"
+ " -d Raise debug level.\n"
+ " -m Enable monitor logging and access.\n"
+ " -c <dev> Show serial console.\n"
+ "\n"
+ "-- \n"
+ "(c) 2008 Gerd Hoffmann <kraxel@redhat.com>\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct qemu_window *win;
+ char *console_tab = NULL;
+ int monitor_tab = 0;
+ int debug = 0;
+ int c;
+
+ gtk_init(&argc, &argv);
+ for (;;) {
+ if (-1 == (c = getopt(argc, argv, "hdmc:")))
+ break;
+ switch (c) {
+ case 'd':
+ debug++;
+ break;
+ case 'm':
+ monitor_tab++;
+ break;
+ case 'c':
+ console_tab = optarg;
+ break;
+ case 'h':
+ usage(stdout);
+ exit(0);
+ default:
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ if (optind == argc) {
+ usage(stderr);
+ exit(1);
+ }
+
+ /* main window */
+ win = qemu_create_window();
+ if (-1 == monitor_connect(win, argv[optind])) {
+ exit(1);
+ }
+
+ /* tabs */
+ if (monitor_tab)
+ qemu_conn_tab(win, &win->monitor, -1);
+ if (console_tab) {
+ if (-1 != conn_connect(&win->console, "console", console_tab))
+ qemu_conn_tab(win, &win->console, 0);
+ }
+
+ /* main loop */
+ gtk_widget_show_all(win->toplevel);
+ gtk_main();
+ if (win->gdb_pid)
+ kill(win->gdb_pid, SIGTERM);
+ exit(0);
+}