diff options
author | kraxel <kraxel> | 2008-09-18 15:51:56 +0000 |
---|---|---|
committer | kraxel <kraxel> | 2008-09-18 15:51:56 +0000 |
commit | a2fa1c886c58b89428d7e9a88080bd9e29f20d51 (patch) | |
tree | a015e98534a140c0d124824e1d999cd524281037 /qemu-gtk.c | |
download | qemu-gtk-a2fa1c886c58b89428d7e9a88080bd9e29f20d51.tar.gz |
Initial revision
Diffstat (limited to 'qemu-gtk.c')
-rw-r--r-- | qemu-gtk.c | 528 |
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); +} |