All of lore.kernel.org
 help / color / mirror / Atom feed
From: Anthony Liguori <anthony@codemonkey.ws>
To: qemu-devel@nongnu.org
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	[thread overview]
Message-ID: <44BA8254.7010909@codemonkey.ws> (raw)

[-- Attachment #1: Type: text/plain, Size: 148 bytes --]

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

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

Regards,

Anthony Liguori

[-- Attachment #2: gtk-qemu.diff --]
[-- Type: text/x-patch, Size: 34461 bytes --]

# HG changeset patch
# User anthony@localhost.localdomain
# 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 <anthony@codemonkey.ws>
+#
+# 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 <anthony@codemonkey.ws>
+#
+# 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 <anthony@codemonkey.ws>
+ *
+ * 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 <anthony@codemonkey.ws>
+ *
+ * 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 <anthony@codemonkey.ws>
+ *
+ * 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 <anthony@codemonkey.ws>
+ *
+ * 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	other threads:[~2006-07-16 18:20 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=44BA8254.7010909@codemonkey.ws \
    --to=anthony@codemonkey.ws \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.