From a2fa1c886c58b89428d7e9a88080bd9e29f20d51 Mon Sep 17 00:00:00 2001 From: kraxel Date: Thu, 18 Sep 2008 15:51:56 +0000 Subject: Initial revision --- GNUmakefile | 65 +++++++ Make.config | 4 + README | 36 ++++ VERSION | 1 + mk/Autoconf.mk | 167 ++++++++++++++++++ mk/Compile.mk | 102 +++++++++++ mk/Maintainer.mk | 28 +++ mk/Variables.mk | 55 ++++++ monitor.c | 163 +++++++++++++++++ qemu-gtk | Bin 0 -> 80074 bytes qemu-gtk.c | 528 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ qemu-gtk.h | 70 ++++++++ tcp.c | 197 +++++++++++++++++++++ tcp.h | 15 ++ 14 files changed, 1431 insertions(+) create mode 100644 GNUmakefile create mode 100644 Make.config create mode 100644 README create mode 100644 VERSION create mode 100644 mk/Autoconf.mk create mode 100644 mk/Compile.mk create mode 100644 mk/Maintainer.mk create mode 100644 mk/Variables.mk create mode 100644 monitor.c create mode 100755 qemu-gtk create mode 100644 qemu-gtk.c create mode 100644 qemu-gtk.h create mode 100644 tcp.c create mode 100644 tcp.h diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..87aec2f --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,65 @@ +# config +-include Make.config +include mk/Variables.mk + +# add our flags + libs +CFLAGS += -DVERSION='"$(VERSION)"' -DLIB='"$(LIB)"' + +# build +TARGETS := qemu-gtk + +# default target +all: build + +################################################################# +# poor man's autoconf ;-) + +include mk/Autoconf.mk + +define make-config +LIB := $(LIB) +HAVE_GTK := $(call ac_pkg_config,gtk+-x11-2.0) +HAVE_GTK_VNC := $(call ac_pkg_config,gtk-vnc-1.0) +HAVE_VTE := $(call ac_pkg_config,vte) +endef + +pkglst := gtk+-x11-2.0 gtk-vnc-1.0 vte + +CFLAGS += -Wno-strict-prototypes +CFLAGS += $(shell test "$(pkglst)" != "" && pkg-config --cflags $(pkglst)) +LDLIBS += $(shell test "$(pkglst)" != "" && pkg-config --libs $(pkglst)) + +# desktop files +DESKTOP := $(wildcard $(patsubst %,%.desktop,$(TARGETS))) + + +######################################################################## +# rules + +ifeq ($(HAVE_GTK)$(HAVE_GTK_VNC)$(HAVE_VTE),yesyesyes) +build: $(TARGETS) +else +build: + @echo "build dependencies are missing" + @false +endif + +install: build + $(INSTALL_BINARY) $(TARGETS) $(bindir) + $(INSTALL_DATA) $(DESKTOP) $(appdir) + +clean: + -rm -f *.o *~ $(depfiles) + +realclean distclean: clean + -rm -f Make.config + -rm -f $(TARGETS) *~ *.bak + +############################################# + +qemu-gtk: qemu-gtk.o monitor.o tcp.o + +include mk/Compile.mk +include mk/Maintainer.mk +-include $(depfiles) + diff --git a/Make.config b/Make.config new file mode 100644 index 0000000..c183710 --- /dev/null +++ b/Make.config @@ -0,0 +1,4 @@ +LIB := lib64 +HAVE_GTK := yes +HAVE_GTK_VNC := yes +HAVE_VTE := yes diff --git a/README b/README new file mode 100644 index 0000000..2f3b266 --- /dev/null +++ b/README @@ -0,0 +1,36 @@ + +about qemu-gtk +============== + +This is a simple gtk-based gui for qemu. It is designed to run +independant from the qemu process, so you can stop and restart the gui +without disturbing your virtual machines. + +qemu-gtk doesn't (yet?) support starting and stopping virtual +machines. It can only connect to a already running qemu process. + + +using qemu-gtk +-------------- + +qemu-gtk connects to the qemu monitor using tcp or unix sockets and to +the built-in vnc server. Thus you must enable the monitor and vnc +when starting qemu. The monitor should be in non-blocking server mode. + +i.e. you'll start qemu like this: + + "qemu -monitor unix:/tmp/monitor,server,nowait -vnc :1 " + +then the gui like this: + + "qemu-gtk unix:/tmp/monitor" + +qemu-gtk will figure automatically where the vnc display is, using the +monitor. + + +Have fun, + Gerd + +-- +Gerd Hoffmann diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..49d5957 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1 diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk new file mode 100644 index 0000000..27eeb32 --- /dev/null +++ b/mk/Autoconf.mk @@ -0,0 +1,167 @@ +# +# simple autoconf system for GNU make +# +# (c) 2002-2006 Gerd Hoffmann +# +# credits for creating this one go to the autotools people because +# they managed it to annoy lots of developers and users (including +# me) with version incompatibilities. +# +# This file is public domain. No warranty. If it breaks you keep +# both pieces. +# +######################################################################## + +# verbose yes/no +verbose ?= no + +# some stuff used by the tests +ifneq ($(verbose),no) + # verbose (for debug) + ac_init = echo "checking $(1) ... " >&2; rc=no + ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes + ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)` + ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}" +else + # normal + ac_init = echo -n "checking $(1) ... " >&2; rc=no + ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes + ac_s_cmd = rc=`$(1) 2>/dev/null` + ac_fini = echo "$${rc}" >&2; echo "$${rc}" +endif + +# some helpers to build cflags and related variables +ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1)) +ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib))) +ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc))) +ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2))) +ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2))) + + +######################################################################## +# the tests ... + +# get uname +ac_uname = $(shell \ + $(call ac_init,for system);\ + $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\ + $(call ac_fini)) + +ac_uname_arch = $(shell \ + $(call ac_init,for arch);\ + $(call ac_s_cmd,uname -m | tr 'A-Z' 'a-z');\ + $(call ac_fini)) + +# check for some header file +# args: header file +ac_header = $(shell \ + $(call ac_init,for $(1));\ + $(call ac_b_cmd,echo '\#include <$(1)>' |\ + $(CC) $(CFLAGS) -E -);\ + $(call ac_fini)) + +# check for some function +# args: function [, additional libs ] +ac_func = $(shell \ + $(call ac_init,for $(1));\ + echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \ + > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \ + __actest __actest.c $(2));\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check for some library +# args: function, library [, additional libs ] +ac_lib = $(shell \ + $(call ac_init,for $(1) in $(2));\ + echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \ + > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \ + __actest __actest.c -l$(2) $(3));\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check if some compiler flag works +# args: compiler flag +ac_cflag = $(shell \ + $(call ac_init,if $(CC) supports $(1));\ + echo 'int main() {return 0;}' > __actest.c;\ + $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \ + __actest __actest.c);\ + rm -f __actest __actest.c;\ + $(call ac_fini)) + +# check for some binary +# args: binary name +ac_binary = $(shell \ + $(call ac_init,for $(1));\ + $(call ac_s_cmd,which $(1));\ + bin="$$rc";rc="no";\ + $(call ac_b_cmd,test -x "$$$$bin");\ + $(call ac_fini)) + +# check if lib64 is used +#ac_lib64 = $(shell \ +# $(call ac_init,for libdir name);\ +# $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\ +# echo "lib64" || echo "lib");\ +# $(call ac_fini)) +ac_lib64 = $(shell \ + $(call ac_init,for libdir name);\ + $(call ac_s_cmd,/sbin/ldconfig -p | grep -q lib64 &&\ + echo "lib64" || echo "lib");\ + $(call ac_fini)) + +# check for x11 ressource dir prefix +ac_resdir = $(shell \ + $(call ac_init,for X11 app-defaults prefix);\ + $(call ac_s_cmd, for dir in \ + /etc/X11/app-defaults \ + /usr/X11R6/lib/X11/app-defaults \ + /usr/share/X11/app-defaults \ + /usr/lib/X11/app-defaults \ + ; do test -d "$$dir" || continue;\ + dirname "$$dir"; break; done);\ + $(call ac_fini)) + +# check if package is installed, via pkg-config +# args: pkg name +ac_pkg_config = $(shell \ + $(call ac_init,for $(1) (using pkg-config));\ + $(call ac_b_cmd, pkg-config $(1));\ + $(call ac_fini)) + +# grep some file +# args: regex, file +ac_grep = $(shell \ + $(call ac_init,for $(1) in $(2));\ + $(call ac_b_cmd, grep -q $(1) $(2));\ + $(call ac_fini)) + + +######################################################################## +# build Make.config + +define newline + + +endef +make-config-q = $(subst $(newline),\n,$(make-config)) + +ifeq ($(filter config,$(MAKECMDGOALS)),config) +.PHONY: Make.config + LIB := $(call ac_lib64) +else + LIB ?= $(call ac_lib64) + LIB := $(LIB) +endif +.PHONY: config +config: Make.config + @true + +Make.config: $(srcdir)/GNUmakefile + @echo -e "$(make-config-q)" > $@ + @echo + @echo "Make.config written, edit if needed" + @echo diff --git a/mk/Compile.mk b/mk/Compile.mk new file mode 100644 index 0000000..4ab59f4 --- /dev/null +++ b/mk/Compile.mk @@ -0,0 +1,102 @@ +# +# some rules to compile stuff ... +# +# (c) 2002-2006 Gerd Hoffmann +# +# main features: +# * autodependencies via "cpp -MD" +# * fancy, non-verbose output +# +# This file is public domain. No warranty. If it breaks you keep +# both pieces. +# +######################################################################## + +# verbose yes/no +verbose ?= no + +# dependency files +tmpdep = mk/$(subst /,_,$*).tmp +depfile = mk/$(subst /,_,$*).dep +depfiles = mk/*.dep + +compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +compile_c_pic = $(CC) $(CFLAGS) -fPIC -Wp,-MD,$(tmpdep) -c -o $@ $< +compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $< +fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep) +cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile)) + +link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) +link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS) +ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@ + +moc_h = $(MOC) $< -o $@ +msgfmt_po = msgfmt -o $@ $< + +# non-verbose output +ifeq ($(verbose),no) + echo_compile_c = echo " CC " $@ + echo_compile_c_pic = echo " CC " $@ + echo_compile_cc = echo " CXX " $@ + echo_link_app = echo " LD " $@ + echo_link_so = echo " LD " $@ + echo_ar_lib = echo " AR " $@ + echo_moc_h = echo " MOC " $@ + echo_msgfmt_po = echo " MSGFMT " $@ +else + echo_compile_c = echo $(compile_c) + echo_compile_c = echo $(compile_c_pic) + echo_compile_cc = echo $(compile_cc) + echo_link_app = echo $(link_app) + echo_link_so = echo $(link_so) + echo_ar_lib = echo $(ar_lib) + echo_moc_h = echo $(moc_h) + echo_msgfmt_po = echo $(msgfmt_po) +endif + +%.o: %.c + @$(cc_makedirs) + @$(echo_compile_c) + @$(compile_c) + @$(fixup_deps) + +%.opic: %.c + @$(cc_makedirs) + @$(echo_compile_c_pic) + @$(compile_c_pic) + @$(fixup_deps) + +%.o: %.cc + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + +%.o: %.cpp + @$(cc_makedirs) + @$(echo_compile_cc) + @$(compile_cc) + @$(fixup_deps) + + +%: %.o + @$(echo_link_app) + @$(link_app) + +%.so: %.o + @$(echo_link_so) + @$(link_so) + +%.a: %.o + @$(echo_ar_lib) + @$(ar_lib) + + +%.moc : %.h + @$(echo_moc_h) + @$(moc_h) + +%.mo : %.po + @$(echo_msgfmt_po) + @$(msgfmt_po) + diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk new file mode 100644 index 0000000..23446ae --- /dev/null +++ b/mk/Maintainer.mk @@ -0,0 +1,28 @@ +# just some maintainer stuff for me ... +######################################################################## + +make-sync-dir = $(HOME)/projects/gnu-makefiles + +.PHONY: sync +sync:: distclean + test -d $(make-sync-dir) + rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk + cp -v $(make-sync-dir)/INSTALL $(srcdir)/. + cp -v $(make-sync-dir)/*.mk $(srcdir)/mk + chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk + + +repository = $(shell cat CVS/Repository) +release-dir ?= $(HOME)/projects/Releases +release-pub ?= goldbach@me.in-berlin.de:dl.bytesex.org/releases/$(repository) +tarball = $(release-dir)/$(repository)-$(VERSION).tar.gz + +.PHONY: release +release: + cvs tag $(RELTAG) + cvs export -r $(RELTAG) -d "$(repository)-$(VERSION)" "$(repository)" + find "$(repository)-$(VERSION)" -name .cvsignore -exec rm -fv "{}" ";" + tar -c -z -f "$(tarball)" "$(repository)-$(VERSION)" + rm -rf "$(repository)-$(VERSION)" + scp $(tarball) $(release-pub) + diff --git a/mk/Variables.mk b/mk/Variables.mk new file mode 100644 index 0000000..99f787c --- /dev/null +++ b/mk/Variables.mk @@ -0,0 +1,55 @@ +# common variables ... +######################################################################## + +# directories +DESTDIR = +srcdir ?= . +prefix ?= /usr/local +bindir = $(DESTDIR)$(prefix)/bin +sbindir = $(DESTDIR)$(prefix)/sbin +libdir = $(DESTDIR)$(prefix)/$(LIB) +shrdir = $(DESTDIR)$(prefix)/share +mandir = $(shrdir)/man +locdir = $(shrdir)/locale +appdir = $(shrdir)/applications + +# package + version +empty := +space := $(empty) $(empty) +ifneq ($(wildcard $(srcdir)/VERSION),) + VERSION := $(shell cat $(srcdir)/VERSION) +else + VERSION := 42 +endif +RELTAG := v$(subst .,_,$(VERSION)) + +# programs +CC ?= gcc +CXX ?= g++ +MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc) + +STRIP ?= -s +INSTALL ?= install +INSTALL_BINARY := $(INSTALL) $(STRIP) +INSTALL_SCRIPT := $(INSTALL) +INSTALL_DATA := $(INSTALL) -m 644 +INSTALL_DIR := $(INSTALL) -d + +# cflags +CFLAGS ?= -g -O2 +CXXFLAGS ?= $(CFLAGS) +CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \ + -Wpointer-arith -Wunused +CXXFLAGS += -Wall -Wpointer-arith -Wunused + +# add /usr/local to the search path if something is in there ... +ifneq ($(wildcard /usr/local/include/*.h),) + CFLAGS += -I/usr/local/include + LDFLAGS += -L/usr/local/$(LIB) +endif + +# fixup include path for $(srcdir) != "." +ifneq ($(srcdir),.) + CFLAGS += -I. -I$(srcdir) +endif + diff --git a/monitor.c b/monitor.c new file mode 100644 index 0000000..f8bd9ff --- /dev/null +++ b/monitor.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "qemu-gtk.h" +#include "tcp.h" + +/* ----------------------------------------------------------------- */ + +static int monitor_parse(struct qemu_window *win, char *buf, int len) +{ + char *reply, *prompt, *cmd; + int off; + + prompt = strstr(buf, "(qemu) "); + if (NULL == prompt) + return 0; + off = prompt - buf; + if (prompt > buf+1 && prompt[-2] == '\r' && prompt[-1] == '\n') + prompt -= 2; + *prompt = 0; + + if (!win->mrun) + goto out; + cmd = win->mrun->cmd; + reply = strstr(buf, "\r\n"); + if (!reply) + goto out; + reply += 2; + +#if 0 + fprintf(stderr, "\"%s\" -> [%s]\n--\n", cmd, reply); +#endif + if (0 == strcmp(cmd, "info version")) { + snprintf(win->version, sizeof(win->version), "%s", reply); + update_status(win); + } else if (0 == strcmp(cmd, "info name")) { + snprintf(win->name, sizeof(win->name), "%s", reply); + update_status(win); + } else if (0 == strcmp(cmd, "info vnc")) { + if (1 == sscanf(reply, "VNC server active on: %127[^\r\n]", + win->vnc_display) && + strstr(reply, "No client connected")) + vnc_connect(win); + } + +out: + return off + 7; +} + +/* ----------------------------------------------------------------- */ + +static void monitor_next(struct qemu_window *win) +{ + char buf[256]; + int len; + + if (win->mrun) + free(win->mrun); + win->mrun = win->mqueue; + if (!win->mrun) + return; + win->mqueue = win->mrun->next; + len = snprintf(buf, sizeof(buf), "%s\n", win->mrun->cmd); + write(win->monitor.handle, buf, len); +} + +void monitor_append(struct qemu_window *win, char *cmd) +{ + struct qemu_mq *mq; + + if (win->mqueue) { + for (mq = win->mqueue; mq->next; mq = mq->next) + ; + mq->next = malloc(sizeof(*mq)); + mq = mq->next; + } else { + win->mqueue = malloc(sizeof(*mq)); + mq = win->mqueue; + } + mq->next = NULL; + snprintf(mq->cmd, sizeof(mq->cmd), "%s", cmd); + + if (0 == win->mused) + monitor_next(win); +} + +static gboolean monitor_watch(GIOChannel *source, GIOCondition condition, + gpointer userdata) +{ + struct qemu_window *win = userdata; + int rc; + + if (win->mused == win->msize) { + if (!win->msize) + win->msize = 4; + win->msize *= 2; + win->mbuf = realloc(win->mbuf, win->msize +1); + } + + rc = read(win->monitor.handle, win->mbuf + win->mused, win->msize - win->mused); + switch(rc) { + case -1: + if (EINTR == errno) + break; + perror("monitor: read"); + goto close; + case 0: + fprintf(stderr, "monitor: EOF\n"); + goto close; + default: + if (win->monitor.vte) + vte_terminal_feed(VTE_TERMINAL(win->monitor.vte), win->mbuf + win->mused, rc); + win->mused += rc; + win->mbuf[win->mused] = 0; + rc = monitor_parse(win, win->mbuf, win->mused); + if (rc) { + if (rc < win->mused) + memmove(win->mbuf, win->mbuf + rc, win->mused - rc); + win->mused -= rc; + } + break; + } + + if (0 == win->mused) + monitor_next(win); + return TRUE; + +close: + if (win->monitor.vte) + vte_terminal_feed(VTE_TERMINAL(win->monitor.vte), "\r\n=== CLOSED ===", 16); + close(win->monitor.handle); + win->monitor.handle = -1; +// gtk_widget_destroy(win->toplevel); + return FALSE; +} + +/* ----------------------------------------------------------------- */ + +int monitor_connect(struct qemu_window *win, char *dest) +{ + int fd; + + fd = conn_init(&win->monitor, "monitor", dest); + if (-1 == fd) + return -1; + + win->monitor.ch = g_io_channel_unix_new(fd); + win->monitor.id = g_io_add_watch(win->monitor.ch, G_IO_IN, monitor_watch, win); + + monitor_append(win, "info version"); + monitor_append(win, "info name"); + monitor_append(win, "info vnc"); + return fd; +} diff --git a/qemu-gtk b/qemu-gtk new file mode 100755 index 0000000..cf40928 Binary files /dev/null and b/qemu-gtk differ 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 +#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); +} diff --git a/qemu-gtk.h b/qemu-gtk.h new file mode 100644 index 0000000..1db432b --- /dev/null +++ b/qemu-gtk.h @@ -0,0 +1,70 @@ +struct qemu_window; + +struct qemu_mq { + struct qemu_mq *next; + char cmd[64]; +}; + +enum vnc_state { + VNC_NONE = 0, + VNC_CONNECTED, + VNC_INITIALIZED, + VNC_DISCONNECTED, +}; + +struct qemu_conn { + int handle; + GIOChannel *ch; + guint id; + GtkWidget *vte; + struct qemu_window *win; + char name[32]; + char hostname[128]; +}; + +struct qemu_window { + /* widgets */ + GtkWidget *toplevel, *status; + GtkWidget *tab, *vnc; + + /* vnc (gfx) */ + char vnc_display[128]; + char vnc_hostname[128]; + char vnc_tcpport[16]; + enum vnc_state vnc_state; + int vnc_grab; + int vnc_width; + int vnc_height; + + /* console (text) */ + struct qemu_conn console; + + /* monitor */ + struct qemu_conn monitor; + char *mbuf; + int msize; + int mused; + struct qemu_mq *mqueue; + struct qemu_mq *mrun; + + /* gdb */ + GtkWidget *gdb_vte; + pid_t gdb_pid; + + /* vm info */ + char version[32]; + char name[128]; +}; + +/* qemu-gtk.c */ +void update_status(struct qemu_window *win); +void vnc_connect(struct qemu_window *win); +int conn_init(struct qemu_conn *conn, char *name, char *dest); +void qemu_vnc_tab(struct qemu_window *win); +void qemu_conn_tab(struct qemu_window *win, struct qemu_conn *conn, int pos); + +/* monitor.c */ +int monitor_connect(struct qemu_window *win, char *dest); +void monitor_append(struct qemu_window *win, char *cmd); + + diff --git a/tcp.c b/tcp.c new file mode 100644 index 0000000..60a824f --- /dev/null +++ b/tcp.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "tcp.h" + +int tcp_verbose; + +/* ------------------------------------------------------------------ */ + +static char *strfamily(int family) +{ + switch (family) { + case PF_INET6: return "ipv6"; + case PF_INET: return "ipv4"; + case PF_UNIX: return "unix"; + } + return "????"; +} + +int tcp_connect(struct addrinfo *ai, + char *addr, char *port, + char *host, char *serv) +{ + struct addrinfo *res,*e; + struct addrinfo *lres, ask; + char uaddr[INET6_ADDRSTRLEN+1]; + char uport[33]; + char uhost[INET6_ADDRSTRLEN+1]; + char userv[33]; + int sock,rc,opt=1; + + /* lookup peer */ + ai->ai_flags = AI_CANONNAME; + if (0 != (rc = getaddrinfo(host, serv, ai, &res))) { + if (tcp_verbose) + fprintf(stderr,"getaddrinfo (peer): %s\n", gai_strerror(rc)); + return -1; + } + for (e = res; e != NULL; e = e->ai_next) { + if (0 != getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen, + uhost,INET6_ADDRSTRLEN,userv,32, + NI_NUMERICHOST | NI_NUMERICSERV)) { + if (tcp_verbose) + fprintf(stderr,"getnameinfo (peer): oops\n"); + continue; + } + if (-1 == (sock = socket(e->ai_family, e->ai_socktype, + e->ai_protocol))) { + if (tcp_verbose) + fprintf(stderr,"socket (%s): %s\n", + strfamily(e->ai_family),strerror(errno)); + continue; + } + setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); + if (NULL != addr || NULL != port) { + /* bind local port */ + memset(&ask,0,sizeof(ask)); + ask.ai_flags = AI_PASSIVE; + ask.ai_family = e->ai_family; + ask.ai_socktype = e->ai_socktype; + if (0 != (rc = getaddrinfo(addr, port, &ask, &lres))) { + if (tcp_verbose) + fprintf(stderr,"getaddrinfo (local): %s\n", + gai_strerror(rc)); + continue; + } + if (0 != getnameinfo((struct sockaddr*)lres->ai_addr, + lres->ai_addrlen, + uaddr,INET6_ADDRSTRLEN,uport,32, + NI_NUMERICHOST | NI_NUMERICSERV)) { + if (tcp_verbose) + fprintf(stderr,"getnameinfo (local): oops\n"); + continue; + } + if (-1 == bind(sock, lres->ai_addr, lres->ai_addrlen)) { + if (tcp_verbose) + fprintf(stderr,"%s [%s] %s bind: %s\n", + strfamily(lres->ai_family),uaddr,uport, + strerror(errno)); + continue; + } + } + /* connect to peer */ + if (-1 == connect(sock,e->ai_addr,e->ai_addrlen)) { + if (tcp_verbose) + fprintf(stderr,"%s %s [%s] %s connect: %s\n", + strfamily(e->ai_family),e->ai_canonname,uhost,userv, + strerror(errno)); + close(sock); + continue; + } + if (tcp_verbose) + fprintf(stderr,"%s %s [%s] %s open\n", + strfamily(e->ai_family),e->ai_canonname,uhost,userv); + fcntl(sock,F_SETFL,O_NONBLOCK); + return sock; + } + return -1; +} + +int tcp_listen(struct addrinfo *ai, char *addr, char *port) +{ + struct addrinfo *res,*e; + char uaddr[INET6_ADDRSTRLEN+1]; + char uport[33]; + int slisten,rc,opt=1; + + /* lookup */ + ai->ai_flags = AI_PASSIVE; + if (0 != (rc = getaddrinfo(addr, port, ai, &res))) { + if (tcp_verbose) + fprintf(stderr,"getaddrinfo: %s\n",gai_strerror(rc)); + exit(1); + } + + /* create socket + bind */ + for (e = res; e != NULL; e = e->ai_next) { + getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen, + uaddr,INET6_ADDRSTRLEN,uport,32, + NI_NUMERICHOST | NI_NUMERICSERV); + if (-1 == (slisten = socket(e->ai_family, e->ai_socktype, + e->ai_protocol))) { + if (tcp_verbose) + fprintf(stderr,"socket (%s): %s\n", + strfamily(e->ai_family),strerror(errno)); + continue; + } + opt = 1; + setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); + if (-1 == bind(slisten, e->ai_addr, e->ai_addrlen)) { + if (tcp_verbose) + fprintf(stderr,"%s [%s] %s bind: %s\n", + strfamily(e->ai_family),uaddr,uport, + strerror(errno)); + continue; + } + listen(slisten,1); + break; + } + if (NULL == e) + return -1; + + /* wait for a incoming connection */ + if (tcp_verbose) + fprintf(stderr,"listen on %s [%s] %s ...\n", + strfamily(e->ai_family),uaddr,uport); + fcntl(slisten,F_SETFL,O_NONBLOCK); + return slisten; +} + +int unix_connect(char *path) +{ + struct sockaddr_un un; + int sock; + + if (-1 == (sock = socket(PF_UNIX, SOCK_STREAM, 0))) { + perror("socket"); + return -1; + } + + un.sun_family = AF_UNIX; + strncpy(un.sun_path, path, sizeof(un.sun_path)); + if (-1 == connect(sock, (struct sockaddr*) &un, sizeof(un))) { + fprintf(stderr, "connect(unix:%s): %s\n", path, strerror(errno)); + return -1; + } + + return sock; +} + +int pipe_connect(char *path) +{ + struct stat st; + int fd; + + fd = open(path, O_RDWR); + if (fd == -1) { + mkfifo(path, 0666); + fd = open(path, O_RDWR); + } + if (fd == -1) { + fprintf(stderr, "open %s: %s\n", path, strerror(errno)); + return -1; + } + fstat(fd, &st); + if (!S_ISFIFO(st.st_mode)) { + fprintf(stderr, "not a pipe: %s\n", path); + close(fd); + return -1; + } + return fd; +} diff --git a/tcp.h b/tcp.h new file mode 100644 index 0000000..963ab83 --- /dev/null +++ b/tcp.h @@ -0,0 +1,15 @@ +#include +#include +#include +#include + +extern int tcp_verbose; + +int tcp_connect(struct addrinfo *ai, + char *addr, char *port, + char *host, char *serv); + +int tcp_listen(struct addrinfo *ai, char *addr, char *port); + +int unix_connect(char *path); +int pipe_connect(char *path); -- cgit