qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 4/4] Add an example GTK based GUI that uses the shar


From: Anthony Liguori
Subject: [Qemu-devel] [PATCH 4/4] Add an example GTK based GUI that uses the shared memory interface
Date: Sun, 16 Jul 2006 13:15:48 -0500
User-agent: Thunderbird 1.5.0.4 (X11/20060615)

To run from the subdirectory, first do a make and then run:

./gtk-qemu -qemu ../i386-softmmu/qemu <normal qemu args...>

Regards,

Anthony Liguori
# HG changeset patch
# User address@hidden
# Node ID 1f7d5a9d262388eb7ca22a0869f0bdddb104a0bd
# Parent  161ce66608016b77bb1889ce5bd046b26e942476
Add a sample GUI that uses the shared memory interface.  This GUI is written
in Python and uses a GTK widget.

diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/Makefile
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/Makefile      Sun Jul 16 17:47:31 2006
@@ -0,0 +1,50 @@
+# Makefile -- QEMU GTK GUI
+#
+#  Copyright (C) 2006 Anthony Liguori <address@hidden>
+#
+# This file is subject to the terms and conditions of the GNU General Public
+# License.  See the file COPYING in the main directory of this archive for more
+# details.
+
+prefix ?= /usr
+
+CFLAGS = $(shell pkg-config --cflags gtk+-2.0 pygtk-2.0)
+LDLIBS = $(shell pkg-config --libs gtk+-2.0 pygtk-2.0)
+
+CODEGENDIR = $(shell pkg-config --variable=codegendir pygtk-2.0)
+DEFSDIR = $(shell pkg-config --variable=defsdir pygtk-2.0)
+
+PYVERSION = $(shell python -c "import sys; print sys.version[:3]")
+PYLIBDIR = $(prefix)/lib/python$(PYVERSION)/site-packages
+
+CFLAGS += -I$(prefix)/include/python$(PYVERSION)
+
+CFLAGS += -Wall -g
+
+INSTALL_DIR = install -d
+INSTALL_LIB = install -m755
+INSTALL_BIN = install -m755
+
+all: qemu.so
+
+clean: 
+       $(RM) *.o *~ qemu.defs gen-qemu.defs.c qemu.so
+
+qemu-screen.o: qemu-screen.c qemu-screen.h qemu-keys.h
+
+qemu.defs: qemu-screen.h
+       python $(CODEGENDIR)/h2def.py $< > $@
+
+gen-qemu.defs.c: qemu.override qemu.defs
+       pygtk-codegen-2.0 --prefix qemu --register $(DEFSDIR)/gdk-types.defs \
+                          --register $(DEFSDIR)/gtk-types.defs \
+                          --override qemu.override qemu.defs > $@
+
+qemu.so: gen-qemu.defs.o qemu-module.o qemu-screen.o
+       $(CC) $(LDLIBS) -shared $^ -o $@
+
+install: qemu.so
+       $(INSTALL_DIR) $(DESTDIR)$(prefix)/bin
+       $(INSTALL_BIN) gtk-qemu $(DESTDIR)$(prefix)/bin/gtk-qemu
+       $(INSTALL_DIR) $(DESTDIR)$(PYLIBDIR)
+       $(INSTALL_LIB) qemu.so $(DESTDIR)$(PYLIBDIR)/qemu.so
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/README
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/README        Sun Jul 16 17:47:31 2006
@@ -0,0 +1,23 @@
+QEMU GTK GUI
+
+This is a simple GUI that makes use of QEMU's new shared memory interface.
+It uses a XShmImage so that there is virtually no performance overhead to
+having the screen appear in an external application.
+
+Interacting with this GUI is very similar to interacting with the SDL GUI with
+a few important exceptions.
+
+ - There is currently no full screen mode so ctrl-alt-f and -fs do nothing.
+
+ - Instead of switching virtual consoles with ctrl-alt-N, virtual consoles will
+   appear as tabs within a notebook.
+
+ - There is an additional -qemu option which can be used to specify the
+   location of the QEMU executable that you want this GUI to use (normally
+   `qemu' is executed using whatever the current path is).
+
+The hope is that this will provide an example on constructing GUIs that use
+QEMU and perhaps will grow in the future to a much more user friendly interface
+to QEMU.
+
+       aliguori
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/gtk-qemu
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/gtk-qemu      Sun Jul 16 17:47:31 2006
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+#
+# gtk-qemu -- QEMU GTK GUI
+#
+#  Copyright (C) 2006 Anthony Liguori <address@hidden>
+#
+# This file is subject to the terms and conditions of the GNU General Public
+# License.  See the file COPYING in the main directory of this archive for more
+# details.
+
+import sys, os, signal
+from subprocess import Popen, PIPE
+from select import select
+
+def alloc_pty():
+    (master, slave) = os.openpty()
+    return (master, os.ttyname(slave))
+
+def parse_args(args, withargs):
+    i = 0
+    while i < len(args):
+        if args[i] in withargs:
+            a = (args[i], args[i + 1])
+            i += 1
+        else:
+            a = (args[i], None)
+        i += 1
+        yield a
+
+def wait_for_error(fd, pid):
+    while True:
+        r, w, e = select([fd], [], [], .5)
+        if len(r) == 0:
+            f, s = os.waitpid(pid, os.WNOHANG)
+            if f != 0 or s != 0:
+                return True
+        else:
+            return False
+
+class MouseHandler(object):
+    def __init__(self, screen, window):
+        self.screen = screen
+        self.window = window
+        self.in_grab = False
+        self.relative = screen.relative_pointer()
+
+    def do_grab(self):
+        self.screen.enable_pointer(True)
+        self.window.set_title('QEMU - Press Ctrl-Alt to exit grab')
+        gtk.gdk.pointer_grab(self.screen.window,
+                             event_mask=gtk.gdk.BUTTON_PRESS_MASK |
+                             gtk.gdk.BUTTON_RELEASE_MASK |
+                             gtk.gdk.BUTTON_MOTION_MASK |
+                             gtk.gdk.POINTER_MOTION_MASK)
+        gtk.gdk.keyboard_grab(self.screen.window)
+        self.in_grab = True
+
+    def do_ungrab(self):
+        if self.relative:
+            self.screen.enable_pointer(False)
+        self.window.set_title('QEMU')
+        gtk.gdk.pointer_ungrab()
+        gtk.gdk.keyboard_ungrab()
+        self.in_grab = False
+
+    def toggle_grab(self):
+        if self.in_grab:
+            self.do_ungrab()
+        else:
+            self.do_grab()
+
+    def force_grab(self):
+        self.check_grab()
+        
+        if not self.in_grab and self.screen.relative_pointer():
+            self.do_grab()
+
+    def check_grab(self, x=None, y=None):
+        if self.relative != self.screen.relative_pointer():
+            if self.relative:
+                self.relative = False
+                if self.in_grab:
+                    self.do_ungrab()
+                else:
+                    self.screen.enable_pointer(True)
+            else:
+                self.relative = True
+                if not self.in_grab:
+                    self.screen.enable_pointer(False)
+        if self.in_grab and (x and y):
+            scrn = gtk.gdk.screen_get_default()
+            need_warp = False
+            if x < 20:
+                x += 20
+                need_warp = True
+            if y < 20:
+                y += 20
+                need_warp = True
+            if x > scrn.get_width():
+                x -= 20
+                need_warp = True
+            if y > scrn.get_height():
+                y -= 20
+                need_warp = True
+
+            if need_warp:
+                self.screen.warp_pointer(x, y)
+
+def enable_grab(widget, event, handler):
+    if event.button == 1:
+        handler.force_grab()
+    return False
+
+def toggle_grab(widget, event, handler):
+    if ((event.keyval == gtk.gdk.keyval_from_name('Control_L') and
+        event.state == gtk.gdk.MOD1_MASK) or
+        (event.keyval == gtk.gdk.keyval_from_name('Alt_L') and
+        event.state == gtk.gdk.CONTROL_MASK)):
+        handler.toggle_grab()
+    return False
+
+def check_grab(widget, event, handler):
+    handler.check_grab(int(event.x_root), int(event.y_root))
+    return False
+
+wargs = ['-M', '-fda', '-fdb', '-hda', '-hdb', '-hdc', '-hdd', '-cdrom',
+         '-boot', '-m', '-smp', '-k', '-soundhw', '-usbdevice', '-net',
+         '-tftp', '-smb', '-redir', '-kernel', '-append', '-initrd',
+         '-monitor', '-serial', '-parallel', '-pidfile', '-p','-hdachs',
+         '-L', '-loadvm', '-vnc', '-qemu']
+
+channels = []
+
+shmem_fd, name = alloc_pty()
+args = ['qemu', '-shmem', 'pipe:%s' % name]
+channels.append(('-shmem', shmem_fd))
+
+has_dev = {'-serial' : 0, '-monitor': 0, '-parallel': 0}
+
+# search arguments for virtual consoles
+for key, value in parse_args(sys.argv[1:], wargs):
+    if key in ['-serial', '-monitor', '-parallel']:
+        if value == 'vc':
+            fd, name = alloc_pty()
+            value = 'pipe:%s' % name
+
+            title = key
+            if key != '-monitor':
+                title += str(has_dev[key])
+
+            channels.append((title, fd))
+        has_dev[key] += 1
+    elif key == '-qemu':
+        args[0] = value
+        continue
+
+    args.append(key)
+    if value != None:
+        args.append(value)
+
+# add default virtual consoles
+for x in ['-parallel', '-serial', '-monitor']:
+    if not has_dev[x] > 0:
+        fd, name = alloc_pty()
+        args += [x, 'pipe:%s' % name]
+        if x != '-monitor':
+            x += '0'
+        channels = [(x, fd)] + channels
+
+import qemu, gtk, vte, termios, tty, gtk.gdk, gobject
+
+win = gtk.Window()
+win.set_resizable(False)
+win.set_title('QEMU')
+win.connect('delete-event', gtk.main_quit)
+
+screen = qemu.Screen()
+
+handler = MouseHandler(screen, win)
+
+screen.connect('button-press-event', enable_grab, handler)
+screen.connect('key-press-event', toggle_grab, handler)
+screen.connect('motion-notify-event', check_grab, handler)
+
+# create notebook and add vga tab
+nb = gtk.Notebook()
+nb.insert_page(screen, gtk.Label('vga'))
+
+# create notebook tabs foreach virtual console
+for kind, fd in channels:
+    if kind == '-shmem':
+        screen.attach(fd)
+    else:
+        term = vte.Terminal()
+        tty.setraw(fd, termios.TCSANOW)
+        term.set_pty(fd)
+        term.set_font_from_string('monospace')
+        term.set_scrollback_lines(1000)
+        w = gtk.ScrolledWindow()
+        w.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        w.add_with_viewport(term)
+        nb.insert_page(w, gtk.Label(kind[1:]))
+
+# when qemu exits, close the gui
+def on_qemu_exit(pid, status, q):
+    q.pid = -1
+    gtk.main_quit()
+    
+q = Popen(args)
+gobject.child_watch_add(q.pid, on_qemu_exit, q)
+
+# if there is only the VGA page, don't bother showing tabs
+if nb.get_n_pages() == 1:
+    nb.set_show_tabs(False)
+    nb.set_show_border(False)
+win.add(nb)
+
+if not wait_for_error(shmem_fd, q.pid):
+    win.show_all()
+    screen.grab_focus()
+
+    gtk.main()
+
+    screen.detach()
+
+    # kill qemu if it's still around
+    if q.pid != -1:
+        os.kill(q.pid, signal.SIGTERM)
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/qemu-keys.h
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/qemu-keys.h   Sun Jul 16 17:47:31 2006
@@ -0,0 +1,252 @@
+/*
+ * qemu-keys.h -- QEMU GTK Widget
+ *
+ *  Copyright (C) 2006 Anthony Liguori <address@hidden>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License.  See the file COPYING.lib in the main directory of this
+ * archive for more details.
+ */
+
+#ifndef _QEMU_KEYS_H_
+#define _QEMU_KEYS_H_
+
+#include <gdk/gdkkeysyms.h>
+
+static const char *qemu_keymap[0x10000] = {
+  [GDK_A] = "a",
+  [GDK_B] = "b",
+  [GDK_C] = "c",
+  [GDK_D] = "d",
+  [GDK_E] = "e",
+  [GDK_F] = "f",
+  [GDK_G] = "g",
+  [GDK_H] = "h",
+  [GDK_I] = "i",
+  [GDK_J] = "j",
+  [GDK_K] = "k",
+  [GDK_L] = "l",
+  [GDK_M] = "m",
+  [GDK_N] = "n",
+  [GDK_O] = "o",
+  [GDK_P] = "p",
+  [GDK_Q] = "q",
+  [GDK_R] = "r",
+  [GDK_S] = "s",
+  [GDK_T] = "t",
+  [GDK_U] = "u",
+  [GDK_V] = "v",
+  [GDK_W] = "w",
+  [GDK_X] = "x",
+  [GDK_Y] = "y",
+  [GDK_Z] = "z",
+
+  [GDK_bracketleft] = "bracketleft",
+  [GDK_backslash] = "backslash",
+  [GDK_bracketright] = "bracketright",
+  [GDK_asciicircum] = "asciicircum",
+  [GDK_underscore] = "underscore",
+  [GDK_grave] = "grave",
+
+  [GDK_a] = "a",
+  [GDK_b] = "b",
+  [GDK_c] = "c",
+  [GDK_d] = "d",
+  [GDK_e] = "e",
+  [GDK_f] = "f",
+  [GDK_g] = "g",
+  [GDK_h] = "h",
+  [GDK_i] = "i",
+  [GDK_j] = "j",
+  [GDK_k] = "k",
+  [GDK_l] = "l",
+  [GDK_m] = "m",
+  [GDK_n] = "n",
+  [GDK_o] = "o",
+  [GDK_p] = "p",
+  [GDK_q] = "q",
+  [GDK_r] = "r",
+  [GDK_s] = "s",
+  [GDK_t] = "t",
+  [GDK_u] = "u",
+  [GDK_v] = "v",
+  [GDK_w] = "w",
+  [GDK_x] = "x",
+  [GDK_y] = "y",
+  [GDK_z] = "z",
+
+  [GDK_BackSpace] = "BackSpace",
+  [GDK_Tab] = "Tab",
+  [GDK_Linefeed] = "Linefeed",
+  [GDK_Clear] = "Clear",
+  [GDK_Return] = "Return",
+  [GDK_Pause] = "Pause",
+  [GDK_Scroll_Lock] = "Scroll_Lock",
+  [GDK_Sys_Req] = "Sys_Req",
+  [GDK_Escape] = "Escape",
+  [GDK_Delete] = "Delete",
+
+  [GDK_space] = "space",
+  [GDK_exclam] = "exclam",
+  [GDK_quotedbl] = "quotedbl",
+  [GDK_numbersign] = "numbersign",
+  [GDK_dollar] = "dollar",
+  [GDK_percent] = "percent",
+  [GDK_ampersand] = "ampersand",
+  [GDK_apostrophe] = "apostrophe",
+  [GDK_parenleft] = "parenleft",
+  [GDK_parenright] = "parenright",
+  [GDK_asterisk] = "asterisk",
+  [GDK_plus] = "plus",
+  [GDK_comma] = "comma",
+  [GDK_minus] = "minus",
+  [GDK_period] = "period",
+  [GDK_slash] = "slash",
+  [GDK_0] = "0",
+  [GDK_1] = "1",
+  [GDK_2] = "2",
+  [GDK_3] = "3",
+  [GDK_4] = "4",
+  [GDK_5] = "5",
+  [GDK_6] = "6",
+  [GDK_7] = "7",
+  [GDK_8] = "8",
+  [GDK_9] = "9",
+  [GDK_colon] = "colon",
+  [GDK_semicolon] = "semicolon",
+  [GDK_less] = "less",
+  [GDK_equal] = "equal",
+  [GDK_greater] = "greater",
+  [GDK_question] = "question",
+  [GDK_at] = "at",
+
+  [GDK_Control_L] = "Control_L",
+  [GDK_Control_R] = "Control_R",
+  [GDK_Alt_L] = "Alt_L",
+  [GDK_Alt_R] = "Alt_R",
+  [GDK_Shift_L] = "Shift_L",
+  [GDK_Shift_R] = "Shift_R",
+
+  [GDK_Caps_Lock] = "Caps_Lock",
+  [GDK_Shift_Lock] = "Shift_Lock",
+  [GDK_Meta_L] = "Meta_L",
+  [GDK_Meta_R] = "Meta_R",
+  [GDK_Super_L] = "Super_L",
+  [GDK_Super_R] = "Super_R",
+  [GDK_Hyper_L] = "Hyper_L",
+  [GDK_Hyper_R] = "Hyper_R",
+
+  [GDK_Home] = "Home",
+  [GDK_Left] = "Left",
+  [GDK_Up] = "Up",
+  [GDK_Right] = "Right",
+  [GDK_Down] = "Down",
+  [GDK_Prior] = "Prior",
+  [GDK_Page_Up] = "Page_Up",
+  [GDK_Next] = "Next",
+  [GDK_Page_Down] = "Page_Down",
+  [GDK_End] = "End",
+  [GDK_Begin] = "Begin",
+  [GDK_Select] = "Select",
+  [GDK_Print] = "Print",
+  [GDK_Execute] = "Execute",
+  [GDK_Insert] = "Insert",
+  [GDK_Undo] = "Undo",
+  [GDK_Redo] = "Redo",
+  [GDK_Menu] = "Menu",
+  [GDK_Find] = "Find",
+  [GDK_Cancel] = "Cancel",
+  [GDK_Help] = "Help",
+  [GDK_Break] = "Break",
+  [GDK_Mode_switch] = "Mode_switch",
+  [GDK_script_switch] = "script_switch",
+  [GDK_Num_Lock] = "Num_Lock",
+  [GDK_KP_Space] = "KP_Space",
+  [GDK_KP_Tab] = "KP_Tab",
+  [GDK_KP_Enter] = "KP_Enter",
+  [GDK_KP_F1] = "KP_F1",
+  [GDK_KP_F2] = "KP_F2",
+  [GDK_KP_F3] = "KP_F3",
+  [GDK_KP_F4] = "KP_F4",
+  [GDK_KP_Home] = "KP_Home",
+  [GDK_KP_Left] = "KP_Left",
+  [GDK_KP_Up] = "KP_Up",
+  [GDK_KP_Right] = "KP_Right",
+  [GDK_KP_Down] = "KP_Down",
+  [GDK_KP_Prior] = "KP_Prior",
+  [GDK_KP_Page_Up] = "KP_Page_Up",
+  [GDK_KP_Next] = "KP_Next",
+  [GDK_KP_Page_Down] = "KP_Page_Down",
+  [GDK_KP_End] = "KP_End",
+  [GDK_KP_Begin] = "KP_Begin",
+  [GDK_KP_Insert] = "KP_Insert",
+  [GDK_KP_Delete] = "KP_Delete",
+  [GDK_KP_Equal] = "KP_Equal",
+  [GDK_KP_Multiply] = "KP_Multiply",
+  [GDK_KP_Add] = "KP_Add",
+  [GDK_KP_Separator] = "KP_Separator",
+  [GDK_KP_Subtract] = "KP_Subtract",
+  [GDK_KP_Decimal] = "KP_Decimal",
+  [GDK_KP_Divide] = "KP_Divide",
+  [GDK_KP_0] = "KP_0",
+  [GDK_KP_1] = "KP_1",
+  [GDK_KP_2] = "KP_2",
+  [GDK_KP_3] = "KP_3",
+  [GDK_KP_4] = "KP_4",
+  [GDK_KP_5] = "KP_5",
+  [GDK_KP_6] = "KP_6",
+  [GDK_KP_7] = "KP_7",
+  [GDK_KP_8] = "KP_8",
+  [GDK_KP_9] = "KP_9",
+  [GDK_F1] = "F1",
+  [GDK_F2] = "F2",
+  [GDK_F3] = "F3",
+  [GDK_F4] = "F4",
+  [GDK_F5] = "F5",
+  [GDK_F6] = "F6",
+  [GDK_F7] = "F7",
+  [GDK_F8] = "F8",
+  [GDK_F9] = "F9",
+  [GDK_F10] = "F10",
+  [GDK_F11] = "F11",
+  [GDK_F12] = "F12",
+
+  [GDK_braceleft] = "braceleft",
+  [GDK_bar] = "bar",
+  [GDK_braceright] = "braceright",
+  [GDK_asciitilde] = "asciitilde",
+  [GDK_nobreakspace] = "nobreakspace",
+  [GDK_exclamdown] = "exclamdown",
+  [GDK_cent] = "cent",
+  [GDK_sterling] = "sterling",
+  [GDK_currency] = "currency",
+  [GDK_yen] = "yen",
+  [GDK_brokenbar] = "brokenbar",
+  [GDK_section] = "section",
+  [GDK_diaeresis] = "diaeresis",
+  [GDK_copyright] = "copyright",
+  [GDK_ordfeminine] = "ordfeminine",
+  [GDK_guillemotleft] = "guillemotleft",
+  [GDK_notsign] = "notsign",
+  [GDK_hyphen] = "hyphen",
+  [GDK_registered] = "registered",
+  [GDK_macron] = "macron",
+  [GDK_degree] = "degree",
+  [GDK_plusminus] = "plusminus",
+  [GDK_twosuperior] = "twosuperior",
+  [GDK_threesuperior] = "threesuperior",
+  [GDK_acute] = "acute",
+  [GDK_mu] = "mu",
+  [GDK_paragraph] = "paragraph",
+  [GDK_periodcentered] = "periodcentered",
+  [GDK_cedilla] = "cedilla",
+  [GDK_onesuperior] = "onesuperior",
+  [GDK_masculine] = "masculine",
+  [GDK_guillemotright] = "guillemotright",
+  [GDK_onequarter] = "onequarter",
+  [GDK_onehalf] = "onehalf",
+  [GDK_threequarters] = "threequarters",
+  [GDK_questiondown] = "questiondown",
+};
+
+#endif
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/qemu-module.c
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/qemu-module.c Sun Jul 16 17:47:31 2006
@@ -0,0 +1,36 @@
+/*
+ * qemu-module.c -- QEMU GTK Widget Python Bindings
+ *
+ *  Copyright (C) 2006 Anthony Liguori <address@hidden>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License.  See the file COPYING.lib in the main directory of this
+ * archive for more details.
+ */
+
+#include <pygobject.h>
+ 
+void qemu_register_classes (PyObject *d); 
+extern PyMethodDef qemu_functions[];
+ 
+DL_EXPORT(void)
+initqemu(void)
+{
+    PyObject *m, *d;
+ 
+    init_pygobject ();
+ 
+    m = Py_InitModule ("qemu", qemu_functions);
+    if (PyErr_Occurred())
+       Py_FatalError("can't init module");
+
+    d = PyModule_GetDict (m);
+    if (PyErr_Occurred())
+       Py_FatalError("can't get dict");
+ 
+    qemu_register_classes (d);
+ 
+    if (PyErr_Occurred ()) {
+        Py_FatalError ("can't initialise module qemu");
+    }
+}
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/qemu-screen.c
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/qemu-screen.c Sun Jul 16 17:47:31 2006
@@ -0,0 +1,592 @@
+/*
+ * qemu-screen.c -- QEMU GTK Widget
+ *
+ *  Copyright (C) 2006 Anthony Liguori <address@hidden>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License.  See the file COPYING.lib in the main directory of this
+ * archive for more details.
+ */
+
+#include "qemu-screen.h"
+
+#include <gtk/gtk.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+
+#include <gdk/gdkx.h>
+
+#include "qemu-keys.h"
+
+#define QEMU_SCREEN_GET_PRIVATE(obj) \
+        (G_TYPE_INSTANCE_GET_PRIVATE((obj), QEMU_TYPE_SCREEN, QemuScreenPriv))
+
+struct _QemuScreenPriv
+{
+    int fd;
+    GIOChannel *channel;
+    int id;
+    char line[1024];
+    int offset;
+    int shmid;
+    XImage *ximage;
+    XShmSegmentInfo shm_info;
+    GdkGC *gc;
+    gboolean report_pointer;
+    /* TODO use signal */
+    gboolean relative_pointer;
+    GdkCursor *null_cursor;
+    int x_offset, y_offset;
+};
+
+/* TODO deref gc on exit; cursor too */
+
+static gboolean startswith(const char *lhs, const char *rhs)
+{
+    if (strlen(lhs) < strlen(rhs))
+       return FALSE;
+    return (strncmp(lhs, rhs, strlen(rhs)) == 0);
+}
+
+static void qemu_screen_send_cmd(QemuScreen *obj, const char *fmt, ...)
+{
+    QemuScreenPriv *priv = obj->priv;
+    char cmd[1024];
+    va_list ap;
+    int len;
+    
+    va_start(ap, fmt);
+    len = vsnprintf(cmd, sizeof(cmd), fmt, ap);
+    va_end(ap);
+
+    if (priv->fd != -1 && write(priv->fd, cmd, len) != len) {
+       qemu_screen_detach(obj);
+       g_warning("partial write?\n");
+    }
+}
+
+static void qemu_screen_class_init(QemuScreenClass *klass)
+{
+    g_type_class_add_private(klass, sizeof(QemuScreenPriv));
+}
+
+static gboolean qemu_screen_expose(GtkWidget *widget, GdkEventExpose *expose,
+                                  gpointer data)
+{
+    QemuScreen *obj = QEMU_SCREEN(widget);
+    QemuScreenPriv *priv = obj->priv;
+
+    if (priv->ximage) {
+       int x, y, w, h;
+
+       x = MIN(expose->area.x, priv->ximage->width);
+       y = MIN(expose->area.y, priv->ximage->height);
+       w = MIN(expose->area.x + expose->area.width, priv->ximage->width);
+       h = MIN(expose->area.y + expose->area.height, priv->ximage->height);
+       
+       w -= x;
+       h -= y;
+       
+       XShmPutImage(GDK_GC_XDISPLAY(priv->gc),
+                    GDK_DRAWABLE_XID(widget->window),
+                    GDK_GC_XGC(priv->gc),
+                    priv->ximage,
+                    x, y, x, y, w, h, False);
+    }
+
+    return TRUE;
+}
+
+static gboolean qemu_screen_motion(GtkWidget *widget, GdkEventMotion *motion,
+                                  gpointer data)
+{
+    QemuScreen *obj = QEMU_SCREEN(widget);
+    QemuScreenPriv *priv = obj->priv;
+    int buttons = 0;
+
+    if (motion->state & 0x100)
+       buttons |= 1;
+    if (motion->state & 0x400)
+       buttons |= 2;
+    if (motion->state & 0x200)
+       buttons |= 4;
+
+    if (!priv->relative_pointer || priv->report_pointer) {
+       qemu_screen_send_cmd(obj, "MOUSE %d, %d, %d, %d\n",
+                            (int)motion->x + priv->x_offset,
+                            (int)motion->y + priv->y_offset,
+                            0, buttons);
+    }
+
+    return FALSE;
+}
+
+static gboolean qemu_screen_button(GtkWidget *widget, GdkEventButton *button,
+                                  gpointer data)
+{
+    QemuScreen *obj = QEMU_SCREEN(widget);
+    QemuScreenPriv *priv = obj->priv;
+    int buttons = 0;
+    int b;
+
+    if (button->state & 0x100)
+       buttons |= 1;
+    if (button->state & 0x400)
+       buttons |= 2;
+    if (button->state & 0x200)
+       buttons |= 4;
+
+    if (button->button == 1)
+       b = 0;
+    else if (button->button == 3)
+       b = 1;
+    else if (button->button == 2)
+       b = 2;
+    else
+       return FALSE;
+
+    if (button->type == GDK_BUTTON_PRESS)
+       buttons |= (1 << b);
+    else if (button->type == GDK_BUTTON_RELEASE)
+       buttons &= ~(1 << b);
+
+    if (!priv->relative_pointer || priv->report_pointer) {
+       qemu_screen_send_cmd(obj, "MOUSE %d, %d, %d, %d\n",
+                            (int)button->x + priv->x_offset,
+                            (int)button->y + priv->y_offset,
+                            0, buttons);
+    }
+
+    return FALSE;
+}
+
+static gboolean qemu_screen_scroll(GtkWidget *widget, GdkEventScroll *scroll,
+                                  gpointer data)
+{
+    QemuScreen *obj = QEMU_SCREEN(widget);
+    QemuScreenPriv *priv = obj->priv;
+    int buttons = 0;
+    int dz = 0;
+
+    if (scroll->state & 0x100)
+       buttons |= 1;
+    if (scroll->state & 0x400)
+       buttons |= 2;
+    if (scroll->state & 0x200)
+       buttons |= 4;
+
+    if (scroll->direction == GDK_SCROLL_UP)
+       dz = -1;
+    else if (scroll->direction == GDK_SCROLL_DOWN)
+       dz = 1;
+
+    if (!priv->relative_pointer || priv->report_pointer) {
+       qemu_screen_send_cmd(obj, "MOUSE %d, %d, %d, %d\n",
+                            (int)scroll->x + priv->x_offset,
+                            (int)scroll->y + priv->y_offset,
+                            dz, buttons);
+    }
+
+    return TRUE;
+}
+
+static gboolean qemu_screen_key(GtkWidget *widget, GdkEventKey *key,
+                               gpointer data)
+{
+    QemuScreen *obj = QEMU_SCREEN(widget);
+    const char *name;
+    int down;
+
+    if (key->type == GDK_KEY_PRESS)
+       down = 1;
+    else
+       down = 0;
+
+    if (key->keyval >= 0x10000)
+       return FALSE;
+
+    name = qemu_keymap[key->keyval];
+
+    if (name)
+       qemu_screen_send_cmd(obj, "KEY-%s %s\n", down ? "DOWN" : "UP", name);
+
+    return FALSE;
+}
+
+static GdkCursor *create_null_cursor(void)
+{
+    GdkBitmap *image;
+    gchar data[4] = {0};
+    GdkColor fg = { 0 };
+    GdkCursor *cursor;
+
+    image = gdk_bitmap_create_from_data(NULL, data, 1, 1);
+
+    cursor = gdk_cursor_new_from_pixmap(GDK_PIXMAP(image),
+                                       GDK_PIXMAP(image),
+                                       &fg, &fg, 0, 0);
+    gdk_bitmap_unref(image);
+
+    return cursor;
+}
+
+static void qemu_screen_init(GTypeInstance *instance, gpointer klass)
+{
+    QemuScreen *obj = QEMU_SCREEN(instance);
+    obj->priv = QEMU_SCREEN_GET_PRIVATE(obj);
+
+    obj->priv->fd = -1;
+    obj->priv->channel = NULL;
+    obj->priv->id = -1;
+    obj->priv->offset = 0;
+    obj->priv->ximage = NULL;
+    obj->priv->shm_info.shmaddr = (void *)-1;
+    obj->priv->shm_info.shmid = -1;
+    obj->priv->gc = NULL;
+    obj->priv->report_pointer = FALSE;
+    obj->priv->relative_pointer = TRUE;
+    obj->priv->null_cursor = create_null_cursor();
+    obj->priv->x_offset = 0;
+    obj->priv->y_offset = 0;
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "expose-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_expose),
+                      NULL);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "motion-notify-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_motion),
+                      NULL);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "button-press-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_button),
+                      NULL);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "button-release-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_button),
+                      NULL);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "scroll-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_scroll),
+                      NULL);
+
+    GTK_WIDGET_SET_FLAGS(GTK_OBJECT(obj), GTK_CAN_FOCUS);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "key-press-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_key),
+                      NULL);
+
+    gtk_signal_connect(GTK_OBJECT(obj),
+                      "key-release-event",
+                      GTK_SIGNAL_FUNC(qemu_screen_key),
+                      NULL);
+
+    gtk_widget_set_double_buffered(GTK_WIDGET(obj), FALSE);
+
+    gtk_widget_add_events(GTK_WIDGET(obj),
+                         GDK_BUTTON_MOTION_MASK |
+                         GDK_POINTER_MOTION_MASK |
+                         GDK_BUTTON_PRESS_MASK |
+                         GDK_BUTTON_RELEASE_MASK | 
+                         GDK_KEY_PRESS_MASK |
+                         GDK_KEY_RELEASE_MASK |
+                         GDK_SCROLL_MASK);
+}
+
+GType qemu_screen_get_type(void)
+{
+    static GType type;
+
+    if (type == 0) {
+       static const GTypeInfo info = {
+           sizeof(QemuScreenClass),
+           NULL,
+           NULL,
+           (GClassInitFunc)qemu_screen_class_init,
+           NULL,
+           NULL,
+           sizeof(QemuScreen),
+           0,
+           qemu_screen_init,
+       };
+
+       type = g_type_register_static(GTK_TYPE_DRAWING_AREA,
+                                     "QemuScreen",
+                                     &info,
+                                     0);
+    }
+
+    return type;
+}
+
+static void qemu_screen_resize(QemuScreen *obj, int width, int height)
+{
+    QemuScreenPriv *priv = obj->priv;
+    size_t size;
+    GdkDrawable *drawable;
+
+    drawable = GTK_WIDGET(obj)->window;
+
+    if (priv->gc == NULL)
+       priv->gc = gdk_gc_new(drawable);
+
+    if (priv->ximage) {
+       XDestroyImage(priv->ximage);
+       shmdt(priv->shm_info.shmaddr);
+       shmctl(priv->shm_info.shmid, IPC_RMID, NULL);
+
+       priv->ximage = NULL;
+       priv->shm_info.shmaddr = (void *)-1;
+       priv->shm_info.shmid = -1;
+    }
+
+    priv->ximage = XShmCreateImage(GDK_GC_XDISPLAY(priv->gc),
+                                  GDK_VISUAL_XVISUAL(gdk_visual_get_best()),
+                                  gdk_drawable_get_depth(drawable),
+                                  ZPixmap, NULL, &priv->shm_info,
+                                  width, height);
+    if (priv->ximage == NULL) {
+       qemu_screen_detach(obj);
+       return;
+    }
+
+    size = priv->ximage->bytes_per_line * priv->ximage->height;
+    priv->shm_info.shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
+    if (priv->shm_info.shmid == -1) {
+       qemu_screen_detach(obj);
+       return;
+    }
+
+    priv->shm_info.readOnly = False;
+    priv->shm_info.shmaddr = shmat(priv->shm_info.shmid, NULL, 0);
+    if (priv->shm_info.shmaddr == (void *)-1) {
+       qemu_screen_detach(obj);
+       return;
+    }
+
+    priv->ximage->data = priv->shm_info.shmaddr;
+    
+    gdk_error_trap_push();
+    XShmAttach(GDK_GC_XDISPLAY(priv->gc), &priv->shm_info);
+    XSync(GDK_GC_XDISPLAY(priv->gc), False);
+    if (gdk_error_trap_pop()) {
+       qemu_screen_detach(obj);
+       return;
+    }
+
+    qemu_screen_send_cmd(obj, "SHMID %d, %d, %d, %d, %d, %d\n",
+                        priv->shm_info.shmid, size,
+                        width, height,
+                        priv->ximage->bits_per_pixel,
+                        priv->ximage->bytes_per_line);
+
+    gtk_widget_set_size_request(GTK_WIDGET(obj), width, height);
+}
+
+static void qemu_screen_update(QemuScreen *obj, int x, int y, int w, int h)
+{
+    gtk_widget_queue_draw_area(GTK_WIDGET(obj), x, y, w, h);
+}
+
+static void qemu_screen_attached(QemuScreen *obj, int shmid)
+{
+    QemuScreenPriv *priv = obj->priv;
+
+    if (priv->shm_info.shmid == shmid) {
+       shmctl(shmid, IPC_RMID, NULL);
+       priv->shm_info.shmid = -1;
+    }
+}
+
+static void qemu_screen_handle_cmd(QemuScreen *obj, const char *line)
+{
+    QemuScreenPriv *priv = obj->priv;
+    int width, height, x, y, shmid;
+
+    if (sscanf(line, "UPDATE %d, %d, %d, %d\n",
+                     &x, &y, &width, &height) == 4) {
+       qemu_screen_update(obj, x, y, width, height);
+    } else if (sscanf(line, "RESIZE %d, %d\n", &width, &height) == 2) {
+       qemu_screen_resize(obj, width, height);
+    } else if (sscanf(line, "ATTACHED %d\n", &shmid) == 1) {
+       qemu_screen_attached(obj, shmid);
+    } else if (startswith(line, "MOUSE-ABSOLUTE")) {
+       priv->relative_pointer = FALSE;
+       priv->x_offset = 0;
+       priv->y_offset = 0;
+    } else if (startswith(line, "MOUSE-RELATIVE")) {
+       priv->relative_pointer = TRUE;
+    } else if (startswith(line, "ERROR")) {
+       g_warning(line + 6);
+    }
+}
+
+static gboolean qemu_screen_read(GIOChannel *src, GIOCondition cond,
+                                gpointer data)
+{
+    QemuScreen *obj = data;
+    QemuScreenPriv *priv = obj->priv;
+    ssize_t len;
+    char *nl;
+
+    len = read(priv->fd,
+              priv->line + priv->offset,
+              sizeof(priv->line) - priv->offset - 1);
+    if (len == 0) {
+       qemu_screen_detach(obj);
+       return FALSE;
+    }
+    
+    if (len == -1) {
+       if (errno == EINTR || errno == EAGAIN)
+           return TRUE;
+       qemu_screen_detach(obj);
+       return FALSE;
+    }
+    
+    priv->offset += len;
+    priv->line[priv->offset] = 0;
+    
+    while ((nl = strchr(priv->line, '\n'))) {
+       *nl = 0;
+       qemu_screen_handle_cmd(obj, priv->line);
+       len = priv->offset - (nl - priv->line);
+       memmove(priv->line, nl + 1, len - 1);
+       priv->offset = len - 1;
+    }
+    
+    if (priv->offset == sizeof(priv->line) - 1) {
+       g_warning("QemuScreen: buffer full");
+       qemu_screen_detach(obj);
+       return FALSE;
+    }
+    
+    return TRUE;
+}
+
+GtkWidget *qemu_screen_new(void)
+{
+    return GTK_WIDGET(g_object_new(QEMU_TYPE_SCREEN, NULL));
+}
+
+void qemu_screen_detach(QemuScreen *obj)
+{
+    QemuScreenPriv *priv = obj->priv;
+
+    if (priv->ximage) {
+       XDestroyImage(priv->ximage);
+       priv->ximage = NULL;
+    }
+
+    if (priv->shm_info.shmaddr != (void *)-1) {
+       shmdt(priv->shm_info.shmaddr);
+       priv->shm_info.shmaddr = (void *)-1;
+    }
+
+    if (priv->shm_info.shmid != -1) {
+       shmctl(priv->shm_info.shmid, IPC_RMID, NULL);
+       priv->shm_info.shmid = -1;
+    }
+
+    if (priv->id != -1) {
+       g_source_remove(priv->id);
+       priv->id = -1;
+    }
+
+    if (priv->channel) {
+       g_io_channel_unref(priv->channel);
+       priv->channel = NULL;
+    }
+
+    if (priv->fd != -1) {
+       close(priv->fd);
+       priv->fd = -1;
+    }
+
+    obj->priv->report_pointer = FALSE;
+    obj->priv->relative_pointer = TRUE;
+}
+
+gboolean qemu_screen_attach(QemuScreen *obj, int fd)
+{
+    QemuScreenPriv *priv = obj->priv;
+
+    priv->fd = fd;
+    if (priv->fd == -1)
+       return FALSE;
+
+    priv->channel = g_io_channel_unix_new(priv->fd);
+    priv->id = g_io_add_watch(priv->channel, G_IO_IN, qemu_screen_read, obj);
+
+    return TRUE;
+}
+
+gboolean qemu_screen_enable_pointer(QemuScreen *obj, gboolean enable)
+{
+    QemuScreenPriv *priv = obj->priv;
+    gboolean ret;
+    GdkCursor *cursor = NULL;
+
+    if (enable != priv->report_pointer) {
+       priv->x_offset = 0;
+       priv->y_offset = 0;
+    }
+
+    ret = priv->report_pointer;
+    priv->report_pointer = enable;
+
+    if (enable)
+       cursor = priv->null_cursor;
+
+    gdk_window_set_cursor(GTK_WIDGET(obj)->window, cursor);
+
+    return ret;
+}
+
+gboolean qemu_screen_pointer_enabled(QemuScreen *obj)
+{
+    QemuScreenPriv *priv = obj->priv;
+
+    return priv->report_pointer;
+}
+
+gboolean qemu_screen_relative_pointer(QemuScreen *obj)
+{
+    QemuScreenPriv *priv = obj->priv;
+
+    return priv->relative_pointer;
+}
+
+void qemu_screen_warp_pointer(QemuScreen *obj, int x, int y)
+{
+    QemuScreenPriv *priv = obj->priv;
+    GdkDisplay *dpy = gdk_display_get_default();
+    GdkScreen *screen;
+    int dx, dy;
+    GdkModifierType mask;
+
+    gdk_display_get_pointer(dpy, &screen, &dx, &dy, &mask);
+
+    if (priv->relative_pointer) {
+       priv->x_offset += (dx - x);
+       priv->y_offset += (dy - y);
+       gdk_display_warp_pointer(dpy, screen, x, y);
+    }
+}
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/qemu-screen.h
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/qemu-screen.h Sun Jul 16 17:47:31 2006
@@ -0,0 +1,65 @@
+/*
+ * qemu-screen.h -- QEMU GTK Widget
+ *
+ *  Copyright (C) 2006 Anthony Liguori <address@hidden>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License.  See the file COPYING.lib in the main directory of this
+ * archive for more details.
+ */
+
+#ifndef _QEMU_SCREEN_H_
+#define _QEMU_SCREEN_H_
+
+typedef struct _QemuScreen QemuScreen;
+typedef struct _QemuScreenClass QemuScreenClass;
+typedef struct _QemuScreenPriv QemuScreenPriv;
+
+#include <gtk/gtkdrawingarea.h>
+
+#define QEMU_TYPE_SCREEN (qemu_screen_get_type())
+
+#define QEMU_SCREEN(obj) \
+        (G_TYPE_CHECK_INSTANCE_CAST((obj), QEMU_TYPE_SCREEN, QemuScreen))
+
+#define QEMU_SCREEN_CLASS(klass) \
+        (G_TYPE_CHECK_CLASS_CAST((klass), QEMU_TYPE_SCREEN, QemuScreenClass))
+
+#define QEMU_IS_SCREEN(obj) \
+        (G_TYPE_CHECK_INSTANCE_TYPE((obj), QEMU_TYPE_SCREEN))
+
+#define QEMU_IS_SCREEN_CLASS(klass) \
+        (G_TYPE_CHECK_CLASS_TYPE((klass), QEMU_TYPE_SCREEN))
+
+#define QEMU_SCREEN_GET_CLASS(obj) \
+        (G_TYPE_INSTANCE_GET_CLASS((obj), QEMU_TYPE_SCREEN, QemuScreenClass))
+
+struct _QemuScreen
+{
+    GtkDrawingArea parent;
+
+    QemuScreenPriv *priv;
+};
+
+struct _QemuScreenClass
+{
+    GtkDrawingAreaClass parent;
+};
+
+G_BEGIN_DECLS
+
+GType          qemu_screen_get_type(void);
+GtkWidget *    qemu_screen_new(void);
+
+gboolean       qemu_screen_attach(QemuScreen *obj, int fd);
+void           qemu_screen_detach(QemuScreen *obj);
+
+gboolean       qemu_screen_enable_pointer(QemuScreen *obj, gboolean enable);
+gboolean       qemu_screen_pointer_enabled(QemuScreen *obj);
+gboolean       qemu_screen_relative_pointer(QemuScreen *obj);
+
+void           qemu_screen_warp_pointer(QemuScreen *obj, int x, int y);
+
+G_END_DECLS
+
+#endif
diff -r 161ce6660801 -r 1f7d5a9d2623 gtk/qemu.override
--- /dev/null   Sun Jul 16 17:46:42 2006
+++ b/gtk/qemu.override Sun Jul 16 17:47:31 2006
@@ -0,0 +1,13 @@
+%%
+headers
+#include <Python.h>
+#include "pygobject.h"
+#include "qemu-screen.h"
+%%
+modulename qemu
+%%
+import gtk.DrawingArea as PyGtkDrawingArea_Type
+%%
+ignore-glob
+  *_get_type
+%%
\ No newline at end of file

reply via email to

[Prev in Thread] Current Thread [Next in Thread]