#include #include #include #include #include #include #include #include #include #include #include #include #include #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 = "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[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " #ifdef WITH_TOOLBAR " " " " " " #endif ""; 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 Show serial console.\n" "\n" "-- \n" "(c) 2008 Gerd Hoffmann \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); }