summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2010-08-25 18:18:34 +0200
committerMarc-André Lureau <marcandre.lureau@gmail.com>2010-11-23 17:09:45 +0100
commit75c1a7dc1a7b1ec16515912f6e0618b7e820249f (patch)
treeeb8863a70a2f8e1d0fdf896fbbd6820223a6de25
parentd960229a091a7bd5b3ce4a29ae5f5648978af51c (diff)
Add glib objects + gtk widgets for spice.
-rw-r--r--gtk/.gitignore11
-rw-r--r--gtk/Makefile.am253
-rw-r--r--gtk/README50
-rw-r--r--gtk/channel-base.c59
-rw-r--r--gtk/channel-cursor.c329
-rw-r--r--gtk/channel-cursor.h42
-rw-r--r--gtk/channel-display-mjpeg.c102
-rw-r--r--gtk/channel-display-priv.h48
-rw-r--r--gtk/channel-display.c680
-rw-r--r--gtk/channel-display.h35
-rw-r--r--gtk/channel-inputs.c250
-rw-r--r--gtk/channel-inputs.h46
-rw-r--r--gtk/channel-main.c345
-rw-r--r--gtk/channel-main.h39
-rw-r--r--gtk/channel-playback.c178
-rw-r--r--gtk/channel-playback.h41
-rw-r--r--gtk/channel-record.c0
-rw-r--r--gtk/channel-record.h0
-rw-r--r--gtk/decode-glz-tmpl.c336
-rw-r--r--gtk/decode-glz.c402
-rw-r--r--gtk/decode-jpeg.c1
-rw-r--r--gtk/decode-zlib.c1
-rw-r--r--gtk/decode.h9
-rwxr-xr-xgtk/keymap-gen.pl202
-rw-r--r--gtk/keymaps.csv461
-rw-r--r--gtk/snappy.c172
-rw-r--r--gtk/spice-channel-cache.h109
-rw-r--r--gtk/spice-channel-priv.h54
-rw-r--r--gtk/spice-channel.c897
-rw-r--r--gtk/spice-channel.h109
-rw-r--r--gtk/spice-client.h39
-rw-r--r--gtk/spice-common.h17
-rw-r--r--gtk/spice-events.c64
-rw-r--r--gtk/spice-events.h19
-rw-r--r--gtk/spice-marshal.txt6
-rw-r--r--gtk/spice-pulse.c183
-rw-r--r--gtk/spice-pulse.h38
-rw-r--r--gtk/spice-session-priv.h11
-rw-r--r--gtk/spice-session.c374
-rw-r--r--gtk/spice-session.h46
-rw-r--r--gtk/spice-types.h17
-rw-r--r--gtk/spice-widget.c1045
-rw-r--r--gtk/spice-widget.h39
-rw-r--r--gtk/spicy.c498
-rw-r--r--gtk/tcp.c173
-rw-r--r--gtk/tcp.h11
-rw-r--r--gtk/vncdisplaykeymap.c245
-rw-r--r--gtk/vncdisplaykeymap.h35
48 files changed, 8121 insertions, 0 deletions
diff --git a/gtk/.gitignore b/gtk/.gitignore
new file mode 100644
index 0000000..e60ccfe
--- /dev/null
+++ b/gtk/.gitignore
@@ -0,0 +1,11 @@
+*.o
+*.lo
+*.la
+.libs
+.deps
+generated_*marshallers*.c
+spice-marshal.c
+spice-marshal.h
+vncdisplaykeymap_*.c
+snappy
+spicy
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
new file mode 100644
index 0000000..cc8cb75
--- /dev/null
+++ b/gtk/Makefile.am
@@ -0,0 +1,253 @@
+NULL =
+
+COMMON_DIR=$(SPICE_COMMON_SRCDIR)
+CLIENT_DIR=$(top_srcdir)/client
+
+EXTRA_DIST = spice-marshall.txt keymap-gen.pl keymaps.csv
+
+bin_PROGRAMS = spicy snappy
+lib_LTLIBRARIES = \
+ libspice-client-gtk.la \
+ libspice-client-pulse.la \
+ libspice-client-glib.la
+
+KEYMAP_GEN = $(srcdir)/keymap-gen.pl
+
+KEYMAPS = \
+ vncdisplaykeymap_xorgevdev2xtkbd.c \
+ vncdisplaykeymap_xorgkbd2xtkbd.c \
+ vncdisplaykeymap_xorgxquartz2xtkbd.c \
+ vncdisplaykeymap_xorgxwin2xtkbd.c \
+ vncdisplaykeymap_osx2xtkbd.c \
+ vncdisplaykeymap_win322xtkbd.c
+
+WARN = -Wall -Wno-sign-compare -Wmissing-prototypes -Wpointer-arith -Wunused
+
+INCLUDES = \
+ $(WARN) \
+ -DSW_CANVAS_CACHE \
+ \
+ -I$(COMMON_DIR) \
+ -I$(CLIENT_DIR) \
+ -I$(CLIENT_DIR)/x11 \
+ \
+ $(PROTOCOL_CFLAGS) \
+ $(PIXMAN_CFLAGS) \
+ $(CELT051_CFLAGS) \
+ $(PULSE_CFLAGS) \
+ $(GTK2_CFLAGS) \
+ $(GLIB2_CFLAGS) \
+ $(GOBJECT2_CFLAGS) \
+ $(SSL_CFLAGS) \
+ $(NULL)
+
+
+libspice_client_gtk_la_LDFLAGS = \
+ -version-number 0:0:1 \
+ -no-undefined \
+ $(NULL)
+
+libspice_client_gtk_la_LIBADD = \
+ $(GTK2_LIBS) \
+ $(NULL)
+
+libspice_client_gtk_la_SOURCES = \
+ spice-widget.c \
+ spice-widget.h \
+ vncdisplaykeymap.c \
+ vncdisplaykeymap.h \
+ $(NULL)
+
+libspice_client_gtkincludedir = $(includedir)/spice-client
+libspice_client_gtkinclude_HEADERS = \
+ spice-widget.h \
+ $(NULL)
+
+
+libspice_client_pulse_la_LDFLAGS = \
+ -version-number 0:0:1 \
+ -no-undefined \
+ $(NULL)
+
+libspice_client_pulse_la_LIBADD = \
+ $(PULSE_LIBS) \
+ $(NULL)
+
+libspice_client_pulse_la_SOURCES = \
+ spice-pulse.c \
+ spice-pulse.h \
+ $(NULL)
+
+libspice_client_pulseincludedir = $(includedir)/spice-client
+libspice_client_pulseinclude_HEADERS = \
+ spice-pulse.h \
+ $(NULL)
+
+
+libspice_client_glib_la_LDFLAGS = \
+ -version-number 0:0:1 \
+ -no-undefined \
+ $(NULL)
+
+libspice_client_glib_la_LIBADD = \
+ $(GLIB2_LIBS) \
+ $(GOBJECT2_LIBS) \
+ $(CELT051_LIBS) \
+ $(JPEG_LIBS) \
+ $(Z_LIBS) \
+ $(PIXMAN_LIBS) \
+ $(SSL_LIBS) \
+ $(NULL)
+
+libspice_client_glib_la_SOURCES = \
+ spice-client.h \
+ spice-common.h \
+ \
+ spice-events.c \
+ spice-events.h \
+ spice-session.c \
+ spice-session.h \
+ spice-channel.c \
+ spice-channel.h \
+ spice-channel-priv.h \
+ tcp.c \
+ tcp.h \
+ spice-marshal.c \
+ spice-marshal.h \
+ generated_demarshallers.c \
+ generated_demarshallers1.c \
+ generated_marshallers.c \
+ generated_marshallers1.c \
+ \
+ channel-base.c \
+ channel-main.h \
+ channel-main.c \
+ channel-display.h \
+ channel-display.c \
+ channel-display-mjpeg.c \
+ channel-cursor.h \
+ channel-cursor.c \
+ channel-inputs.h \
+ channel-inputs.c \
+ channel-playback.h \
+ channel-playback.c \
+ channel-record.h \
+ channel-record.c \
+ \
+ decode.h \
+ decode-glz.c \
+ decode-jpeg.c \
+ decode-zlib.c \
+ \
+ $(COMMON_DIR)/mem.c \
+ $(COMMON_DIR)/mem.h \
+ $(COMMON_DIR)/marshaller.c \
+ $(COMMON_DIR)/marshaller.h \
+ $(COMMON_DIR)/canvas_utils.c \
+ $(COMMON_DIR)/canvas_utils.h \
+ $(COMMON_DIR)/sw_canvas.c \
+ $(COMMON_DIR)/sw_canvas.h \
+ $(COMMON_DIR)/pixman_utils.c \
+ $(COMMON_DIR)/pixman_utils.h \
+ $(COMMON_DIR)/lines.c \
+ $(COMMON_DIR)/lines.h \
+ $(COMMON_DIR)/rop3.c \
+ $(COMMON_DIR)/rop3.h \
+ $(COMMON_DIR)/quic.c \
+ $(COMMON_DIR)/quic.h \
+ $(COMMON_DIR)/lz.c \
+ $(COMMON_DIR)/lz.h \
+ $(NULL)
+
+libspice_client_gtkincludedir = $(includedir)/spice-client
+libspice_client_gtkinclude_HEADERS = \
+ spice-client.h \
+ spice-types.h \
+ spice-session.h \
+ spice-channel.h \
+ channel-main.h \
+ channel-display.h \
+ channel-cursor.h \
+ channel-inputs.h \
+ channel-playback.h \
+ channel-record.h \
+ $(NULL)
+
+
+spicy_SOURCES = \
+ spicy.c \
+ $(NULL)
+
+spicy_DEPENDENCIES = \
+ $(lib_LTLIBRARIES) \
+ $(NULL)
+
+spicy_LDFLAGS = \
+ -lspice-client-gtk \
+ -lspice-client-pulse \
+ -lspice-client-glib \
+ $(NULL)
+
+
+snappy_SOURCES = \
+ snappy.c \
+ $(NULL)
+
+snappy_DEPENDENCIES = \
+ $(lib_LTLIBRARIES) \
+ $(NULL)
+
+snappy_LDFLAGS = \
+ -lspice-client-glib \
+ $(NULL)
+
+
+spice-channel.c: spice-marshal.h
+
+spice-marshal.c: spice-marshal.txt
+ $(AM_V_GEN)echo "#include \"spice-marshal.h\"" > $@ && \
+ glib-genmarshal --body $< >> $@ || (rm -f $@ && exit 1)
+
+spice-marshal.h: spice-marshal.txt
+ $(AM_V_GEN)glib-genmarshal --header $< > $@ || (rm -f $@ && exit 1)
+
+generated_demarshallers.c: $(top_srcdir)/spice.proto
+ $(PYTHON) $(top_srcdir)/spice_codegen.py --generate-demarshallers --client --include messages.h $< $@
+
+generated_demarshallers1.c: $(top_srcdir)/spice1.proto
+ $(PYTHON) $(top_srcdir)/spice_codegen.py --generate-demarshallers --client --include messages.h --prefix 1 --ptrsize 8 $< $@
+
+generated_marshallers.c: $(top_srcdir)/spice.proto
+ $(PYTHON) $(top_srcdir)/spice_codegen.py --generate-marshallers -P --include messages.h --include marshallers.h --client $< $@
+
+generated_marshallers1.c: $(top_srcdir)/spice1.proto
+ $(PYTHON) $(top_srcdir)/spice_codegen.py --generate-marshallers -P --include messages.h --include marshallers.h --client --prefix 1 --ptrsize 8 $< $@
+
+vncdisplaykeymap.c: $(KEYMAPS)
+
+$(KEYMAPS): $(KEYMAP_GEN) keymaps.csv
+
+vncdisplaykeymap_xorgevdev2xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgevdev xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgkbd2xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgkbd xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgxquartz2xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxquartz xtkbd > $@ || rm $@
+
+vncdisplaykeymap_xorgxwin2xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv xorgxwin xtkbd > $@ || rm $@
+
+vncdisplaykeymap_osx2xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv osx xtkbd > $@ || rm $@
+
+vncdisplaykeymap_win322xtkbd.c:
+ $(KEYMAP_GEN) $(srcdir)/keymaps.csv win32 xtkbd > $@ || rm $@
+
+BUILT_SOURCES = spice-marshal.c spice-marshal.h \
+ generated_demarshallers.c generated_demarshallers1.c \
+ generated_marshallers.c generated_marshallers1.c \
+ $(KEYMAPS)
+
+CLEANFILES = $(BUILT_SOURCES)
diff --git a/gtk/README b/gtk/README
new file mode 100644
index 0000000..8e00a5b
--- /dev/null
+++ b/gtk/README
@@ -0,0 +1,50 @@
+
+spice & gtk
+===========
+
+Porting spice client to gtk ...
+
+
+What you can find here
+----------------------
+
+libspice-client-glib
+ provides glib objects for spice protocol decoding and surface rendering.
+ * SpiceSession (see spice-session.h).
+ * SpiceChannel (see spice-channel.h).
+ * Various Spice<Type>Channel (see channel-<type>.h).
+
+libspice-client-pulse
+ provides glib object for sound support via pulseaudio.
+ * SpicePulse (see spice-pulse.h)
+
+libspice-client-gtk
+ provides gtk widget to show spice display and accept user input.
+ * SpiceDisplay (see spice-widget.h)
+
+spicy
+ gtk based spice client app. Command line options are simliar
+ to the spicec ones.
+
+snappy
+ Command line tool, connects to spice server and writes out a
+ screen shot.
+
+
+current state
+-------------
+
+spicy app starts becoming usable.
+
+Library API is far from being stable. Likewise the ABI of course.
+If you play with the libs make sure you rebuild everything after
+updating the library for the time being.
+
+Some features are missing:
+ - No sound recording support.
+ - No client migration support.
+ - No mm time handling.
+ - Most channel implementations are incomplete.
+ - Almost no documentation.
+ - Probably more ...
+
diff --git a/gtk/channel-base.c b/gtk/channel-base.c
new file mode 100644
index 0000000..097f012
--- /dev/null
+++ b/gtk/channel-base.c
@@ -0,0 +1,59 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+
+void spice_channel_handle_set_ack(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_channel *c = channel->priv;
+ SpiceMsgSetAck* ack = spice_msg_in_parsed(in);
+ spice_msg_out *out = spice_msg_out_new(channel, SPICE_MSGC_ACK_SYNC);
+ SpiceMsgcAckSync sync = {
+ .generation = ack->generation,
+ };
+
+ c->message_ack_window = c->message_ack_count = ack->window;
+ c->marshallers->msgc_ack_sync(out->marshaller, &sync);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+}
+
+void spice_channel_handle_ping(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_channel *c = channel->priv;
+ SpiceMsgPing *ping = spice_msg_in_parsed(in);
+ spice_msg_out *pong = spice_msg_out_new(channel, SPICE_MSGC_PONG);
+
+ c->marshallers->msgc_pong(pong->marshaller, ping);
+ spice_msg_out_send(pong);
+ spice_msg_out_put(pong);
+}
+
+void spice_channel_handle_notify(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_channel *c = channel->priv;
+ static const char* severity_strings[] = {"info", "warn", "error"};
+ static const char* visibility_strings[] = {"!", "!!", "!!!"};
+
+ SpiceMsgNotify *notify = spice_msg_in_parsed(in);
+ const char *severity = "?";
+ const char *visibility = "?";
+ const char *message_str = NULL;
+
+ if (notify->severity <= SPICE_NOTIFY_SEVERITY_ERROR) {
+ severity = severity_strings[notify->severity];
+ }
+ if (notify->visibilty <= SPICE_NOTIFY_VISIBILITY_HIGH) {
+ visibility = visibility_strings[notify->visibilty];
+ }
+
+ if (notify->message_len &&
+ notify->message_len <= in->dpos - sizeof(*notify)) {
+ message_str = (char*)notify->message;
+ }
+
+ fprintf(stderr, "%s: channel %s -- %s%s #%u%s%.*s\n", __FUNCTION__,
+ c->name, severity, visibility, notify->what,
+ message_str ? ": " : "", notify->message_len,
+ message_str ? message_str : "");
+}
diff --git a/gtk/channel-cursor.c b/gtk/channel-cursor.c
new file mode 100644
index 0000000..4009faa
--- /dev/null
+++ b/gtk/channel-cursor.c
@@ -0,0 +1,329 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-cache.h"
+#include "spice-marshal.h"
+
+#define SPICE_CURSOR_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_CURSOR_CHANNEL, spice_cursor_channel))
+
+typedef struct display_cursor {
+ SpiceCursorHeader hdr;
+ uint8_t data[];
+} display_cursor;
+
+struct spice_cursor_channel {
+ display_cache cursors;
+};
+
+G_DEFINE_TYPE(SpiceCursorChannel, spice_cursor_channel, SPICE_TYPE_CHANNEL)
+
+enum {
+ SPICE_CURSOR_SET,
+ SPICE_CURSOR_MOVE,
+ SPICE_CURSOR_HIDE,
+ SPICE_CURSOR_RESET,
+
+ SPICE_CURSOR_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_CURSOR_LAST_SIGNAL];
+
+static void spice_cursor_handle_msg(SpiceChannel *channel, spice_msg_in *msg);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_cursor_channel_init(SpiceCursorChannel *channel)
+{
+ spice_cursor_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_CURSOR_CHANNEL_GET_PRIVATE(channel);
+ memset(c, 0, sizeof(*c));
+
+ cache_init(&c->cursors, "cursor");
+}
+
+static void spice_cursor_channel_finalize(GObject *obj)
+{
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ if (G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_cursor_channel_parent_class)->finalize(obj);
+}
+
+static void spice_cursor_channel_class_init(SpiceCursorChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->finalize = spice_cursor_channel_finalize;
+ channel_class->handle_msg = spice_cursor_handle_msg;
+
+ signals[SPICE_CURSOR_SET] =
+ g_signal_new("spice-cursor-set",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceCursorChannelClass, spice_cursor_set),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__INT_INT_INT_INT_POINTER,
+ G_TYPE_NONE,
+ 5,
+ G_TYPE_INT, G_TYPE_INT,
+ G_TYPE_INT, G_TYPE_INT,
+ G_TYPE_POINTER);
+
+ signals[SPICE_CURSOR_MOVE] =
+ g_signal_new("spice-cursor-move",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceCursorChannelClass, spice_cursor_move),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__INT_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_INT, G_TYPE_INT);
+
+ signals[SPICE_CURSOR_HIDE] =
+ g_signal_new("spice-cursor-hide",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceCursorChannelClass, spice_cursor_hide),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SPICE_CURSOR_RESET] =
+ g_signal_new("spice-cursor-reset",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceCursorChannelClass, spice_cursor_reset),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private(klass, sizeof(spice_cursor_channel));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void mono_cursor(display_cursor *cursor, uint8_t *data)
+{
+ uint8_t *xor, *and, *dest;
+ int bpl, x, y, bit;
+
+ bpl = (cursor->hdr.width + 7) / 8;
+ and = data;
+ xor = and + bpl * cursor->hdr.height;
+ dest = cursor->data;
+ for (y = 0; y < cursor->hdr.height; y++) {
+ bit = 0x80;
+ for (x = 0; x < cursor->hdr.width; x++, dest += 4) {
+ if (and[x/8] & bit) {
+ if (xor[x/8] & bit) {
+ /* flip -> hmm? */
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0x80;
+ } else {
+ /* unchanged -> transparent */
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0x00;
+ }
+ } else {
+ if (xor[x/8] & bit) {
+ /* set -> white */
+ dest[0] = 0xff;
+ dest[1] = 0xff;
+ dest[2] = 0xff;
+ dest[3] = 0xff;
+ } else {
+ /* clear -> black */
+ dest[0] = 0x00;
+ dest[1] = 0x00;
+ dest[2] = 0x00;
+ dest[3] = 0xff;
+ }
+ }
+ bit >>= 1;
+ if (bit == 0) {
+ bit = 0x80;
+ }
+ }
+ and += bpl;
+ xor += bpl;
+ }
+}
+
+static display_cursor *set_cursor(SpiceChannel *channel, SpiceCursor *scursor)
+{
+ spice_cursor_channel *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+ SpiceCursorHeader *hdr = &scursor->header;
+ display_cache_item *item;
+ display_cursor *cursor;
+ size_t size;
+
+#if 0
+ fprintf(stderr, "%s: type %d, %" PRIx64 ", %dx%d, flags %d, size %d\n",
+ __FUNCTION__, hdr->type, hdr->unique, hdr->width, hdr->height,
+ scursor->flags, scursor->data_size);
+#endif
+
+ if (scursor->flags & SPICE_CURSOR_FLAGS_FROM_CACHE) {
+ item = cache_find(&c->cursors, hdr->unique);
+ if (!item) {
+ return NULL;
+ }
+ return item->ptr;
+ }
+ if (scursor->data_size == 0) {
+ return NULL;
+ }
+
+ size = 4 * hdr->width * hdr->height;
+ cursor = spice_malloc(sizeof(*cursor) + size);
+ cursor->hdr = *hdr;
+
+ switch (hdr->type) {
+ case SPICE_CURSOR_TYPE_MONO:
+ mono_cursor(cursor, scursor->data);
+ break;
+ case SPICE_CURSOR_TYPE_ALPHA:
+ memcpy(cursor->data, scursor->data, size);
+ break;
+ default:
+ fprintf(stderr, "%s: unimplemented cursor type %d\n", __FUNCTION__,
+ hdr->type);
+ free(cursor);
+ cursor = NULL;
+ break;
+ }
+
+ if (cursor && (scursor->flags & SPICE_CURSOR_FLAGS_CACHE_ME)) {
+ item = cache_add(&c->cursors, hdr->unique);
+ item->ptr = cursor;
+ }
+
+ return cursor;
+}
+
+static void delete_cursor_one(SpiceChannel *channel, display_cache_item *item)
+{
+ spice_cursor_channel *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+
+ free(item->ptr);
+ cache_del(&c->cursors, item);
+}
+
+static void delete_cursor_all(SpiceChannel *channel)
+{
+ spice_cursor_channel *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+ display_cache_item *item;
+
+ for (;;) {
+ item = cache_get_lru(&c->cursors);
+ if (item == NULL) {
+ return;
+ }
+ delete_cursor_one(channel, item);
+ }
+}
+
+static void emit_cursor_set(SpiceChannel *channel, display_cursor *cursor)
+{
+ if (!cursor)
+ return;
+ g_signal_emit(channel, signals[SPICE_CURSOR_SET], 0,
+ cursor->hdr.width, cursor->hdr.height,
+ cursor->hdr.hot_spot_x, cursor->hdr.hot_spot_y,
+ cursor->data);
+}
+
+static void cursor_handle_init(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgCursorInit *init = spice_msg_in_parsed(in);
+ display_cursor *cursor;
+
+ delete_cursor_all(channel);
+ cursor = set_cursor(channel, &init->cursor);
+ emit_cursor_set(channel, cursor);
+}
+
+static void cursor_handle_reset(SpiceChannel *channel, spice_msg_in *in)
+{
+ delete_cursor_all(channel);
+ g_signal_emit(channel, signals[SPICE_CURSOR_RESET], 0);
+}
+
+static void cursor_handle_set(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgCursorSet *set = spice_msg_in_parsed(in);
+ display_cursor *cursor;
+
+ cursor = set_cursor(channel, &set->cursor);
+ emit_cursor_set(channel, cursor);
+}
+
+static void cursor_handle_move(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgCursorMove *move = spice_msg_in_parsed(in);
+
+ g_signal_emit(channel, signals[SPICE_CURSOR_MOVE], 0,
+ move->position.x, move->position.y);
+}
+
+static void cursor_handle_hide(SpiceChannel *channel, spice_msg_in *in)
+{
+ g_signal_emit(channel, signals[SPICE_CURSOR_HIDE], 0);
+}
+
+static void cursor_handle_trail(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void cursor_handle_inval_one(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_cursor_channel *c = SPICE_CURSOR_CHANNEL(channel)->priv;
+ SpiceMsgDisplayInvalOne *zap = spice_msg_in_parsed(in);
+ display_cache_item *item;
+
+ item = cache_find(&c->cursors, zap->id);
+ delete_cursor_one(channel, item);
+}
+
+static void cursor_handle_inval_all(SpiceChannel *channel, spice_msg_in *in)
+{
+ delete_cursor_all(channel);
+}
+
+static spice_msg_handler cursor_handlers[] = {
+ [ SPICE_MSG_SET_ACK ] = spice_channel_handle_set_ack,
+ [ SPICE_MSG_PING ] = spice_channel_handle_ping,
+ [ SPICE_MSG_NOTIFY ] = spice_channel_handle_notify,
+
+ [ SPICE_MSG_CURSOR_INIT ] = cursor_handle_init,
+ [ SPICE_MSG_CURSOR_RESET ] = cursor_handle_reset,
+ [ SPICE_MSG_CURSOR_SET ] = cursor_handle_set,
+ [ SPICE_MSG_CURSOR_MOVE ] = cursor_handle_move,
+ [ SPICE_MSG_CURSOR_HIDE ] = cursor_handle_hide,
+ [ SPICE_MSG_CURSOR_TRAIL ] = cursor_handle_trail,
+ [ SPICE_MSG_CURSOR_INVAL_ONE ] = cursor_handle_inval_one,
+ [ SPICE_MSG_CURSOR_INVAL_ALL ] = cursor_handle_inval_all,
+};
+
+static void spice_cursor_handle_msg(SpiceChannel *channel, spice_msg_in *msg)
+{
+ int type = spice_msg_in_type(msg);
+ assert(type < SPICE_N_ELEMENTS(cursor_handlers));
+ assert(cursor_handlers[type] != NULL);
+ cursor_handlers[type](channel, msg);
+}
diff --git a/gtk/channel-cursor.h b/gtk/channel-cursor.h
new file mode 100644
index 0000000..d6af6ab
--- /dev/null
+++ b/gtk/channel-cursor.h
@@ -0,0 +1,42 @@
+#ifndef __SPICE_CLIENT_CURSOR_CHANNEL_H__
+#define __SPICE_CLIENT_CURSOR_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_CURSOR_CHANNEL (spice_cursor_channel_get_type())
+#define SPICE_CURSOR_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannel))
+#define SPICE_CURSOR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
+#define SPICE_IS_CURSOR_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_CURSOR_CHANNEL))
+#define SPICE_IS_CURSOR_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_CURSOR_CHANNEL))
+#define SPICE_CURSOR_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_CURSOR_CHANNEL, SpiceCursorChannelClass))
+
+typedef struct _SpiceCursorChannel SpiceCursorChannel;
+typedef struct _SpiceCursorChannelClass SpiceCursorChannelClass;
+typedef struct spice_cursor_channel spice_cursor_channel;
+
+struct _SpiceCursorChannel {
+ SpiceChannel parent;
+ spice_cursor_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceCursorChannelClass {
+ SpiceChannelClass parent_class;
+
+ /* signals */
+ void (*spice_cursor_set)(SpiceCursorChannel *channel, gint width, gint height,
+ gint hot_x, gint hot_y, gpointer rgba);
+ void (*spice_cursor_move)(SpiceCursorChannel *channel, gint x, gint y);
+ void (*spice_cursor_hide)(SpiceCursorChannel *channel);
+ void (*spice_cursor_reset)(SpiceCursorChannel *channel);
+
+ /* Do not add fields to this struct */
+};
+
+GType spice_cursor_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_CURSOR_CHANNEL_H__ */
diff --git a/gtk/channel-display-mjpeg.c b/gtk/channel-display-mjpeg.c
new file mode 100644
index 0000000..db3ee67
--- /dev/null
+++ b/gtk/channel-display-mjpeg.c
@@ -0,0 +1,102 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "channel-display-priv.h"
+
+static void mjpeg_src_init(struct jpeg_decompress_struct *cinfo)
+{
+ display_stream *st = SPICE_CONTAINEROF(cinfo->src, display_stream, mjpeg_src);
+ SpiceMsgDisplayStreamData *data = spice_msg_in_parsed(st->msg_data);
+
+ cinfo->src->next_input_byte = data->data;
+ cinfo->src->bytes_in_buffer = data->data_size;
+}
+
+static int mjpeg_src_fill(struct jpeg_decompress_struct *cinfo)
+{
+ PANIC("need more input data");
+}
+
+static void mjpeg_src_skip(struct jpeg_decompress_struct *cinfo,
+ long num_bytes)
+{
+ cinfo->src->next_input_byte += num_bytes;
+}
+
+static void mjpeg_src_term(struct jpeg_decompress_struct *cinfo)
+{
+ /* nothing */
+}
+
+void stream_mjpeg_init(display_stream *st)
+{
+ st->mjpeg_cinfo.err = jpeg_std_error(&st->mjpeg_jerr);
+ jpeg_create_decompress(&st->mjpeg_cinfo);
+
+ st->mjpeg_src.init_source = mjpeg_src_init;
+ st->mjpeg_src.fill_input_buffer = mjpeg_src_fill;
+ st->mjpeg_src.skip_input_data = mjpeg_src_skip;
+ st->mjpeg_src.resync_to_restart = jpeg_resync_to_restart;
+ st->mjpeg_src.term_source = mjpeg_src_term;
+ st->mjpeg_cinfo.src = &st->mjpeg_src;
+}
+
+static void mjpeg_convert_scanline(uint8_t *dest, uint8_t *src, int width, int compat)
+{
+ uint32_t *row = (void*)dest;
+ uint32_t c;
+ int x;
+
+ if (compat) {
+ /*
+ * We need to check for the old major and for backwards compat
+ * a) swap r and b (done)
+ * b) to-yuv with right values and then from-yuv with old wrong values (TODO)
+ */
+ for (x = 0; x < width; x++) {
+ c = src[2] << 16 | src[1] << 8 | src[0];
+ src += 3;
+ *row++ = c;
+ }
+ } else {
+ for (x = 0; x < width; x++) {
+ c = src[0] << 16 | src[1] << 8 | src[2];
+ src += 3;
+ *row++ = c;
+ }
+ }
+}
+
+void stream_mjpeg_data(display_stream *st)
+{
+ SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
+ int width = info->stream_width;
+ int height = info->stream_height;
+ uint8_t *line, *dest;
+ int i;
+
+ line = malloc(width * 4);
+ dest = malloc(width * height * 4);
+
+ if (st->out_frame) {
+ free(st->out_frame);
+ }
+ st->out_frame = dest;
+
+ jpeg_read_header(&st->mjpeg_cinfo, 1);
+ st->mjpeg_cinfo.out_color_space = JCS_RGB;
+ jpeg_start_decompress(&st->mjpeg_cinfo);
+ for (i = 0; i < height; i++) {
+ jpeg_read_scanlines(&st->mjpeg_cinfo, &line, 1);
+ mjpeg_convert_scanline(dest, line, width, 0 /* FIXME: compat */);
+ dest += 4 * width;
+ }
+ jpeg_finish_decompress(&st->mjpeg_cinfo);
+
+ free(line);
+}
+
+void stream_mjpeg_cleanup(display_stream *st)
+{
+ jpeg_destroy_decompress(&st->mjpeg_cinfo);
+}
diff --git a/gtk/channel-display-priv.h b/gtk/channel-display-priv.h
new file mode 100644
index 0000000..76e6b8c
--- /dev/null
+++ b/gtk/channel-display-priv.h
@@ -0,0 +1,48 @@
+#include <pixman.h>
+#include <jpeglib.h>
+
+/* spice/common */
+#include "canvas_base.h"
+#include "canvas_utils.h"
+#include "sw_canvas.h"
+#include "ring.h"
+#include "quic.h"
+#include "rop3.h"
+
+#define DISPLAY_PIXMAP_CACHE (1024 * 1024 * 32)
+#define GLZ_WINDOW_SIZE (1024 * 1024 * 16)
+
+typedef struct display_surface {
+ RingItem link;
+ int surface_id;
+ bool primary;
+ enum SpiceSurfaceFmt format;
+ int width, height, stride, size;
+ int shmid;
+ uint8_t *data;
+ SpiceCanvas *canvas;
+ SpiceGlzDecoder *glz_decoder;
+} display_surface;
+
+typedef struct display_stream {
+ spice_msg_in *msg_create;
+ spice_msg_in *msg_clip;
+ spice_msg_in *msg_data;
+
+ /* from messages */
+ display_surface *surface;
+ SpiceClip *clip;
+ int codec;
+
+ /* mjpeg decoder */
+ struct jpeg_source_mgr mjpeg_src;
+ struct jpeg_decompress_struct mjpeg_cinfo;
+ struct jpeg_error_mgr mjpeg_jerr;
+
+ uint8_t *out_frame;
+} display_stream;
+
+/* channel-display-mjpeg.c */
+void stream_mjpeg_init(display_stream *st);
+void stream_mjpeg_data(display_stream *st);
+void stream_mjpeg_cleanup(display_stream *st);
diff --git a/gtk/channel-display.c b/gtk/channel-display.c
new file mode 100644
index 0000000..2e3a061
--- /dev/null
+++ b/gtk/channel-display.c
@@ -0,0 +1,680 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-marshal.h"
+#include "spice-channel-cache.h"
+#include "channel-display-priv.h"
+#include "decode.h"
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#define SPICE_DISPLAY_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY_CHANNEL, spice_display_channel))
+
+struct spice_display_channel {
+ Ring surfaces;
+ display_cache images;
+ display_cache palettes;
+ SpiceImageCache image_cache;
+ SpicePaletteCache palette_cache;
+ SpiceGlzDecoderWindow *glz_window;
+ display_stream *streams[128];
+};
+
+G_DEFINE_TYPE(SpiceDisplayChannel, spice_display_channel, SPICE_TYPE_CHANNEL)
+
+enum {
+ SPICE_DISPLAY_PRIMARY_CREATE,
+ SPICE_DISPLAY_PRIMARY_DESTROY,
+ SPICE_DISPLAY_INVALIDATE,
+
+ SPICE_DISPLAY_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
+
+static void spice_display_handle_msg(SpiceChannel *channel, spice_msg_in *msg);
+static void spice_display_channel_init(SpiceDisplayChannel *channel);
+static void spice_display_channel_up(SpiceChannel *channel);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_display_channel_finalize(GObject *obj)
+{
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ if (G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_display_channel_parent_class)->finalize(obj);
+}
+
+static void spice_display_channel_class_init(SpiceDisplayChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->finalize = spice_display_channel_finalize;
+ channel_class->handle_msg = spice_display_handle_msg;
+ channel_class->channel_up = spice_display_channel_up;
+
+ signals[SPICE_DISPLAY_PRIMARY_CREATE] =
+ g_signal_new("spice-display-primary-create",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceChannelClass, spice_display_primary_create),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__INT_INT_INT_INT_INT_POINTER,
+ G_TYPE_NONE,
+ 6,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_POINTER);
+
+ signals[SPICE_DISPLAY_PRIMARY_DESTROY] =
+ g_signal_new("spice-display-primary-destroy",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceChannelClass, spice_display_primary_destroy),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SPICE_DISPLAY_INVALIDATE] =
+ g_signal_new("spice-display-invalidate",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceChannelClass, spice_display_invalidate),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__INT_INT_INT_INT,
+ G_TYPE_NONE,
+ 4,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+ g_type_class_add_private(klass, sizeof(spice_display_channel));
+
+ sw_canvas_init();
+ quic_init();
+ rop3_init();
+}
+
+/* ------------------------------------------------------------------ */
+
+static void image_put(SpiceImageCache *cache, uint64_t id, pixman_image_t *image)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, image_cache);
+ display_cache_item *item;
+
+ if (c->images.nitems == 256) {
+ item = cache_get_lru(&c->images);
+ pixman_image_unref(item->ptr);
+ cache_del(&c->images, item);
+ }
+
+ item = cache_add(&c->images, id);
+ item->ptr = pixman_image_ref(image);
+}
+
+static pixman_image_t *image_get(SpiceImageCache *cache, uint64_t id)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, image_cache);
+ display_cache_item *item;
+
+ item = cache_find(&c->images, id);
+ if (item) {
+ cache_used(&c->images, item);
+ return pixman_image_ref(item->ptr);
+ }
+ return NULL;
+}
+
+static void palette_put(SpicePaletteCache *cache, SpicePalette *palette)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, palette_cache);
+ display_cache_item *item;
+
+ item = cache_add(&c->palettes, palette->unique);
+ item->ptr = palette;
+}
+
+static SpicePalette *palette_get(SpicePaletteCache *cache, uint64_t id)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, palette_cache);
+ display_cache_item *item;
+
+ item = cache_find(&c->palettes, id);
+ if (item) {
+ cache_ref(item);
+ return item->ptr;
+ }
+ return NULL;
+}
+
+static void palette_remove(SpicePaletteCache *cache, uint32_t id)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, palette_cache);
+ display_cache_item *item;
+
+ item = cache_find(&c->palettes, id);
+ if (item) {
+ if (cache_unref(item)) {
+ cache_del(&c->palettes, item);
+ }
+ }
+}
+
+static void palette_clear(SpicePaletteCache *cache)
+{
+ spice_display_channel *c =
+ SPICE_CONTAINEROF(cache, spice_display_channel, palette_cache);
+ display_cache_item *item;
+
+ for (;;) {
+ item = cache_get_lru(&c->palettes);
+ if (item == NULL) {
+ break;
+ }
+ cache_del(&c->palettes, item);
+ }
+}
+
+static void palette_release(SpicePaletteCache *cache, SpicePalette *palette)
+{
+ palette_remove(cache, palette->unique);
+}
+
+static SpiceImageCacheOps image_cache_ops = {
+ .put = image_put,
+ .get = image_get,
+};
+
+static SpicePaletteCacheOps palette_cache_ops = {
+ .put = palette_put,
+ .get = palette_get,
+ .release = palette_release,
+};
+
+static void spice_display_channel_init(SpiceDisplayChannel *channel)
+{
+ spice_display_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_DISPLAY_CHANNEL_GET_PRIVATE(channel);
+ memset(c, 0, sizeof(*c));
+
+ ring_init(&c->surfaces);
+ cache_init(&c->images, "image");
+ cache_init(&c->palettes, "palette");
+ c->image_cache.ops = &image_cache_ops;
+ c->palette_cache.ops = &palette_cache_ops;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int create_canvas(SpiceChannel *channel, display_surface *surface)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+ if (surface->primary) {
+ surface->shmid = shmget(IPC_PRIVATE, surface->size, IPC_CREAT | 0777);
+ if (surface->shmid >= 0) {
+ surface->data = shmat(surface->shmid, 0, 0);
+ if (surface->data == NULL) {
+ shmctl(surface->shmid, IPC_RMID, 0);
+ surface->shmid = -1;
+ }
+ }
+ } else {
+ surface->shmid = -1;
+ }
+
+ if (surface->shmid == -1) {
+ surface->data = spice_malloc(surface->size);
+ }
+
+ if (!c->glz_window) {
+ c->glz_window = glz_decoder_window_new();
+ }
+ surface->glz_decoder = glz_decoder_new(c->glz_window);
+
+ surface->canvas = canvas_create_for_data(surface->width,
+ surface->height,
+ surface->format,
+ surface->data,
+ surface->stride,
+#ifdef SW_CANVAS_CACHE
+ &c->image_cache,
+ &c->palette_cache,
+#endif
+ NULL, // &csurfaces.base,
+ surface->glz_decoder,
+ NULL, // &jpeg_decoder(),
+ NULL); // &zlib_decoder());
+ ASSERT(surface->canvas != NULL);
+ return 0;
+}
+
+static void destroy_canvas(display_surface *surface)
+{
+ if (surface->shmid == -1) {
+ free(surface->data);
+ } else {
+ shmdt(surface->data);
+ shmctl(surface->shmid, IPC_RMID, 0);
+ }
+ surface->shmid = -1;
+ surface->data = NULL;
+
+ surface->canvas->ops->destroy(surface->canvas);
+ surface->canvas = NULL;
+}
+
+static display_surface *find_surface(SpiceChannel *channel, int surface_id)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ display_surface *surface;
+ RingItem *item;
+
+ for (item = ring_get_head(&c->surfaces);
+ item != NULL;
+ item = ring_next(&c->surfaces, item)) {
+ surface = SPICE_CONTAINEROF(item, display_surface, link);
+ if (surface->surface_id == surface_id)
+ return surface;
+ }
+ return NULL;
+}
+
+static void emit_invalidate(SpiceChannel *channel, SpiceRect *bbox)
+{
+ g_signal_emit(channel, signals[SPICE_DISPLAY_INVALIDATE], 0,
+ bbox->left, bbox->top,
+ bbox->right - bbox->left,
+ bbox->bottom - bbox->top);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void spice_display_channel_up(SpiceChannel *channel)
+{
+ spice_msg_out *out;
+ SpiceMsgcDisplayInit init = {
+ .pixmap_cache_id = 1,
+ .pixmap_cache_size = DISPLAY_PIXMAP_CACHE,
+ .glz_dictionary_id = 1,
+ .glz_dictionary_window_size = GLZ_WINDOW_SIZE,
+ };
+
+ out = spice_msg_out_new(channel, SPICE_MSGC_DISPLAY_INIT);
+ out->marshallers->msgc_display_init(out->marshaller, &init);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+}
+
+#define DRAW(type) { \
+ display_surface *surface = \
+ find_surface(channel, op->base.surface_id); \
+ ASSERT(surface != NULL); \
+ surface->canvas->ops->draw_##type(surface->canvas, &op->base.box, \
+ &op->base.clip, &op->data); \
+ if (surface->primary) { \
+ emit_invalidate(channel, &op->base.box); \
+ } \
+}
+
+static void display_handle_mode(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgDisplayMode *mode = spice_msg_in_parsed(in);
+ display_surface *surface = find_surface(channel, 0);
+
+ if (surface) {
+ g_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
+ ring_remove(&surface->link);
+ destroy_canvas(surface);
+ free(surface);
+ }
+
+ surface = spice_new0(display_surface, 1);
+ surface->format = mode->bits == 32 ?
+ SPICE_SURFACE_FMT_32_xRGB : SPICE_SURFACE_FMT_16_555;
+ surface->width = mode->x_res;
+ surface->height = mode->y_res;
+ surface->stride = surface->width * 4;
+ surface->size = surface->height * surface->stride;
+ surface->primary = true;
+ create_canvas(channel, surface);
+ g_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_CREATE], 0,
+ surface->format, surface->width, surface->height,
+ surface->stride, surface->shmid, surface->data);
+ ring_add(&c->surfaces, &surface->link);
+}
+
+static void display_handle_mark(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void display_handle_reset(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void display_handle_copy_bits(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayCopyBits *op = spice_msg_in_parsed(in);
+ display_surface *surface = find_surface(channel, op->base.surface_id);
+
+ ASSERT(surface != NULL);
+ surface->canvas->ops->copy_bits(surface->canvas, &op->base.box,
+ &op->base.clip, &op->src_pos);
+ if (surface->primary) {
+ emit_invalidate(channel, &op->base.box);
+ }
+}
+
+static void display_handle_inv_list(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void display_handle_inv_pixmap_all(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void display_handle_inv_palette(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgDisplayInvalOne* op = spice_msg_in_parsed(in);
+
+ palette_remove(&c->palette_cache, op->id);
+}
+
+static void display_handle_inv_palette_all(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+
+ palette_clear(&c->palette_cache);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void display_handle_stream_create(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgDisplayStreamCreate *op = spice_msg_in_parsed(in);
+ display_stream *st;
+
+ fprintf(stderr, "%s: id %d\n", __FUNCTION__, op->id);
+
+ assert(op->id < SPICE_N_ELEMENTS(c->streams));
+ assert(c->streams[op->id] == NULL);
+ c->streams[op->id] = spice_new0(display_stream, 1);
+ st = c->streams[op->id];
+
+ st->msg_create = in;
+ spice_msg_in_get(in);
+ st->clip = &op->clip;
+ st->codec = op->codec_type;
+ st->surface = find_surface(channel, op->surface_id);
+
+ switch (st->codec) {
+ case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+ stream_mjpeg_init(st);
+ break;
+ }
+}
+
+static void display_handle_stream_data(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgDisplayStreamData *op = spice_msg_in_parsed(in);
+ display_stream *st = c->streams[op->id];
+
+ st->msg_data = in;
+
+ switch (st->codec) {
+ case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+ stream_mjpeg_data(st);
+ break;
+ }
+
+ if (st->out_frame) {
+ SpiceMsgDisplayStreamCreate *info = spice_msg_in_parsed(st->msg_create);
+ uint8_t *data;
+ int stride;
+
+ data = st->out_frame;
+ stride = info->src_width * sizeof(uint32_t);
+ if (!(info->flags & SPICE_STREAM_FLAGS_TOP_DOWN)) {
+ data += stride * (info->src_height - 1);
+ stride = -stride;
+ }
+ st->surface->canvas->ops->put_image
+ (st->surface->canvas, &info->dest, data,
+ info->src_width, info->src_height, stride,
+ NULL /* FIXME: st->clip */ );
+ if (st->surface->primary)
+ emit_invalidate(channel, &info->dest);
+ }
+
+ st->msg_data = NULL;
+}
+
+static void display_handle_stream_clip(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgDisplayStreamClip *op = spice_msg_in_parsed(in);
+ display_stream *st = c->streams[op->id];
+
+ if (st->msg_clip) {
+ spice_msg_in_put(st->msg_clip);
+ }
+ spice_msg_in_get(in);
+ st->msg_clip = in;
+ st->clip = &op->clip;
+ fprintf(stderr, "%s: TODO (id %d)\n", __FUNCTION__, op->id);
+}
+
+static void destroy_stream(SpiceChannel *channel, int id)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ display_stream *st = c->streams[id];
+
+ if (!st)
+ return;
+
+ switch (st->codec) {
+ case SPICE_VIDEO_CODEC_TYPE_MJPEG:
+ stream_mjpeg_cleanup(st);
+ break;
+ }
+
+ if (st->msg_clip)
+ spice_msg_in_put(st->msg_clip);
+ spice_msg_in_put(st->msg_create);
+ free(st);
+ c->streams[id] = NULL;
+}
+
+static void display_handle_stream_destroy(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayStreamDestroy *op = spice_msg_in_parsed(in);
+
+ fprintf(stderr, "%s: id %d\n", __FUNCTION__, op->id);
+ destroy_stream(channel, op->id);
+}
+
+static void display_handle_stream_destroy_all(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void display_handle_draw_fill(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawFill *op = spice_msg_in_parsed(in);
+ DRAW(fill);
+}
+
+static void display_handle_draw_opaque(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawOpaque *op = spice_msg_in_parsed(in);
+ DRAW(opaque);
+}
+
+static void display_handle_draw_copy(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawCopy *op = spice_msg_in_parsed(in);
+ DRAW(copy);
+}
+
+static void display_handle_draw_blend(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawBlend *op = spice_msg_in_parsed(in);
+ DRAW(blend);
+}
+
+static void display_handle_draw_blackness(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawBlackness *op = spice_msg_in_parsed(in);
+ DRAW(blackness);
+}
+
+static void display_handle_draw_whiteness(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawWhiteness *op = spice_msg_in_parsed(in);
+ DRAW(whiteness);
+}
+
+static void display_handle_draw_invers(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawInvers *op = spice_msg_in_parsed(in);
+ DRAW(invers);
+}
+
+static void display_handle_draw_rop3(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawRop3 *op = spice_msg_in_parsed(in);
+ DRAW(rop3);
+}
+
+static void display_handle_draw_stroke(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawStroke *op = spice_msg_in_parsed(in);
+ DRAW(stroke);
+}
+
+static void display_handle_draw_text(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawText *op = spice_msg_in_parsed(in);
+ DRAW(text);
+}
+
+static void display_handle_draw_transparent(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawTransparent *op = spice_msg_in_parsed(in);
+ DRAW(transparent);
+}
+
+static void display_handle_draw_alpha_blend(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgDisplayDrawAlphaBlend *op = spice_msg_in_parsed(in);
+ DRAW(alpha_blend);
+}
+
+static void display_handle_surface_create(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_display_channel *c = SPICE_DISPLAY_CHANNEL(channel)->priv;
+ SpiceMsgSurfaceCreate *create = spice_msg_in_parsed(in);
+ display_surface *surface = spice_new0(display_surface, 1);
+
+ surface->surface_id = create->surface_id;
+ surface->format = create->format;
+ surface->width = create->width;
+ surface->height = create->height;
+ surface->stride = create->width * 4;
+ surface->size = surface->height * surface->stride;
+
+ if (create->flags == SPICE_SURFACE_FLAGS_PRIMARY) {
+ surface->primary = true;
+ create_canvas(channel, surface);
+ g_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_CREATE], 0,
+ surface->format, surface->width, surface->height,
+ surface->stride, surface->shmid, surface->data);
+ } else {
+ surface->primary = false;
+ create_canvas(channel, surface);
+ }
+
+ ring_add(&c->surfaces, &surface->link);
+}
+
+static void display_handle_surface_destroy(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgSurfaceDestroy *destroy = spice_msg_in_parsed(in);
+ display_surface *surface = find_surface(channel, destroy->surface_id);
+
+ if (surface->primary) {
+ g_signal_emit(channel, signals[SPICE_DISPLAY_PRIMARY_DESTROY], 0);
+ }
+
+ ring_remove(&surface->link);
+ destroy_canvas(surface);
+ free(surface);
+}
+
+static spice_msg_handler display_handlers[] = {
+ [ SPICE_MSG_SET_ACK ] = spice_channel_handle_set_ack,
+ [ SPICE_MSG_PING ] = spice_channel_handle_ping,
+ [ SPICE_MSG_NOTIFY ] = spice_channel_handle_notify,
+
+ [ SPICE_MSG_DISPLAY_MODE ] = display_handle_mode,
+ [ SPICE_MSG_DISPLAY_MARK ] = display_handle_mark,
+ [ SPICE_MSG_DISPLAY_RESET ] = display_handle_reset,
+ [ SPICE_MSG_DISPLAY_COPY_BITS ] = display_handle_copy_bits,
+ [ SPICE_MSG_DISPLAY_INVAL_LIST ] = display_handle_inv_list,
+ [ SPICE_MSG_DISPLAY_INVAL_ALL_PIXMAPS ] = display_handle_inv_pixmap_all,
+ [ SPICE_MSG_DISPLAY_INVAL_PALETTE ] = display_handle_inv_palette,
+ [ SPICE_MSG_DISPLAY_INVAL_ALL_PALETTES ] = display_handle_inv_palette_all,
+
+ [ SPICE_MSG_DISPLAY_STREAM_CREATE ] = display_handle_stream_create,
+ [ SPICE_MSG_DISPLAY_STREAM_DATA ] = display_handle_stream_data,
+ [ SPICE_MSG_DISPLAY_STREAM_CLIP ] = display_handle_stream_clip,
+ [ SPICE_MSG_DISPLAY_STREAM_DESTROY ] = display_handle_stream_destroy,
+ [ SPICE_MSG_DISPLAY_STREAM_DESTROY_ALL ] = display_handle_stream_destroy_all,
+
+ [ SPICE_MSG_DISPLAY_DRAW_FILL ] = display_handle_draw_fill,
+ [ SPICE_MSG_DISPLAY_DRAW_OPAQUE ] = display_handle_draw_opaque,
+ [ SPICE_MSG_DISPLAY_DRAW_COPY ] = display_handle_draw_copy,
+ [ SPICE_MSG_DISPLAY_DRAW_BLEND ] = display_handle_draw_blend,
+ [ SPICE_MSG_DISPLAY_DRAW_BLACKNESS ] = display_handle_draw_blackness,
+ [ SPICE_MSG_DISPLAY_DRAW_WHITENESS ] = display_handle_draw_whiteness,
+ [ SPICE_MSG_DISPLAY_DRAW_INVERS ] = display_handle_draw_invers,
+ [ SPICE_MSG_DISPLAY_DRAW_ROP3 ] = display_handle_draw_rop3,
+ [ SPICE_MSG_DISPLAY_DRAW_STROKE ] = display_handle_draw_stroke,
+ [ SPICE_MSG_DISPLAY_DRAW_TEXT ] = display_handle_draw_text,
+ [ SPICE_MSG_DISPLAY_DRAW_TRANSPARENT ] = display_handle_draw_transparent,
+ [ SPICE_MSG_DISPLAY_DRAW_ALPHA_BLEND ] = display_handle_draw_alpha_blend,
+
+ [ SPICE_MSG_DISPLAY_SURFACE_CREATE ] = display_handle_surface_create,
+ [ SPICE_MSG_DISPLAY_SURFACE_DESTROY ] = display_handle_surface_destroy,
+};
+
+static void spice_display_handle_msg(SpiceChannel *channel, spice_msg_in *msg)
+{
+ int type = spice_msg_in_type(msg);
+ assert(type < SPICE_N_ELEMENTS(display_handlers));
+ assert(display_handlers[type] != NULL);
+ display_handlers[type](channel, msg);
+}
+
diff --git a/gtk/channel-display.h b/gtk/channel-display.h
new file mode 100644
index 0000000..e6cb596
--- /dev/null
+++ b/gtk/channel-display.h
@@ -0,0 +1,35 @@
+#ifndef __SPICE_CLIENT_DISPLAY_CHANNEL_H__
+#define __SPICE_CLIENT_DISPLAY_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_DISPLAY_CHANNEL (spice_display_channel_get_type())
+#define SPICE_DISPLAY_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannel))
+#define SPICE_DISPLAY_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
+#define SPICE_IS_DISPLAY_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY_CHANNEL))
+#define SPICE_IS_DISPLAY_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY_CHANNEL))
+#define SPICE_DISPLAY_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY_CHANNEL, SpiceDisplayChannelClass))
+
+typedef struct _SpiceDisplayChannel SpiceDisplayChannel;
+typedef struct _SpiceDisplayChannelClass SpiceDisplayChannelClass;
+typedef struct spice_display_channel spice_display_channel;
+
+struct _SpiceDisplayChannel {
+ SpiceChannel parent;
+ spice_display_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceDisplayChannelClass {
+ SpiceChannelClass parent_class;
+
+ /* Do not add fields to this struct */
+};
+
+GType spice_display_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_DISPLAY_CHANNEL_H__ */
diff --git a/gtk/channel-inputs.c b/gtk/channel-inputs.c
new file mode 100644
index 0000000..5878c60
--- /dev/null
+++ b/gtk/channel-inputs.c
@@ -0,0 +1,250 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#define SPICE_INPUTS_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_INPUTS_CHANNEL, spice_inputs_channel))
+
+struct spice_inputs_channel {
+ int bs;
+ int dx, dy;
+ unsigned int x, y, dpy;
+ int motion_count;
+};
+
+G_DEFINE_TYPE(SpiceInputsChannel, spice_inputs_channel, SPICE_TYPE_CHANNEL)
+
+static void spice_inputs_handle_msg(SpiceChannel *channel, spice_msg_in *msg);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_inputs_channel_init(SpiceInputsChannel *channel)
+{
+ spice_inputs_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_INPUTS_CHANNEL_GET_PRIVATE(channel);
+ memset(c, 0, sizeof(*c));
+}
+
+static void spice_inputs_channel_finalize(GObject *obj)
+{
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ if (G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_inputs_channel_parent_class)->finalize(obj);
+}
+
+static void spice_inputs_channel_class_init(SpiceInputsChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->finalize = spice_inputs_channel_finalize;
+ channel_class->handle_msg = spice_inputs_handle_msg;
+
+ g_type_class_add_private(klass, sizeof(spice_inputs_channel));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void send_motion(SpiceInputsChannel *channel)
+{
+ spice_inputs_channel *c = channel->priv;
+ SpiceMsgcMouseMotion motion;
+ spice_msg_out *msg;
+
+ if (!c->dx && !c->dy)
+ return;
+
+ motion.buttons_state = c->bs;
+ motion.dx = c->dx;
+ motion.dy = c->dy;
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_MOUSE_MOTION);
+ msg->marshallers->msgc_inputs_mouse_motion(msg->marshaller, &motion);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+
+ c->motion_count++;
+ c->dx = 0;
+ c->dy = 0;
+}
+
+static void send_position(SpiceInputsChannel *channel)
+{
+ spice_inputs_channel *c = channel->priv;
+ SpiceMsgcMousePosition position;
+ spice_msg_out *msg;
+
+ if (c->dpy == -1)
+ return;
+
+#if 0
+ fprintf(stderr, "%s: +%d+%d\n", __FUNCTION__, c->x, c->y);
+#endif
+ position.buttons_state = c->bs;
+ position.x = c->x;
+ position.y = c->y;
+ position.display_id = c->dpy;
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_MOUSE_POSITION);
+ msg->marshallers->msgc_inputs_mouse_position(msg->marshaller, &position);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+
+ c->motion_count++;
+ c->dpy = -1;
+}
+
+static void inputs_handle_init(SpiceChannel *channel, spice_msg_in *in)
+{
+}
+
+static void inputs_handle_modifiers(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void inputs_handle_ack(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_inputs_channel *c = SPICE_INPUTS_CHANNEL(channel)->priv;
+ c->motion_count -= SPICE_INPUT_MOTION_ACK_BUNCH;
+ send_motion(SPICE_INPUTS_CHANNEL(channel));
+ send_position(SPICE_INPUTS_CHANNEL(channel));
+}
+
+static spice_msg_handler inputs_handlers[] = {
+ [ SPICE_MSG_SET_ACK ] = spice_channel_handle_set_ack,
+ [ SPICE_MSG_PING ] = spice_channel_handle_ping,
+ [ SPICE_MSG_NOTIFY ] = spice_channel_handle_notify,
+
+ [ SPICE_MSG_INPUTS_INIT ] = inputs_handle_init,
+ [ SPICE_MSG_INPUTS_KEY_MODIFIERS ] = inputs_handle_modifiers,
+ [ SPICE_MSG_INPUTS_MOUSE_MOTION_ACK ] = inputs_handle_ack,
+};
+
+static void spice_inputs_handle_msg(SpiceChannel *channel, spice_msg_in *msg)
+{
+ int type = spice_msg_in_type(msg);
+ assert(type < SPICE_N_ELEMENTS(inputs_handlers));
+ assert(inputs_handlers[type] != NULL);
+ inputs_handlers[type](channel, msg);
+}
+
+void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
+ gint button_state)
+{
+ spice_inputs_channel *c = channel->priv;
+
+ c->bs = button_state;
+ c->dx += dx;
+ c->dy += dy;
+
+ if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
+ send_motion(channel);
+ }
+}
+
+void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
+ gint display, gint button_state)
+{
+ spice_inputs_channel *c = channel->priv;
+
+ c->bs = button_state;
+ c->x = x;
+ c->y = y;
+ c->dpy = display;
+
+ if (c->motion_count < SPICE_INPUT_MOTION_ACK_BUNCH * 2) {
+ send_position(channel);
+ }
+}
+
+void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
+ gint button_state)
+{
+ spice_inputs_channel *c = channel->priv;
+ SpiceMsgcMousePress press = {
+ .button = button,
+ .buttons_state = button_state,
+ };
+ spice_msg_out *msg;
+
+ c->bs = button_state;
+
+ send_motion(channel);
+ send_position(channel);
+
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_MOUSE_PRESS);
+ msg->marshallers->msgc_inputs_mouse_press(msg->marshaller, &press);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+}
+
+void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
+ gint button_state)
+{
+ spice_inputs_channel *c = channel->priv;
+ SpiceMsgcMouseRelease release = {
+ .button = button,
+ .buttons_state = button_state,
+ };
+ spice_msg_out *msg;
+
+ c->bs = button_state;
+
+ send_motion(channel);
+ send_position(channel);
+
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_MOUSE_RELEASE);
+ msg->marshallers->msgc_inputs_mouse_release(msg->marshaller, &release);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+}
+
+void spice_inputs_key_press(SpiceInputsChannel *channel, guint scancode)
+{
+ SpiceMsgcKeyDown down;
+ spice_msg_out *msg;
+
+#if 0
+ fprintf(stderr, "%s: scancode %d\n", __FUNCTION__, scancode);
+#endif
+ if (scancode < 0x100) {
+ down.code = scancode;
+ } else {
+ down.code = 0xe0 | ((scancode - 0x100) << 8);
+ }
+
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_KEY_DOWN);
+ msg->marshallers->msgc_inputs_key_down(msg->marshaller, &down);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+}
+
+void spice_inputs_key_release(SpiceInputsChannel *channel, guint scancode)
+{
+ SpiceMsgcKeyUp up;
+ spice_msg_out *msg;
+
+#if 0
+ fprintf(stderr, "%s: scancode %d\n", __FUNCTION__, scancode);
+#endif
+ if (scancode < 0x100) {
+ up.code = scancode | 0x80;
+ } else {
+ up.code = 0x80e0 | ((scancode - 0x100) << 8);
+ }
+
+ msg = spice_msg_out_new(SPICE_CHANNEL(channel),
+ SPICE_MSGC_INPUTS_KEY_UP);
+ msg->marshallers->msgc_inputs_key_up(msg->marshaller, &up);
+ spice_msg_out_send(msg);
+ spice_msg_out_put(msg);
+}
diff --git a/gtk/channel-inputs.h b/gtk/channel-inputs.h
new file mode 100644
index 0000000..728a470
--- /dev/null
+++ b/gtk/channel-inputs.h
@@ -0,0 +1,46 @@
+#ifndef __SPICE_CLIENT_INPUTS_CHANNEL_H__
+#define __SPICE_CLIENT_INPUTS_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_INPUTS_CHANNEL (spice_inputs_channel_get_type())
+#define SPICE_INPUTS_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannel))
+#define SPICE_INPUTS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
+#define SPICE_IS_INPUTS_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_INPUTS_CHANNEL))
+#define SPICE_IS_INPUTS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_INPUTS_CHANNEL))
+#define SPICE_INPUTS_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_INPUTS_CHANNEL, SpiceInputsChannelClass))
+
+typedef struct _SpiceInputsChannel SpiceInputsChannel;
+typedef struct _SpiceInputsChannelClass SpiceInputsChannelClass;
+typedef struct spice_inputs_channel spice_inputs_channel;
+
+struct _SpiceInputsChannel {
+ SpiceChannel parent;
+ spice_inputs_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceInputsChannelClass {
+ SpiceChannelClass parent_class;
+
+ /* Do not add fields to this struct */
+};
+
+GType spice_inputs_channel_get_type(void);
+
+void spice_inputs_motion(SpiceInputsChannel *channel, gint dx, gint dy,
+ gint button_state);
+void spice_inputs_position(SpiceInputsChannel *channel, gint x, gint y,
+ gint display, gint button_state);
+void spice_inputs_button_press(SpiceInputsChannel *channel, gint button,
+ gint button_state);
+void spice_inputs_button_release(SpiceInputsChannel *channel, gint button,
+ gint button_state);
+void spice_inputs_key_press(SpiceInputsChannel *channel, guint keyval);
+void spice_inputs_key_release(SpiceInputsChannel *channel, guint keyval);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_INPUTS_CHANNEL_H__ */
diff --git a/gtk/channel-main.c b/gtk/channel-main.c
new file mode 100644
index 0000000..79cf7e8
--- /dev/null
+++ b/gtk/channel-main.c
@@ -0,0 +1,345 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-session-priv.h"
+
+#include <spice/vd_agent.h>
+
+#define SPICE_MAIN_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_MAIN_CHANNEL, spice_main_channel))
+
+struct spice_main_channel {
+ enum SpiceMouseMode mouse_mode;
+ int agent_connected;
+ int agent_tokens;
+ uint8_t *agent_msg;
+ uint8_t *agent_msg_pos;
+ uint8_t *agent_msg_size;
+ struct {
+ int x;
+ int y;
+ int width;
+ int height;
+ } display[1];
+};
+
+G_DEFINE_TYPE(SpiceMainChannel, spice_main_channel, SPICE_TYPE_CHANNEL)
+
+enum {
+ SPICE_MAIN_MOUSE_MODE,
+ SPICE_MAIN_AGENT_EVENT,
+
+ SPICE_MAIN_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_MAIN_LAST_SIGNAL];
+
+static void spice_main_handle_msg(SpiceChannel *channel, spice_msg_in *msg);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_main_channel_init(SpiceMainChannel *channel)
+{
+ spice_main_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_MAIN_CHANNEL_GET_PRIVATE(channel);
+ memset(c, 0, sizeof(*c));
+}
+
+static void spice_main_channel_finalize(GObject *obj)
+{
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ if (G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_main_channel_parent_class)->finalize(obj);
+}
+
+static void spice_main_channel_class_init(SpiceMainChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->finalize = spice_main_channel_finalize;
+ channel_class->handle_msg = spice_main_handle_msg;
+
+ signals[SPICE_MAIN_MOUSE_MODE] =
+ g_signal_new("spice-main-mouse-mode",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceMainChannelClass, spice_main_mouse_mode),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ signals[SPICE_MAIN_AGENT_EVENT] =
+ g_signal_new("spice-main-agent-event",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceMainChannelClass, spice_main_agent_event),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ g_type_class_add_private(klass, sizeof(spice_main_channel));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void agent_monitors_config(SpiceChannel *channel)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ spice_msg_out *out;
+ VDAgentMessage* msg;
+ VDAgentMonitorsConfig *mon;
+ int i, monitors = 1;
+ size_t size;
+
+ if (!c->agent_connected)
+ return;
+ for (i = 0; i < monitors; i++) {
+ if (!c->display[i].width ||
+ !c->display[i].height)
+ return;
+ }
+
+ size = sizeof(VDAgentMonitorsConfig) + sizeof(VDAgentMonConfig) * monitors;
+ out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_AGENT_DATA);
+ msg = (VDAgentMessage*)
+ spice_marshaller_reserve_space(out->marshaller, sizeof(VDAgentMessage));
+ mon = (VDAgentMonitorsConfig*)
+ spice_marshaller_reserve_space(out->marshaller, size);
+
+ msg->protocol = VD_AGENT_PROTOCOL;
+ msg->type = VD_AGENT_MONITORS_CONFIG;
+ msg->opaque = 0;
+ msg->size = size;
+
+ mon->num_of_monitors = monitors;
+ mon->flags = 0;
+ for (i = 0; i < monitors; i++) {
+ mon->monitors[i].depth = 32;
+ mon->monitors[i].width = c->display[i].width;
+ mon->monitors[i].height = c->display[i].height;
+ mon->monitors[i].x = c->display[i].x;
+ mon->monitors[i].y = c->display[i].y;
+ fprintf(stderr, "%s: #%d %dx%d+%d+%d @ %d bpp\n", __FUNCTION__, i,
+ mon->monitors[i].width, mon->monitors[i].height,
+ mon->monitors[i].x, mon->monitors[i].y,
+ mon->monitors[i].depth);
+ }
+
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+}
+
+static void agent_start(SpiceChannel *channel)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ SpiceMsgcMainAgentStart agent_start = {
+ .num_tokens = ~0,
+ };
+ spice_msg_out *out;
+
+ c->agent_connected = true;
+ g_signal_emit(channel, signals[SPICE_MAIN_AGENT_EVENT], 0,
+ SPICE_AGENT_CONNECT);
+
+ out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_AGENT_START);
+ out->marshallers->msgc_main_agent_start(out->marshaller, &agent_start);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+
+ agent_monitors_config(channel);
+}
+
+static void agent_stopped(SpiceChannel *channel)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+ c->agent_connected = false;
+ g_signal_emit(channel, signals[SPICE_MAIN_AGENT_EVENT], 0,
+ SPICE_AGENT_DISCONNECT);
+}
+
+static void set_mouse_mode(SpiceChannel *channel, uint32_t supported, uint32_t current)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+ if (c->mouse_mode != current) {
+ c->mouse_mode = current;
+ g_signal_emit(channel, signals[SPICE_MAIN_MOUSE_MODE], 0, current);
+ }
+
+ /* switch to client mode if possible */
+ if ((supported & SPICE_MOUSE_MODE_CLIENT) && (current != SPICE_MOUSE_MODE_CLIENT)) {
+ SpiceMsgcMainMouseModeRequest req = {
+ .mode = SPICE_MOUSE_MODE_CLIENT,
+ };
+ spice_msg_out *out;
+ out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_MOUSE_MODE_REQUEST);
+ out->marshallers->msgc_main_mouse_mode_request(out->marshaller, &req);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+ }
+}
+
+static void main_handle_init(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ SpiceMsgMainInit *init = spice_msg_in_parsed(in);
+ SpiceSession *session;
+ spice_msg_out *out;
+
+ g_object_get(channel, "spice-session", &session, NULL);
+ spice_session_set_connection_id(session, init->session_id);
+
+ out = spice_msg_out_new(channel, SPICE_MSGC_MAIN_ATTACH_CHANNELS);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+
+ set_mouse_mode(channel, init->supported_mouse_modes, init->current_mouse_mode);
+
+ c->agent_tokens = init->agent_tokens;
+ if (init->agent_connected) {
+ agent_start(channel);
+ }
+
+#if 0
+ set_mm_time(init->multi_media_time);
+#endif
+}
+
+static void main_handle_mm_time(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+#if 0
+ set_mm_time(init->multi_media_time);
+#endif
+}
+
+static void main_handle_channels_list(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgChannels *msg = spice_msg_in_parsed(in);
+ SpiceSession *session;
+ SpiceChannel *add;
+ int i;
+
+ g_object_get(channel, "spice-session", &session, NULL);
+ for (i = 0; i < msg->num_of_channels; i++) {
+ add = spice_channel_new(session, msg->channels[i].type,
+ msg->channels[i].id);
+ }
+}
+
+static void main_handle_mouse_mode(SpiceChannel *channel, spice_msg_in *in)
+{
+ SpiceMsgMainMouseMode *msg = spice_msg_in_parsed(in);
+ set_mouse_mode(channel, msg->supported_modes, msg->current_mode);
+}
+
+static void main_handle_agent_connected(SpiceChannel *channel, spice_msg_in *in)
+{
+ agent_start(channel);
+}
+
+static void main_handle_agent_disconnected(SpiceChannel *channel, spice_msg_in *in)
+{
+ agent_stopped(channel);
+}
+
+static void main_handle_agent_data(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ VDAgentMessage *msg;
+ int len;
+
+ spice_msg_in_hexdump(in);
+
+ if (!c->agent_msg) {
+ msg = spice_msg_in_raw(in, &len);
+ assert(len > sizeof(VDAgentMessage));
+ if (msg->size + sizeof(VDAgentMessage) > len) {
+ fprintf(stderr, "%s: TODO: start buffer\n", __FUNCTION__);
+ } else {
+ assert(msg->size + sizeof(VDAgentMessage) == len);
+ goto complete;
+ }
+ } else {
+ fprintf(stderr, "%s: TODO: fill buffer\n", __FUNCTION__);
+ }
+ return;
+
+complete:
+ switch (msg->type) {
+ case VD_AGENT_REPLY:
+ {
+ VDAgentReply *reply = (VDAgentReply*)(msg+1);
+ fprintf(stderr, "%s: reply: type %d, %s\n", __FUNCTION__, reply->type,
+ reply->error == VD_AGENT_SUCCESS ? "success" : "error");
+ break;
+ }
+ case VD_AGENT_CLIPBOARD:
+ fprintf(stderr, "%s: clipboard\n", __FUNCTION__);
+ break;
+ default:
+ fprintf(stderr, "unsupported agent message type %u size %u\n",
+ msg->type, msg->size);
+ }
+}
+
+static void main_handle_agent_token(SpiceChannel *channel, spice_msg_in *in)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static spice_msg_handler main_handlers[] = {
+ [ SPICE_MSG_SET_ACK ] = spice_channel_handle_set_ack,
+ [ SPICE_MSG_PING ] = spice_channel_handle_ping,
+ [ SPICE_MSG_NOTIFY ] = spice_channel_handle_notify,
+
+ [ SPICE_MSG_MAIN_INIT ] = main_handle_init,
+ [ SPICE_MSG_MAIN_CHANNELS_LIST ] = main_handle_channels_list,
+ [ SPICE_MSG_MAIN_MOUSE_MODE ] = main_handle_mouse_mode,
+ [ SPICE_MSG_MAIN_MULTI_MEDIA_TIME ] = main_handle_mm_time,
+
+ [ SPICE_MSG_MAIN_AGENT_CONNECTED ] = main_handle_agent_connected,
+ [ SPICE_MSG_MAIN_AGENT_DISCONNECTED ] = main_handle_agent_disconnected,
+ [ SPICE_MSG_MAIN_AGENT_DATA ] = main_handle_agent_data,
+ [ SPICE_MSG_MAIN_AGENT_TOKEN ] = main_handle_agent_token,
+};
+
+static void spice_main_handle_msg(SpiceChannel *channel, spice_msg_in *msg)
+{
+ int type = spice_msg_in_type(msg);
+ assert(type < SPICE_N_ELEMENTS(main_handlers));
+ assert(main_handlers[type] != NULL);
+ main_handlers[type](channel, msg);
+}
+
+enum SpiceMouseMode spice_main_get_mouse_mode(SpiceChannel *channel)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+ return c->mouse_mode;
+}
+
+void spice_main_set_display(SpiceChannel *channel, int id,
+ int x, int y, int width, int height)
+{
+ spice_main_channel *c = SPICE_MAIN_CHANNEL(channel)->priv;
+
+ if (id < SPICE_N_ELEMENTS(c->display)) {
+ c->display[id].x = x;
+ c->display[id].y = y;
+ c->display[id].width = width;
+ c->display[id].height = height;
+ agent_monitors_config(channel);
+ }
+}
diff --git a/gtk/channel-main.h b/gtk/channel-main.h
new file mode 100644
index 0000000..c019607
--- /dev/null
+++ b/gtk/channel-main.h
@@ -0,0 +1,39 @@
+#ifndef __SPICE_CLIENT_MAIN_CHANNEL_H__
+#define __SPICE_CLIENT_MAIN_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_MAIN_CHANNEL (spice_main_channel_get_type())
+#define SPICE_MAIN_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannel))
+#define SPICE_MAIN_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
+#define SPICE_IS_MAIN_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_MAIN_CHANNEL))
+#define SPICE_IS_MAIN_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_MAIN_CHANNEL))
+#define SPICE_MAIN_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_MAIN_CHANNEL, SpiceMainChannelClass))
+
+typedef struct _SpiceMainChannel SpiceMainChannel;
+typedef struct _SpiceMainChannelClass SpiceMainChannelClass;
+typedef struct spice_main_channel spice_main_channel;
+
+struct _SpiceMainChannel {
+ SpiceChannel parent;
+ spice_main_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceMainChannelClass {
+ SpiceChannelClass parent_class;
+
+ /* signals */
+ void (*spice_main_mouse_mode)(SpiceChannel *channel, enum SpiceMouseMode mode);
+ void (*spice_main_agent_event)(SpiceChannel *channel, enum SpiceAgentEvent event);
+
+ /* Do not add fields to this struct */
+};
+
+GType spice_main_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_MAIN_CHANNEL_H__ */
diff --git a/gtk/channel-playback.c b/gtk/channel-playback.c
new file mode 100644
index 0000000..20b86d7
--- /dev/null
+++ b/gtk/channel-playback.c
@@ -0,0 +1,178 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-marshal.h"
+
+#define SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PLAYBACK_CHANNEL, spice_playback_channel))
+
+struct spice_playback_channel {
+ int mode;
+};
+
+G_DEFINE_TYPE(SpicePlaybackChannel, spice_playback_channel, SPICE_TYPE_CHANNEL)
+
+enum {
+ SPICE_PLAYBACK_START,
+ SPICE_PLAYBACK_DATA,
+ SPICE_PLAYBACK_STOP,
+
+ SPICE_PLAYBACK_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_PLAYBACK_LAST_SIGNAL];
+
+static void spice_playback_handle_msg(SpiceChannel *channel, spice_msg_in *msg);
+
+/* ------------------------------------------------------------------ */
+
+static void spice_playback_channel_init(SpicePlaybackChannel *channel)
+{
+ spice_playback_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_PLAYBACK_CHANNEL_GET_PRIVATE(channel);
+ memset(c, 0, sizeof(*c));
+}
+
+static void spice_playback_channel_finalize(GObject *obj)
+{
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ if (G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_playback_channel_parent_class)->finalize(obj);
+}
+
+static void spice_playback_channel_class_init(SpicePlaybackChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->finalize = spice_playback_channel_finalize;
+ channel_class->handle_msg = spice_playback_handle_msg;
+
+ signals[SPICE_PLAYBACK_START] =
+ g_signal_new("spice-playback-start",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpicePlaybackChannelClass, spice_playback_start),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__INT_INT_INT,
+ G_TYPE_NONE,
+ 3,
+ G_TYPE_INT, G_TYPE_INT, G_TYPE_INT);
+
+ signals[SPICE_PLAYBACK_DATA] =
+ g_signal_new("spice-playback-data",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpicePlaybackChannelClass, spice_playback_data),
+ NULL, NULL,
+ g_cclosure_user_marshal_VOID__POINTER_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_POINTER, G_TYPE_INT);
+
+ signals[SPICE_PLAYBACK_STOP] =
+ g_signal_new("spice-playback-stop",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpicePlaybackChannelClass, spice_playback_stop),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private(klass, sizeof(spice_playback_channel));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void playback_handle_data(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_playback_channel *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+ SpiceMsgPlaybackPacket *data = spice_msg_in_parsed(in);
+
+#if 0
+ fprintf(stderr, "%s: time %d data %p size %d\n", __FUNCTION__,
+ data->time, data->data, data->data_size);
+#endif
+
+ switch (c->mode) {
+ case SPICE_AUDIO_DATA_MODE_RAW:
+ g_signal_emit(channel, signals[SPICE_PLAYBACK_DATA], 0,
+ data->data, data->data_size);
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled mode\n", __FUNCTION__);
+ break;
+ }
+}
+
+static void playback_handle_mode(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_playback_channel *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+ SpiceMsgPlaybackMode *mode = spice_msg_in_parsed(in);
+
+#if 0
+ fprintf(stderr, "%s: time %d mode %d data %p size %d\n", __FUNCTION__,
+ mode->time, mode->mode, mode->data, mode->data_size);
+#endif
+
+ c->mode = mode->mode;
+ switch (c->mode) {
+ case SPICE_AUDIO_DATA_MODE_RAW:
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled mode\n", __FUNCTION__);
+ break;
+ }
+}
+
+static void playback_handle_start(SpiceChannel *channel, spice_msg_in *in)
+{
+ spice_playback_channel *c = SPICE_PLAYBACK_CHANNEL(channel)->priv;
+ SpiceMsgPlaybackStart *start = spice_msg_in_parsed(in);
+
+#if 0
+ fprintf(stderr, "%s: fmt %d channels %d freq %d time %d\n", __FUNCTION__,
+ start->format, start->channels, start->frequency, start->time);
+#endif
+
+ switch (c->mode) {
+ case SPICE_AUDIO_DATA_MODE_RAW:
+ g_signal_emit(channel, signals[SPICE_PLAYBACK_START], 0,
+ start->format, start->channels, start->frequency);
+ break;
+ default:
+ fprintf(stderr, "%s: unhandled mode\n", __FUNCTION__);
+ break;
+ }
+}
+
+static void playback_handle_stop(SpiceChannel *channel, spice_msg_in *in)
+{
+ g_signal_emit(channel, signals[SPICE_PLAYBACK_STOP], 0);
+}
+
+static spice_msg_handler playback_handlers[] = {
+ [ SPICE_MSG_SET_ACK ] = spice_channel_handle_set_ack,
+ [ SPICE_MSG_PING ] = spice_channel_handle_ping,
+ [ SPICE_MSG_NOTIFY ] = spice_channel_handle_notify,
+
+ [ SPICE_MSG_PLAYBACK_DATA ] = playback_handle_data,
+ [ SPICE_MSG_PLAYBACK_MODE ] = playback_handle_mode,
+ [ SPICE_MSG_PLAYBACK_START ] = playback_handle_start,
+ [ SPICE_MSG_PLAYBACK_STOP ] = playback_handle_stop,
+};
+
+static void spice_playback_handle_msg(SpiceChannel *channel, spice_msg_in *msg)
+{
+ int type = spice_msg_in_type(msg);
+ assert(type < SPICE_N_ELEMENTS(playback_handlers));
+ assert(playback_handlers[type] != NULL);
+ playback_handlers[type](channel, msg);
+}
diff --git a/gtk/channel-playback.h b/gtk/channel-playback.h
new file mode 100644
index 0000000..baa1d12
--- /dev/null
+++ b/gtk/channel-playback.h
@@ -0,0 +1,41 @@
+#ifndef __SPICE_CLIENT_PLAYBACK_CHANNEL_H__
+#define __SPICE_CLIENT_PLAYBACK_CHANNEL_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_PLAYBACK_CHANNEL (spice_playback_channel_get_type())
+#define SPICE_PLAYBACK_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannel))
+#define SPICE_PLAYBACK_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
+#define SPICE_IS_PLAYBACK_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PLAYBACK_CHANNEL))
+#define SPICE_IS_PLAYBACK_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PLAYBACK_CHANNEL))
+#define SPICE_PLAYBACK_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PLAYBACK_CHANNEL, SpicePlaybackChannelClass))
+
+typedef struct _SpicePlaybackChannel SpicePlaybackChannel;
+typedef struct _SpicePlaybackChannelClass SpicePlaybackChannelClass;
+typedef struct spice_playback_channel spice_playback_channel;
+
+struct _SpicePlaybackChannel {
+ SpiceChannel parent;
+ spice_playback_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpicePlaybackChannelClass {
+ SpiceChannelClass parent_class;
+
+ /* signals */
+ void (*spice_playback_start)(SpicePlaybackChannel *channel,
+ gint format, gint channels, gint freq);
+ void (*spice_playback_data)(SpicePlaybackChannel *channel, gpointer *data, gint size);
+ void (*spice_playback_stop)(SpicePlaybackChannel *channel);
+
+ /* Do not add fields to this struct */
+};
+
+GType spice_playback_channel_get_type(void);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_PLAYBACK_CHANNEL_H__ */
diff --git a/gtk/channel-record.c b/gtk/channel-record.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gtk/channel-record.c
diff --git a/gtk/channel-record.h b/gtk/channel-record.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gtk/channel-record.h
diff --git a/gtk/decode-glz-tmpl.c b/gtk/decode-glz-tmpl.c
new file mode 100644
index 0000000..672ca93
--- /dev/null
+++ b/gtk/decode-glz-tmpl.c
@@ -0,0 +1,336 @@
+/*
+ Copyright (C) 2009 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+// External defines: PLT, RGBX/PLTXX/ALPHA, TO_RGB32.
+// If PLT4/1 and TO_RGB32 are defined, we need CAST_PLT_DISTANCE (
+// because then the number of pixels differ from the units used in the compression)
+
+/*
+ For each output pixel type the following macros are defined:
+ OUT_PIXEL - the output pixel type
+ COPY_PIXEL(p, out) - assigns the pixel to the place pointed by out and
+ increases out. Used in RLE.
+ Need special handling because in alpha we copy only
+ the pad byte.
+ COPY_REF_PIXEL(ref, out) - copies the pixel pointed by ref to the pixel pointed by out.
+ Increases ref and out.
+ COPY_COMP_PIXEL(encoder, out) - copies pixel from the compressed buffer to the decompressed
+ buffer. Increases out.
+*/
+
+#if !defined(LZ_RGB_ALPHA)
+#define COPY_PIXEL(p, out) (*(out++) = p)
+#define COPY_REF_PIXEL(ref, out) (*(out++) = *(ref++))
+#endif
+
+// decompressing plt to plt
+#ifdef LZ_PLT
+#ifndef TO_RGB32
+#define OUT_PIXEL one_byte_pixel_t
+#define FNAME(name) glz_plt_##name
+#define COPY_COMP_PIXEL(in, out) {(out)->a = *(in++); out++;}
+#else // TO_RGB32
+#define OUT_PIXEL rgb32_pixel_t
+#define COPY_PLT_ENTRY(ent, out) {\
+ (out)->b = ent; (out)->g = (ent >> 8); (out)->r = (ent >> 16); (out)->pad = 0;}
+#ifdef PLT8
+#define FNAME(name) glz_plt8_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette) { \
+ uint32_t rgb = palette->ents[*(in++)]; \
+ COPY_PLT_ENTRY(rgb, out); \
+ out++; \
+}
+#elif defined(PLT4_BE)
+#define FNAME(name) glz_plt4_be_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){ \
+ uint8_t byte = *(in++); \
+ uint32_t rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)]; \
+ COPY_PLT_ENTRY(rgb, out); \
+ out++; \
+ rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)]; \
+ COPY_PLT_ENTRY(rgb, out); \
+ out++; \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*2)
+#elif defined(PLT4_LE)
+#define FNAME(name) glz_plt4_le_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){ \
+ uint8_t byte = *(in++); \
+ uint32_t rgb = palette->ents[(byte & 0x0f) % (palette->num_ents)]; \
+ COPY_PLT_ENTRY(rgb, out); \
+ out++; \
+ rgb = palette->ents[((byte >> 4) & 0x0f) % (palette->num_ents)]; \
+ COPY_PLT_ENTRY(rgb, out); \
+ out++; \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*2)
+#elif defined(PLT1_BE) // TODO store palette entries for direct access
+#define FNAME(name) glz_plt1_be_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){ \
+ uint8_t byte = *(in++); \
+ int i; \
+ uint32_t fore = palette->ents[1]; \
+ uint32_t back = palette->ents[0]; \
+ for (i = 7; i >= 0; i--) \
+ { \
+ if ((byte >> i) & 1) { \
+ COPY_PLT_ENTRY(fore, out); \
+ } else { \
+ COPY_PLT_ENTRY(back, out); \
+ } \
+ out++; \
+ } \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*8)
+#elif defined(PLT1_LE)
+#define FNAME(name) glz_plt1_le_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out, palette){ \
+ uint8_t byte = *(in++); \
+ int i; \
+ uint32_t fore = palette->ents[1]; \
+ uint32_t back = palette->ents[0]; \
+ for (i = 0; i < 8; i++) \
+ { \
+ if ((byte >> i) & 1) { \
+ COPY_PLT_ENTRY(fore, out); \
+ } else { \
+ COPY_PLT_ENTRY(back, out); \
+ } \
+ out++; \
+ } \
+}
+#define CAST_PLT_DISTANCE(dist) (dist*8)
+#endif // PLT Type
+#endif // TO_RGB32
+#endif
+
+#ifdef LZ_RGB16
+#ifndef TO_RGB32
+#define OUT_PIXEL rgb16_pixel_t
+#define FNAME(name) glz_rgb16_##name
+#define COPY_COMP_PIXEL(in, out) {*out = (*(in++)) << 8; *out |= *(in++); out++;}
+#else
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb16_to_rgb32_##name
+#define COPY_COMP_PIXEL(in, out) {out->r = *(in++); out->b= *(in++); \
+ out->g = (((out->r) << 6) | ((out->b) >> 2)) & ~0x07; \
+ out->g |= (out->g >> 5); \
+ out->r = ((out->r << 1) & ~0x07) | ((out->r >> 4) & 0x07) ; \
+ out->b = (out->b << 3) | ((out->b >> 2) & 0x07); \
+ out->pad = 0; \
+ out++; \
+}
+#endif
+#endif
+
+#ifdef LZ_RGB24
+#define OUT_PIXEL rgb24_pixel_t
+#define FNAME(name) glz_rgb24_##name
+#define COPY_COMP_PIXEL(in, out) { \
+ out->b = *(in++); \
+ out->g = *(in++); \
+ out->r = *(in++); \
+ out++; \
+}
+#endif
+
+#ifdef LZ_RGB32
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb32_##name
+#define COPY_COMP_PIXEL(in, out) { \
+ out->b = *(in++); \
+ out->g = *(in++); \
+ out->r = *(in++); \
+ out->pad = 0; \
+ out++; \
+}
+#endif
+
+#ifdef LZ_RGB_ALPHA
+#define OUT_PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb_alpha_##name
+#define COPY_PIXEL(p, out) {out->pad = p.pad; out++;}
+#define COPY_REF_PIXEL(ref, out) {out->pad = ref->pad; out++; ref++;}
+#define COPY_COMP_PIXEL(in, out) {out->pad = *(in++); out++;}
+#endif
+
+// TODO: separate into routines that decode to dist,len. and to a routine that
+// actually copies the data.
+
+/* returns num of bytes read from in buf.
+ size should be in PIXEL */
+static size_t FNAME(decode)(SpiceGlzDecoderWindow *window,
+ uint8_t* in_buf, uint8_t *out_buf, int size,
+ uint64_t image_id, SpicePalette *plt)
+{
+ uint8_t *ip = in_buf;
+ OUT_PIXEL *out_pix_buf = (OUT_PIXEL *)out_buf;
+ OUT_PIXEL *op = out_pix_buf;
+ OUT_PIXEL *op_limit = out_pix_buf + size;
+
+ uint32_t ctrl = *(ip++);
+ int loop = true;
+
+ do {
+ if (ctrl >= MAX_COPY) { // reference (dictionary/RLE)
+ OUT_PIXEL *ref = op;
+ uint32_t len = ctrl >> 5;
+ uint8_t pixel_flag = (ctrl >> 4) & 0x01;
+ uint32_t pixel_ofs = (ctrl & 0x0f);
+ uint8_t image_flag;
+ uint32_t image_dist;
+
+ /* retrieving the referenced images, the offset of the first pixel,
+ and the match length */
+
+ uint8_t code;
+ //len--; // TODO: why do we do this?
+
+ if (len == 7) { // match length is bigger than 7
+ do {
+ code = *(ip++);
+ len += code;
+ } while (code == 255); // remaining of len
+ }
+ code = *(ip++);
+ pixel_ofs += (code << 4);
+
+ code = *(ip++);
+ image_flag = (code >> 6) & 0x03;
+ if (!pixel_flag) { // short pixel offset
+ int i;
+ image_dist = code & 0x3f;
+ for (i = 0; i < image_flag; i++) {
+ code = *(ip++);
+ image_dist += (code << (6 + (8 * i)));
+ }
+ } else {
+ int i;
+ pixel_flag = (code >> 5) & 0x01;
+ pixel_ofs += (code & 0x1f) << 12;
+ image_dist = 0;
+ for (i = 0; i < image_flag; i++) {
+ code = *(ip++);
+ image_dist += (code << 8 * i);
+ }
+
+
+ if (pixel_flag) { // very long pixel offset
+ code = *(ip++);
+ pixel_ofs += code << 17;
+ }
+ }
+
+#if defined(LZ_PLT) || defined(LZ_RGB_ALPHA)
+ len += 2; // length is biased by 2 (fixing bias)
+#elif defined(LZ_RGB16)
+ len += 1; // length is biased by 1 (fixing bias)
+#endif
+ if (!image_dist) {
+ pixel_ofs += 1; // offset is biased by 1 (fixing bias)
+ }
+
+#if defined(TO_RGB32)
+#if defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || defined(PLT1_LE)
+ pixel_ofs = CAST_PLT_DISTANCE(pixel_ofs);
+ len = CAST_PLT_DISTANCE(len);
+#endif
+#endif
+
+ if (!image_dist) { // reference is inside the same image
+ ref -= pixel_ofs;
+ assert(ref + len <= op_limit);
+ assert(ref >= out_pix_buf);
+ } else {
+ ref = glz_decoder_window_bits(window, image_id,
+ image_dist, pixel_ofs);
+ }
+
+ assert(op + len <= op_limit);
+
+ /* copying the match*/
+
+ if (ref == (op - 1)) { // run (this will never be called in PLT4/1_TO_RGB because the
+ // number of pixel copied is larger then one...
+ /* optimize copy for a run */
+ OUT_PIXEL b = *ref;
+ for (; len; --len) {
+ COPY_PIXEL(b, op);
+ assert(op <= op_limit);
+ }
+ } else {
+ for (; len; --len) {
+ COPY_REF_PIXEL(ref, op);
+ assert(op <= op_limit);
+ }
+ }
+ } else { // copy
+ ctrl++; // copy count is biased by 1
+#if defined(TO_RGB32) && (defined(PLT4_BE) || defined(PLT4_LE) || defined(PLT1_BE) || \
+ defined(PLT1_LE))
+ assert(op + CAST_PLT_DISTANCE(ctrl) <= op_limit);
+#else
+ assert(op + ctrl <= op_limit);
+#endif
+
+#if defined(TO_RGB32) && defined(LZ_PLT)
+ assert(plt);
+ COPY_COMP_PIXEL(ip, op, plt);
+#else
+ COPY_COMP_PIXEL(ip, op);
+#endif
+ assert(op <= op_limit);
+
+ for (--ctrl; ctrl; ctrl--) {
+#if defined(TO_RGB32) && defined(LZ_PLT)
+ assert(plt);
+ COPY_COMP_PIXEL(ip, op, plt);
+#else
+ COPY_COMP_PIXEL(ip, op);
+#endif
+ assert(op <= op_limit);
+ }
+ } // END REF/COPY
+
+ if (LZ_EXPECT_CONDITIONAL(op < op_limit)) {
+ ctrl = *(ip++);
+ } else {
+ loop = false;
+ }
+ } while (LZ_EXPECT_CONDITIONAL(loop));
+
+ return (ip - in_buf);
+}
+#undef LZ_PLT
+#undef PLT8
+#undef PLT4_BE
+#undef PLT4_LE
+#undef PLT1_BE
+#undef PLT1_LE
+#undef LZ_RGB16
+#undef LZ_RGB24
+#undef LZ_RGB32
+#undef LZ_RGB_ALPHA
+#undef TO_RGB32
+#undef OUT_PIXEL
+#undef FNAME
+#undef COPY_PIXEL
+#undef COPY_REF_PIXEL
+#undef COPY_COMP_PIXEL
+#undef COPY_PLT_ENTRY
+#undef CAST_PLT_DISTANCE
+
diff --git a/gtk/decode-glz.c b/gtk/decode-glz.c
new file mode 100644
index 0000000..9ee238f
--- /dev/null
+++ b/gtk/decode-glz.c
@@ -0,0 +1,402 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include "decode.h"
+
+/* spice/common */
+#include "canvas_utils.h"
+
+struct glz_image_hdr {
+ uint64_t id;
+ LzImageType type;
+ uint32_t width;
+ uint32_t height;
+ uint32_t gross_pixels;
+ bool top_down;
+ uint32_t win_head_dist;
+};
+
+struct glz_image {
+ struct glz_image_hdr hdr;
+ pixman_image_t *surface;
+ uint8_t *data;
+};
+
+static struct glz_image *glz_image_new(struct glz_image_hdr *hdr,
+ int type, void *opaque)
+{
+ struct glz_image *img;
+
+ assert(type == LZ_IMAGE_TYPE_RGB32 || type == LZ_IMAGE_TYPE_RGBA);
+
+ img = spice_new0(struct glz_image, 1);
+ img->hdr = *hdr;
+ img->surface = alloc_lz_image_surface
+ (opaque, type == LZ_IMAGE_TYPE_RGBA ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
+ img->hdr.width, img->hdr.height, img->hdr.gross_pixels, img->hdr.top_down);
+ pixman_image_ref(img->surface);
+ img->data = (uint8_t *)pixman_image_get_data(img->surface);
+ if (!img->hdr.top_down) {
+ img->data = img->data - img->hdr.width * (img->hdr.height - 1) * 4;
+ }
+ return img;
+}
+
+static void glz_image_destroy(struct glz_image *img)
+{
+ pixman_image_unref(img->surface);
+ free(img);
+}
+
+/* ------------------------------------------------------------------ */
+
+#define INIT_IMAGES_CAPACITY 100
+#define WIN_OVERFLOW_FACTOR 1.5
+#define WIN_REALLOC_FACTOR 1.5
+
+struct SpiceGlzDecoderWindow {
+ struct glz_image **images;
+ uint32_t nimages;
+ uint64_t oldest;
+};
+
+static void glz_decoder_window_resize(SpiceGlzDecoderWindow *w)
+{
+ struct glz_image **new_images;
+ int i, new_slot;
+
+ fprintf(stderr, "%s: array resize %d -> %d\n", __FUNCTION__,
+ w->nimages, w->nimages * 2);
+ new_images = spice_new0(struct glz_image*, w->nimages * 2);
+ for (i = 0; i < w->nimages; i++) {
+ new_slot = w->images[i]->hdr.id % (w->nimages * 2);
+ new_images[new_slot] = w->images[i];
+ }
+ free(w->images);
+ w->images = new_images;
+ w->nimages *= 2;
+}
+
+static void glz_decoder_window_add(SpiceGlzDecoderWindow *w,
+ struct glz_image *img)
+{
+ int slot = img->hdr.id % w->nimages;
+
+ if (w->images[slot]) {
+ /* need more space */
+ glz_decoder_window_resize(w);
+ slot = img->hdr.id % w->nimages;
+ }
+ w->images[slot] = img;
+}
+
+static void *glz_decoder_window_bits(SpiceGlzDecoderWindow *w, uint64_t id,
+ uint32_t dist, uint32_t offset)
+{
+ int slot = (id - dist) % w->nimages;
+
+ if (!w->images[slot]) {
+ fprintf(stderr, "%s: slot %d empty\n", __FUNCTION__, slot);
+ exit(1);
+ }
+ if (w->images[slot]->hdr.id != id - dist) {
+ fprintf(stderr, "%s: oops. ID mismatch\n", __FUNCTION__);
+ exit(1);
+ }
+ if (w->images[slot]->hdr.gross_pixels < offset) {
+ fprintf(stderr, "%s: offset overflow\n", __FUNCTION__);
+ exit(1);
+ }
+ return w->images[slot]->data + offset * 4;
+}
+
+static void glz_decoder_window_release(SpiceGlzDecoderWindow *w,
+ uint64_t oldest)
+{
+ int slot;
+
+ while (w->oldest < oldest) {
+ slot = w->oldest % w->nimages;
+ glz_image_destroy(w->images[slot]);
+ w->images[slot] = NULL;
+ w->oldest++;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+
+typedef struct GlibGlzDecoder {
+ SpiceGlzDecoder base;
+ uint8_t *in_start;
+ uint8_t *in_now;
+ SpiceGlzDecoderWindow *window;
+ struct glz_image_hdr image;
+} GlibGlzDecoder;
+
+/*
+ * Give hints to the compiler for branch prediction optimization.
+ */
+#if defined(__GNUC__) && (__GNUC__ > 2)
+#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
+#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
+#else
+#define LZ_EXPECT_CONDITIONAL(c) (c)
+#define LZ_UNEXPECT_CONDITIONAL(c) (c)
+#endif
+
+
+#ifdef __GNUC__
+#define ATTR_PACKED __attribute__ ((__packed__))
+#else
+#define ATTR_PACKED
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+/*
+ * the palette images will be treated as one byte pixels. Their width
+ * should be transformed accordingly.
+ */
+typedef struct ATTR_PACKED one_byte_pixel_t {
+ uint8_t a;
+} one_byte_pixel_t;
+
+typedef struct ATTR_PACKED rgb32_pixel_t {
+ uint8_t b;
+ uint8_t g;
+ uint8_t r;
+ uint8_t pad;
+} rgb32_pixel_t;
+
+typedef struct ATTR_PACKED rgb24_pixel_t {
+ uint8_t b;
+ uint8_t g;
+ uint8_t r;
+} rgb24_pixel_t;
+
+typedef uint16_t rgb16_pixel_t;
+
+#ifndef __GNUC__
+#pragma pack(pop)
+#endif
+
+#undef ATTR_PACKED
+
+#define LZ_PLT
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT8
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT4_BE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT4_LE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT1_BE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_PLT
+#define PLT1_LE
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+
+#define LZ_RGB16
+#include "decode-glz-tmpl.c"
+#define LZ_RGB16
+#define TO_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB24
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB32
+#include "decode-glz-tmpl.c"
+
+#define LZ_RGB_ALPHA
+#include "decode-glz-tmpl.c"
+
+#undef LZ_UNEXPECT_CONDITIONAL
+#undef LZ_EXPECT_CONDITIONAL
+
+typedef size_t (*decode_function)(SpiceGlzDecoderWindow *window,
+ uint8_t* in_buf, uint8_t *out_buf, int size,
+ uint64_t id, SpicePalette *plt);
+
+// ordered according to LZ_IMAGE_TYPE
+const decode_function DECODE_TO_RGB32[] = {
+ NULL,
+ glz_plt1_le_to_rgb32_decode,
+ glz_plt1_be_to_rgb32_decode,
+ glz_plt4_le_to_rgb32_decode,
+ glz_plt4_be_to_rgb32_decode,
+ glz_plt8_to_rgb32_decode,
+ glz_rgb16_to_rgb32_decode,
+ glz_rgb32_decode,
+ glz_rgb32_decode,
+ glz_rgb32_decode
+};
+
+const decode_function DECODE_TO_SAME[] = {
+ NULL,
+ glz_plt_decode,
+ glz_plt_decode,
+ glz_plt_decode,
+ glz_plt_decode,
+ glz_plt_decode,
+ glz_rgb16_decode,
+ glz_rgb24_decode,
+ glz_rgb32_decode,
+ glz_rgb32_decode
+};
+
+static uint32_t decode_32(GlibGlzDecoder *d)
+{
+ uint32_t word = 0;
+ word |= *(d->in_now++);
+ word <<= 8;
+ word |= *(d->in_now++);
+ word <<= 8;
+ word |= *(d->in_now++);
+ word <<= 8;
+ word |= *(d->in_now++);
+ return word;
+}
+
+static uint64_t decode_64(GlibGlzDecoder *d)
+{
+ uint64_t long_word = decode_32(d);
+ long_word <<= 32;
+ long_word |= decode_32(d);
+ return long_word;
+}
+
+static void decode_header(GlibGlzDecoder *d)
+{
+ uint32_t magic;
+ uint32_t version;
+ uint32_t stride;
+ uint8_t tmp;
+
+ magic = decode_32(d);
+ if (magic != LZ_MAGIC) {
+ fprintf(stderr, "%s: bad lz magic\n", __FUNCTION__);
+ exit(1);
+ }
+
+ version = decode_32(d);
+ if (version != LZ_VERSION) {
+ fprintf(stderr, "%s: bad lz version\n", __FUNCTION__);
+ exit(1);
+ }
+
+ tmp = *(d->in_now++);
+
+ d->image.type = (LzImageType)(tmp & LZ_IMAGE_TYPE_MASK);
+ d->image.top_down = (tmp >> LZ_IMAGE_TYPE_LOG) ? true : false;
+ d->image.width = decode_32(d);
+ d->image.height = decode_32(d);
+ stride = decode_32(d);
+
+ if (IS_IMAGE_TYPE_PLT[d->image.type]) {
+ d->image.gross_pixels = stride * PLT_PIXELS_PER_BYTE[d->image.type]
+ * d->image.height;
+ } else {
+ d->image.gross_pixels = d->image.width * d->image.height;
+ }
+
+ d->image.id = decode_64(d);
+ d->image.win_head_dist = decode_32(d);
+
+#if 0
+ fprintf(stderr, "%s: %dx%d, id %" PRId64 ", ref %" PRId64 "\n",
+ __FUNCTION__,
+ d->image.width, d->image.height, d->image.id,
+ d->image.id - d->image.win_head_dist);
+#endif
+}
+
+static void decode(SpiceGlzDecoder *decoder,
+ uint8_t *data, SpicePalette *palette,
+ void *usr_data)
+{
+ GlibGlzDecoder *d = SPICE_CONTAINEROF(decoder, GlibGlzDecoder, base);
+ int out_size;
+ LzImageType decoded_type;
+ struct glz_image *decoded_image;
+ size_t n_in_bytes_decoded;
+
+ d->in_start = data;
+ d->in_now = data;
+
+ decode_header(d);
+
+ out_size = d->image.gross_pixels << 2;
+
+ if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
+ decoded_type = LZ_IMAGE_TYPE_RGBA;
+ } else {
+ decoded_type = LZ_IMAGE_TYPE_RGB32;
+ }
+
+ decoded_image = glz_image_new(&d->image, decoded_type, usr_data);
+
+ n_in_bytes_decoded = DECODE_TO_RGB32[d->image.type]
+ (d->window, d->in_now, decoded_image->data,
+ d->image.gross_pixels, d->image.id, palette);
+
+ d->in_now += n_in_bytes_decoded;
+
+ if (d->image.type == LZ_IMAGE_TYPE_RGBA) {
+ glz_rgb_alpha_decode(d->window, d->in_now, decoded_image->data,
+ d->image.gross_pixels, d->image.id, palette);
+ }
+
+ glz_decoder_window_release(d->window, d->image.id - d->image.win_head_dist);
+ glz_decoder_window_add(d->window, decoded_image);
+}
+
+/* ------------------------------------------------------------------ */
+
+static SpiceGlzDecoderOps glz_decoder_ops = {
+ .decode = decode,
+};
+
+SpiceGlzDecoderWindow *glz_decoder_window_new(void)
+{
+ SpiceGlzDecoderWindow *w = spice_new0(SpiceGlzDecoderWindow, 1);
+
+ w->nimages = 16;
+ w->images = spice_new0(struct glz_image*, w->nimages);
+ return w;
+}
+
+void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w)
+{
+ free(w);
+}
+
+SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w)
+{
+ GlibGlzDecoder *d = spice_new0(GlibGlzDecoder, 1);
+ d->base.ops = &glz_decoder_ops;
+ d->window = w;
+ return &d->base;
+}
+
+void glz_decoder_destroy(SpiceGlzDecoder *d)
+{
+ free(d);
+}
diff --git a/gtk/decode-jpeg.c b/gtk/decode-jpeg.c
new file mode 100644
index 0000000..9370728
--- /dev/null
+++ b/gtk/decode-jpeg.c
@@ -0,0 +1 @@
+#include "decode.h"
diff --git a/gtk/decode-zlib.c b/gtk/decode-zlib.c
new file mode 100644
index 0000000..9370728
--- /dev/null
+++ b/gtk/decode-zlib.c
@@ -0,0 +1 @@
+#include "decode.h"
diff --git a/gtk/decode.h b/gtk/decode.h
new file mode 100644
index 0000000..5a7ac93
--- /dev/null
+++ b/gtk/decode.h
@@ -0,0 +1,9 @@
+#include "canvas_base.h"
+
+typedef struct SpiceGlzDecoderWindow SpiceGlzDecoderWindow;
+
+SpiceGlzDecoderWindow *glz_decoder_window_new(void);
+void glz_decoder_window_destroy(SpiceGlzDecoderWindow *w);
+
+SpiceGlzDecoder *glz_decoder_new(SpiceGlzDecoderWindow *w);
+void glz_decoder_destroy(SpiceGlzDecoder *d);
diff --git a/gtk/keymap-gen.pl b/gtk/keymap-gen.pl
new file mode 100755
index 0000000..fd96bdf
--- /dev/null
+++ b/gtk/keymap-gen.pl
@@ -0,0 +1,202 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Text::CSV;
+
+my %names = (
+ linux => [],
+ osx => []
+);
+
+my %namecolumns = (
+ linux => 0,
+ osx => 2,
+ win32 => 10,
+ );
+
+# Base data sources:
+#
+# linux: Linux: linux/input.h (master set)
+# osx: OS-X: Carbon/HIToolbox/Events.h (manually mapped)
+# atset1: AT Set 1: linux/drivers/input/keyboard/atkbd.c (atkbd_set2_keycode + atkbd_unxlate_table)
+# atset2: AT Set 2: linux/drivers/input/keyboard/atkbd.c (atkbd_set2_keycode)
+# atset3: AT Set 3: linux/drivers/input/keyboard/atkbd.c (atkbd_set3_keycode)
+# xt: XT: linux/drivers/input/keyboard/xt.c (xtkbd_keycode)
+# xtkbd: Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes)
+# usb: USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode)
+# win32: Win32: mingw32/winuser.h (manually mapped)
+# xwinxt: XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} (xt + manually transcribed)
+# xkbdxt: XKBD XT: xf86-input-keyboard/src/at_scancode.c (xt + manually transcribed)
+#
+# Derived data sources
+#
+# xorgevdev: Xorg + evdev: linux + an offset
+# xorgkbd: Xorg + kbd: xkbdxt + an offset
+# xorgxquartz: Xorg + OS-X: osx + an offset
+# xorgxwin: Xorg + Cygwin: xwinxt + an offset
+# rfb: XT over RFB: xtkbd + special re-encoding of high bit
+
+my @basemaps = qw(linux osx atset1 atset2 atset3 xt xtkbd usb win32 xwinxt xkbdxt);
+my @derivedmaps = qw(xorgevdev xorgkbd xorgxquartz xorgxwin rfb);
+my @maps = (@basemaps, @derivedmaps);
+
+my %maps;
+
+foreach my $map (@maps) {
+ $maps{$map} = [ [], [] ];
+}
+my %mapcolumns = (
+ osx => 3,
+ atset1 => 4,
+ atset2 => 5,
+ atset3 => 6,
+ xt => 7,
+ xtkbd => 8,
+ usb => 9,
+ win32 => 11,
+ xwinxt => 12,
+ xkbdxt => 13,
+ );
+
+sub help {
+ my $msg = shift;
+ print $msg;
+ print "\n";
+ print "Valid keymaps are:\n";
+ print "\n";
+ foreach my $name (sort { $a cmp $b } keys %maps) {
+ print " $name\n";
+ }
+ print "\n";
+ exit (1);
+}
+
+if ($#ARGV != 2) {
+ help("syntax: $0 KEYMAPS SRCMAP DSTMAP\n");
+}
+
+my $keymaps = shift @ARGV;
+my $src = shift @ARGV;
+my $dst = shift @ARGV;
+
+help("$src is not a known keymap\n") unless exists $maps{$src};
+help("$dst is not a known keymap\n") unless exists $maps{$dst};
+
+
+open CSV, $keymaps
+ or die "cannot read $keymaps: $!";
+
+my $csv = Text::CSV->new();
+# Discard column headings
+$csv->getline(\*CSV);
+
+my $row;
+while ($row = $csv->getline(\*CSV)) {
+ my $linux = $row->[1];
+
+ $linux = hex($linux) if $linux =~ /0x/;
+
+ my $to = $maps{linux}->[0];
+ my $from = $maps{linux}->[1];
+ $to->[$linux] = $linux;
+ $from->[$linux] = $linux;
+
+ foreach my $name (keys %namecolumns) {
+ my $col = $namecolumns{$name};
+ my $val = $row->[$col];
+
+ $val = "" unless defined $val;
+
+ $names{$name}->[$linux] = $val;
+ }
+
+ foreach my $name (keys %mapcolumns) {
+ my $col = $mapcolumns{$name};
+ my $val = $row->[$col];
+
+ $val = 0 unless $val;
+ $val = hex($val) if $val =~ /0x/;
+
+ $to = $maps{$name}->[0];
+ $from = $maps{$name}->[1];
+ $to->[$linux] = $val;
+ $from->[$val] = $linux;
+ }
+
+ # XXX there are some special cases in kbd to handle
+ # Xorg KBD driver is the Xorg KBD XT codes offset by +8
+ # The XKBD XT codes are the same as normal XT codes
+ # for values <= 83, and completely made up for extended
+ # scancodes :-(
+ ($to, $from) = @{$maps{xorgkbd}};
+ $to->[$linux] = $maps{xkbdxt}->[0]->[$linux] + 8;
+ $from->[$to->[$linux]] = $linux;
+
+ # Xorg evdev is simply Linux keycodes offset by +8
+ ($to, $from) = @{$maps{xorgevdev}};
+ $to->[$linux] = $linux + 8;
+ $from->[$to->[$linux]] = $linux;
+
+ # Xorg XQuartz is simply OS-X keycodes offset by +8
+ ($to, $from) = @{$maps{xorgxquartz}};
+ $to->[$linux] = $maps{osx}->[0]->[$linux] + 8;
+ $from->[$to->[$linux]] = $linux;
+
+ # RFB keycodes are XT kbd keycodes with a slightly
+ # different encoding of 0xe0 scan codes. RFB uses
+ # the high bit of the first byte, instead of the low
+ # bit of the second byte.
+ ($to, $from) = @{$maps{rfb}};
+ my $xtkbd = $maps{xtkbd}->[0]->[$linux];
+ $to->[$linux] = $xtkbd ? (($xtkbd & 0x100)>>1) | ($xtkbd & 0x7f) : 0;
+ $from->[$to->[$linux]] = $linux;
+
+ # Xorg Cygwin is the Xorg Cygwin XT codes offset by +8
+ # The Cygwin XT codes are the same as normal XT codes
+ # for values <= 83, and completely made up for extended
+ # scancodes :-(
+ ($to, $from) = @{$maps{xorgxwin}};
+ $to->[$linux] = $maps{xwinxt}->[0]->[$linux] + 8;
+ $from->[$to->[$linux]] = $linux;
+
+# print $linux, "\n";
+}
+
+close CSV;
+
+my $srcmap = $maps{$src}->[1];
+my $dstmap = $maps{$dst}->[0];
+
+printf "static const guint16 keymap_%s2%s[] = {\n", $src, $dst;
+
+for (my $i = 0 ; $i <= $#{$srcmap} ; $i++) {
+ my $linux = $srcmap->[$i] || 0;
+ my $j = $dstmap->[$linux];
+ next unless $linux && $j;
+
+ my $srcname = $names{$src}->[$linux] if exists $names{$src};
+ my $dstname = $names{$dst}->[$linux] if exists $names{$dst};
+ my $vianame = $names{linux}->[$linux] unless $src eq "linux" || $dst eq "linux";
+
+ $srcname = "" unless $srcname;
+ $dstname = "" unless $dstname;
+ $vianame = "" unless $vianame;
+ $srcname = " ($srcname)" if $srcname;
+ $dstname = " ($dstname)" if $dstname;
+ $vianame = " ($vianame)" if $vianame;
+
+ my $comment;
+ if ($src ne "linux" && $dst ne "linux") {
+ $comment = sprintf "%d%s => %d%s via %d%s", $i, $srcname, $j, $dstname, $linux, $vianame;
+ } else {
+ $comment = sprintf "%d%s => %d%s", $i, $srcname, $j, $dstname;
+ }
+
+ my $data = sprintf "[0x%x] = 0x%x,", $i, $j;
+
+ printf " %-20s /* %s */\n", $data, $comment;
+}
+
+print "};\n";
diff --git a/gtk/keymaps.csv b/gtk/keymaps.csv
new file mode 100644
index 0000000..275dc17
--- /dev/null
+++ b/gtk/keymaps.csv
@@ -0,0 +1,461 @@
+"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode",XT,"XT KBD","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT"
+KEY_RESERVED,0,,0x0,,,,,,,,,,
+KEY_ESC,1,Escape,0x35,1,118,8,1,1,41,VK_ESCAPE,0x1b,1,1
+KEY_1,2,ANSI_1,0x12,2,22,22,2,2,30,VK_1,0x31,2,2
+KEY_2,3,ANSI_2,0x13,3,30,30,3,3,31,VK_2,0x32,3,3
+KEY_3,4,ANSI_3,0x14,4,38,38,4,4,32,VK_3,0x33,4,4
+KEY_4,5,ANSI_4,0x15,5,37,37,5,5,33,VK_4,0x34,5,5
+KEY_5,6,ANSI_5,0x17,6,46,46,6,6,34,VK_5,0x35,6,6
+KEY_6,7,ANSI_6,0x16,7,54,54,7,7,35,VK_6,0x36,7,7
+KEY_7,8,ANSI_7,0x1a,8,61,61,8,8,36,VK_7,0x37,8,8
+KEY_8,9,ANSI_8,0x1c,9,62,62,9,9,37,VK_8,0x38,9,9
+KEY_9,10,ANSI_9,0x19,10,70,70,10,10,38,VK_9,0x39,10,10
+KEY_0,11,ANSI_0,0x1d,11,69,69,11,11,39,VK_0,0x30,11,11
+KEY_MINUS,12,ANSI_Minus,0x1b,12,78,78,12,12,45,VK_OEM_MINUS,0xbd,12,12
+KEY_EQUAL,13,ANSI_Equal,0x18,13,85,85,13,13,46,VK_OEM_PLUS,0xbb,13,13
+KEY_BACKSPACE,14,Delete,0x33,14,102,102,14,14,42,VK_BACK,0x08,14,14
+KEY_TAB,15,Tab,0x30,15,13,13,15,15,43,VK_TAB,0x09,15,15
+KEY_Q,16,ANSI_Q,0xc,16,21,21,16,16,20,VK_Q,0x51,16,16
+KEY_W,17,ANSI_W,0xd,17,29,29,17,17,26,VK_W,0x57,17,17
+KEY_E,18,ANSI_E,0xe,18,36,36,18,18,8,VK_E,0x45,18,18
+KEY_R,19,ANSI_R,0xf,19,45,45,19,19,21,VK_R,0x52,19,19
+KEY_T,20,ANSI_T,0x11,20,44,44,20,20,23,VK_T,0x54,20,20
+KEY_Y,21,ANSI_Y,0x10,21,53,53,21,21,28,VK_Y,0x59,21,21
+KEY_U,22,ANSI_U,0x20,22,60,60,22,22,24,VK_U,0x55,22,22
+KEY_I,23,ANSI_I,0x22,23,67,67,23,23,12,VK_I,0x49,23,23
+KEY_O,24,ANSI_O,0x1f,24,68,68,24,24,18,VK_O,0x4f,24,24
+KEY_P,25,ANSI_P,0x23,25,77,77,25,25,19,VK_P,0x50,25,25
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,26,84,84,26,26,47,VK_OEM_4,0xdb,26,26
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,27,91,91,27,27,48,VK_OEM_6,0xdd,27,27
+KEY_ENTER,28,Return,0x24,28,90,90,28,28,40,VK_RETURN,0x0d,28,28
+KEY_LEFTCTRL,29,Control,0x3b,29,20,17,29,29,224,VK_LCONTROL,0xa2,29,29
+KEY_A,30,ANSI_A,0x0,30,28,28,30,30,4,VK_A,0x41,30,30
+KEY_S,31,ANSI_S,0x1,31,27,27,31,31,22,VK_S,0x53,31,31
+KEY_D,32,ANSI_D,0x2,32,35,35,32,32,7,VK_D,0x44,32,32
+KEY_F,33,ANSI_F,0x3,33,43,43,33,33,9,VK_F,0x46,33,33
+KEY_G,34,ANSI_G,0x5,34,52,52,34,34,10,VK_G,0x47,34,34
+KEY_H,35,ANSI_H,0x4,35,51,51,35,35,11,VK_H,0x48,35,35
+KEY_J,36,ANSI_J,0x26,36,59,59,36,36,13,VK_J,0x4a,36,36
+KEY_K,37,ANSI_K,0x28,37,66,66,37,37,14,VK_K,0x4b,37,37
+KEY_L,38,ANSI_L,0x25,38,75,75,38,38,15,VK_L,0x4c,38,38
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,39,76,76,39,39,51,VK_OEM_1,0xba,39,39
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,40,82,82,40,40,52,VK_OEM_2,0xbf,40,40
+KEY_GRAVE,41,ANSI_Grave,0x32,41,14,14,41,41,53,VK_OEM_3,0xc0,41,41
+KEY_LEFTSHIFT,42,Shift,0x38,42,18,18,42,42,225,VK_LSHIFT,0xa0,42,42
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,43,93,93,43,43,50,VK_OEM_5,0xdc,43,43
+KEY_Z,44,ANSI_Z,0x6,44,26,26,44,44,29,VK_Z,0x5a,44,44
+KEY_X,45,ANSI_X,0x7,45,34,34,45,45,27,VK_X,0x58,45,45
+KEY_C,46,ANSI_C,0x8,46,33,33,46,46,6,VK_C,0x43,46,46
+KEY_V,47,ANSI_V,0x9,47,42,42,47,47,25,VK_V,0x56,47,47
+KEY_B,48,ANSI_B,0xb,48,50,50,48,48,5,VK_B,0x42,48,48
+KEY_N,49,ANSI_N,0x2d,49,49,49,49,49,17,VK_N,0x4e,49,49
+KEY_M,50,ANSI_M,0x2e,50,58,58,50,50,16,VK_M,0x4d,50,50
+KEY_COMMA,51,ANSI_Comma,0x2b,51,65,65,51,51,54,VK_OEM_COMMA,0xbc,51,51
+KEY_DOT,52,ANSI_Period,0x2f,52,73,73,52,52,55,VK_OEM_PERIOD,0xbe,52,52
+KEY_SLASH,53,ANSI_Slash,0x2c,53,74,74,53,53,56,VK_OEM_2,0xbf,53,53
+KEY_RIGHTSHIFT,54,RightShift,0x3c,54,89,89,54,54,229,VK_RSHIFT,0xa1,54,54
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,55,124,126,55,55,85,VK_MULTIPLY,0x6a,55,55
+KEY_LEFTALT,56,Option,0x3a,56,17,25,56,56,226,VK_LMENU,0xa4,56,56
+KEY_SPACE,57,Space,0x31,57,41,41,57,57,44,VK_SPACE,0x20,57,57
+KEY_CAPSLOCK,58,CapsLock,0x39,58,88,20,58,58,57,VK_CAPITAL,0x14,58,58
+KEY_F1,59,F1,0x7a,59,5,7,59,59,58,VK_F1,0x70,59,59
+KEY_F2,60,F2,0x78,60,6,15,60,60,59,VK_F2,0x71,60,60
+KEY_F3,61,F3,0x63,61,4,23,61,61,60,VK_F3,0x72,61,61
+KEY_F4,62,F4,0x76,62,12,31,62,62,61,VK_F4,0x73,62,62
+KEY_F5,63,F5,0x60,63,3,39,63,63,62,VK_F5,0x74,63,63
+KEY_F6,64,F6,0x61,64,11,47,64,64,63,VK_F6,0x75,64,64
+KEY_F7,65,F7,0x62,65,259,55,65,65,64,VK_F7,0x76,65,65
+KEY_F8,66,F8,0x64,66,10,63,66,66,65,VK_F8,0x77,66,66
+KEY_F9,67,F9,0x65,67,1,71,67,67,66,VK_F9,0x78,67,67
+KEY_F10,68,F10,0x6d,68,9,79,68,68,67,VK_F10,0x79,68,68
+KEY_NUMLOCK,69,,,69,119,118,69,69,83,VK_NUMLOCK,0x90,69,69
+KEY_SCROLLLOCK,70,,,70,126,95,70,70,71,VK_SCROLL,0x91,70,70
+KEY_KP7,71,ANSI_Keypad7,0x59,71,108,108,71,71,95,VK_NUMPAD7,0x67,71,71
+KEY_KP8,72,ANSI_Keypad8,0x5b,72,117,117,72,72,96,VK_NUMPAD8,0x68,72,72
+KEY_KP9,73,ANSI_Keypad9,0x5c,73,125,125,73,73,97,VK_NUMPAD9,0x69,73,73
+KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,74,123,132,74,74,86,VK_SUBTRACT,0x6d,74,74
+KEY_KP4,75,ANSI_Keypad4,0x56,75,107,107,75,75,92,VK_NUMPAD4,0x64,75,75
+KEY_KP5,76,ANSI_Keypad5,0x57,76,115,115,76,76,93,VK_NUMPAD5,0x65,76,76
+KEY_KP6,77,ANSI_Keypad6,0x58,77,116,116,77,77,94,VK_NUMPAD6,0x66,77,77
+KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,78,121,124,78,78,87,VK_ADD,0x6b,78,78
+KEY_KP1,79,ANSI_Keypad1,0x53,79,105,105,79,79,89,VK_NUMPAD1,0x61,79,79
+KEY_KP2,80,ANSI_Keypad2,0x54,80,114,114,80,80,90,VK_NUMPAD2,0x62,80,80
+KEY_KP3,81,ANSI_Keypad3,0x55,81,122,122,81,81,91,VK_NUMPAD3,0x63,81,81
+KEY_KP0,82,ANSI_Keypad0,0x52,82,112,112,82,82,98,VK_NUMPAD0,0x60,82,82
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,83,113,113,83,83,99,VK_DECIMAL,0x6e,83,83
+,84,,,,,,,84,,,,,
+KEY_ZENKAKUHANKAKU,85,,,118,95,,,118,148,,,,
+KEY_102ND,86,,,86,97,19,,86,100,VK_OEM_102,0xe1,,
+KEY_F11,87,F11,0x67,87,120,86,101,87,68,VK_F11,0x7a,,
+KEY_F12,88,F12,0x6f,88,7,94,102,88,69,VK_F12,0x7b,,
+KEY_RO,89,,,115,81,,,115,135,,,,
+KEY_KATAKANA,90,JIS_Kana????,0x68,120,99,,,120,146,VK_KANA,0x15,,
+KEY_HIRAGANA,91,,,119,98,,,119,147,,,,
+KEY_HENKAN,92,,,121,100,134,,121,138,,,,
+KEY_KATAKANAHIRAGANA,93,,,112,19,135,,,136,,,0xc8,0xc8
+KEY_MUHENKAN,94,,,123,103,133,,123,139,,,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,92,39,,,92,140,,,,
+KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,,158,121,,284,88,,,0x64,0x64
+KEY_RIGHTCTRL,97,RightControl,0x3e,,,88,,285,228,VK_RCONTROL,0xa3,0x65,0x65
+KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,,181,119,,309,84,VK_DIVIDE,0x6f,0x68,0x68
+KEY_SYSRQ,99,,,84,260,87,,84,70,"VK_SNAPSHOT ???",0x2c,0x67,0x67
+KEY_RIGHTALT,100,RightOption,0x3d,,,57,,312,230,VK_RMENU,0xa5,0x69,0x69
+KEY_LINEFEED,101,,,,,,,91,,,,,
+KEY_HOME,102,Home,0x73,,224,110,,327,74,VK_HOME,0x24,0x59,0x59
+KEY_UP,103,UpArrow,0x7e,,236,99,109,328,82,VK_UP,0x26,0x5a,0x5a
+KEY_PAGEUP,104,PageUp,0x74,,201,111,,329,75,VK_PRIOR,0x21,0x5b,0x5b
+KEY_LEFT,105,LeftArrow,0x7b,,203,97,111,331,80,VK_LEFT,0x25,0x5c,0x5c
+KEY_RIGHT,106,RightArrow,0x7c,,205,106,112,333,79,VK_RIGHT,0x27,0x5e,0x5e
+KEY_END,107,End,0x77,,225,101,,335,77,VK_END,0x23,0x5f,0x5f
+KEY_DOWN,108,DownArrow,0x7d,,254,96,110,336,81,VK_DOWN,0x28,0x60,0x60
+KEY_PAGEDOWN,109,PageDown,0x79,,243,109,,337,78,VK_NEXT,0x22,0x61,0x61
+KEY_INSERT,110,,,,210,103,107,338,73,VK_INSERT,0x2d,0x62,0x62
+KEY_DELETE,111,ForwardDelete,0x75,,244,100,108,339,76,VK_DELETE,0x2e,0x63,0x63
+KEY_MACRO,112,,,,239,142,,367,,,,,
+KEY_MUTE,113,Mute,0x4a,,251,156,,288,239,VK_VOLUME_MUTE,0xad,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,,,157,,302,238,VK_VOLUME_DOWN,0xae,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,,233,149,,304,237,VK_VOLUME_UP,0xaf,,
+KEY_POWER,116,,,,,,,350,102,,,,
+KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,89,15,,,89,103,,,0x76,0x76
+KEY_KPPLUSMINUS,118,,,,206,,,334,,,,,
+KEY_PAUSE,119,,,,198,98,,326,72,VK_PAUSE,0x013,0x66,0x66
+KEY_SCALE,120,,,,,,,267,,,,,
+KEY_KPCOMMA,121,ANSI_KeypadClear????,0x47,126,109,,,126,133,VK_SEPARATOR??,0x6c,,
+KEY_HANGEUL,122,,,,,,,,144,VK_HANGEUL,0x15,,
+KEY_HANJA,123,,,,,,,269,145,VK_HANJA,0x19,,
+KEY_YEN,124,JIS_Yen,0x5d,125,106,,,125,137,,,0x7d,0x7d
+KEY_LEFTMETA,125,Command,0x37,,,139,,347,227,VK_LWIN,0x5b,0x6b,0x6b
+KEY_RIGHTMETA,126,,,,,140,,348,231,VK_RWIN,0x5c,0x6c,0x6c
+KEY_COMPOSE,127,Function,0x3f,,,141,,349,101,VK_APPS,0x5d,0x6d,0x6d
+KEY_STOP,128,,,,,10,,360,243,VK_BROWSER_STOP,0xa9,,
+KEY_AGAIN,129,,,,,11,,261,121,,,,
+KEY_PROPS,130,,,,,12,,262,118,,,,
+KEY_UNDO,131,,,,,16,,263,122,,,,
+KEY_FRONT,132,,,,,,,268,119,,,,
+KEY_COPY,133,,,,,24,,376,124,,,,
+KEY_OPEN,134,,,,,32,,100,116,,,,
+KEY_PASTE,135,,,,,40,,101,125,,,,
+KEY_FIND,136,,,,,48,,321,244,,,,
+KEY_CUT,137,,,,,56,,316,123,,,,
+KEY_HELP,138,,,,,9,,373,117,VK_HELP,0x2f,,
+KEY_MENU,139,,,,,145,,286,,,,,
+KEY_CALC,140,,,,174,163,,289,251,,,,
+KEY_SETUP,141,,,,,,,102,,,,,
+KEY_SLEEP,142,,,,,,,351,248,VK_SLEEP,0x5f,,
+KEY_WAKEUP,143,,,,,,,355,,,,,
+KEY_FILE,144,,,,,,,103,,,,,
+KEY_SENDFILE,145,,,,,,,104,,,,,
+KEY_DELETEFILE,146,,,,,,,105,,,,,
+KEY_XFER,147,,,,,162,,275,,,,,
+KEY_PROG1,148,,,,,160,,287,,,,,
+KEY_PROG2,149,,,,,161,,279,,,,,
+KEY_WWW,150,,,,,,,258,240,,,,
+KEY_MSDOS,151,,,,,,,106,,,,,
+KEY_SCREENLOCK,152,,,,,150,,274,249,,,,
+KEY_DIRECTION,153,,,,,,,107,,,,,
+KEY_CYCLEWINDOWS,154,,,,,155,,294,,,,,
+KEY_MAIL,155,,,,,,,364,,,,,
+KEY_BOOKMARKS,156,,,,,,,358,,,,,
+KEY_COMPUTER,157,,,,,,,363,,,,,
+KEY_BACK,158,,,,,,,362,241,VK_BROWSER_BACK,0xa6,,
+KEY_FORWARD,159,,,,,,,361,242,VK_BROWSER_FORWARD,0xa7,,
+KEY_CLOSECD,160,,,,,154,,291,,,,,
+KEY_EJECTCD,161,,,,,,,108,236,,,,
+KEY_EJECTCLOSECD,162,,,,,,,381,,,,,
+KEY_NEXTSONG,163,,,,241,147,,281,235,VK_MEDIA_NEXT_TRACK,0xb0,,
+KEY_PLAYPAUSE,164,,,,173,,,290,232,VK_MEDIA_PLAY_PAUSE,0xb3,,
+KEY_PREVIOUSSONG,165,,,,250,148,,272,234,VK_MEDIA_PREV_TRACK,0xb1,,
+KEY_STOPCD,166,,,,164,152,,292,233,VK_MEDIA_STOP,0xb2,,
+KEY_RECORD,167,,,,,158,,305,,,,,
+KEY_REWIND,168,,,,,159,,280,,,,,
+KEY_PHONE,169,,,,,,,99,,,,,
+KEY_ISO,170,ISO_Section,0xa,,,,,112,,,,,
+KEY_CONFIG,171,,,,,,,257,,,,,
+KEY_HOMEPAGE,172,,,,178,151,,306,,VK_BROWSER_HOME,0xac,,
+KEY_REFRESH,173,,,,,,,359,250,VK_BROWSER_REFRESH,0xa8,,
+KEY_EXIT,174,,,,,,,113,,,,,
+KEY_MOVE,175,,,,,,,114,,,,,
+KEY_EDIT,176,,,,,,,264,247,,,,
+KEY_SCROLLUP,177,,,,,,,117,245,,,,
+KEY_SCROLLDOWN,178,,,,,,,271,246,,,,
+KEY_KPLEFTPAREN,179,,,,,,,374,182,,,,
+KEY_KPRIGHTPAREN,180,,,,,,,379,183,,,,
+KEY_NEW,181,,,,,,,265,,,,,
+KEY_REDO,182,,,,,,,266,,,,,
+KEY_F13,183,F13,0x69,93,47,127,,93,104,VK_F13,0x7c,0x6e,0x6e
+KEY_F14,184,F14,0x6b,94,55,128,,94,105,VK_F14,0x7d,0x6f,0x6f
+KEY_F15,185,F15,0x71,95,63,129,,95,106,VK_F15,0x7e,0x70,0x70
+KEY_F16,186,F16,0x6a,,,130,,85,107,VK_F16,0x7f,0x71,0x71
+KEY_F17,187,F17,0x40,,,131,,259,108,VK_F17,0x80,0x72,0x72
+KEY_F18,188,F18,0x4f,,,,,375,109,VK_F18,0x81,,
+KEY_F19,189,F19,0x50,,,,,260,110,VK_F19,0x82,,
+KEY_F20,190,F20,0x5a,,,,,90,111,VK_F20,0x83,,
+KEY_F21,191,,,,,,,116,112,VK_F21,0x84,,
+KEY_F22,192,,,,,,,377,113,VK_F22,0x85,,
+KEY_F23,193,,,,,,,109,114,VK_F23,0x86,,
+KEY_F24,194,,,,,,,111,115,VK_F24,0x87,,
+,195,,,,,,,277,,,,,
+,196,,,,,,,278,,,,,
+,197,,,,,,,282,,,,,
+,198,,,,,,,283,,,,,
+,199,,,,,,,295,,,,,
+KEY_PLAYCD,200,,,,,,,296,,,,,
+KEY_PAUSECD,201,,,,,,,297,,,,,
+KEY_PROG3,202,,,,,,,299,,,,,
+KEY_PROG4,203,,,,,,,300,,,,,
+KEY_DASHBOARD,204,,,,,,,301,,,,,
+KEY_SUSPEND,205,,,,,,,293,,,,,
+KEY_CLOSE,206,,,,,,,303,,,,,
+KEY_PLAY,207,,,,,,,307,,VK_PLAY,0xfa,,
+KEY_FASTFORWARD,208,,,,,,,308,,,,,
+KEY_BASSBOOST,209,,,,,,,310,,,,,
+KEY_PRINT,210,,,,,,,313,,VK_PRINT,0x2a,,
+KEY_HP,211,,,,,,,314,,,,,
+KEY_CAMERA,212,,,,,,,315,,,,,
+KEY_SOUND,213,,,,,,,317,,,,,
+KEY_QUESTION,214,,,,,,,318,,,,,
+KEY_EMAIL,215,,,,,,,319,,VK_LAUNCH_MAIL,0xb4,,
+KEY_CHAT,216,,,,,,,320,,,,,
+KEY_SEARCH,217,,,,,,,357,,VK_BROWSER_SEARCH,0xaa,,
+KEY_CONNECT,218,,,,,,,322,,,,,
+KEY_FINANCE,219,,,,,,,323,,,,,
+KEY_SPORT,220,,,,,,,324,,,,,
+KEY_SHOP,221,,,,,,,325,,,,,
+KEY_ALTERASE,222,,,,,,,276,,,,,
+KEY_CANCEL,223,,,,,,,330,,,,,
+KEY_BRIGHTNESSDOWN,224,,,,,,,332,,,,,
+KEY_BRIGHTNESSUP,225,,,,,,,340,,,,,
+KEY_MEDIA,226,,,,,,,365,,,,,
+KEY_SWITCHVIDEOMODE,227,,,,,,,342,,,,,
+KEY_KBDILLUMTOGGLE,228,,,,,,,343,,,,,
+KEY_KBDILLUMDOWN,229,,,,,,,344,,,,,
+KEY_KBDILLUMUP,230,,,,,,,345,,,,,
+KEY_SEND,231,,,,,,,346,,,,,
+KEY_REPLY,232,,,,,,,356,,,,,
+KEY_FORWARDMAIL,233,,,,,,,270,,,,,
+KEY_SAVE,234,,,,,,,341,,,,,
+KEY_DOCUMENTS,235,,,,,,,368,,,,,
+KEY_BATTERY,236,,,,,,,369,,,,,
+KEY_BLUETOOTH,237,,,,,,,370,,,,,
+KEY_WLAN,238,,,,,,,371,,,,,
+KEY_UWB,239,,,,,,,372,,,,,
+KEY_UNKNOWN,240,,,,,,,,,,,,
+KEY_VIDEO_NEXT,241,,,,,,,,,,,,
+KEY_VIDEO_PREV,242,,,,,,,,,,,,
+KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,
+KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,
+KEY_DISPLAY_OFF,245,,,,,,,,,,,,
+KEY_WIMAX,246,,,,,,,,,,,,
+,247,,,,,,,,,,,,
+,248,,,,,,,,,,,,
+,249,,,,,,,,,,,,
+,250,,,,,,,,,,,,
+,251,,,,,,,,,,,,
+,252,,,,,,,,,,,,
+,253,,,,,,,,,,,,
+,254,,,,,,,,,,,,
+,255,,,,182,,,,,,,,
+BTN_MISC,0x100,,,,,,,,,,,,
+BTN_0,0x100,,,,,,,,,VK_LBUTTON,0x01,,
+BTN_1,0x101,,,,,,,,,VK_RBUTTON,0x02,,
+BTN_2,0x102,,,,,,,,,VK_MBUTTON,0x04,,
+BTN_3,0x103,,,,,,,,,VK_XBUTTON1,0x05,,
+BTN_4,0x104,,,,,,,,,VK_XBUTTON2,0x06,,
+BTN_5,0x105,,,,,,,,,,,,
+BTN_6,0x106,,,,,,,,,,,,
+BTN_7,0x107,,,,,,,,,,,,
+BTN_8,0x108,,,,,,,,,,,,
+BTN_9,0x109,,,,,,,,,,,,
+BTN_MOUSE,0x110,,,,,,,,,,,,
+BTN_LEFT,0x110,,,,,,,,,,,,
+BTN_RIGHT,0x111,,,,,,,,,,,,
+BTN_MIDDLE,0x112,,,,,,,,,,,,
+BTN_SIDE,0x113,,,,,,,,,,,,
+BTN_EXTRA,0x114,,,,,,,,,,,,
+BTN_FORWARD,0x115,,,,,,,,,,,,
+BTN_BACK,0x116,,,,,,,,,,,,
+BTN_TASK,0x117,,,,,,,,,,,,
+BTN_JOYSTICK,0x120,,,,,,,,,,,,
+BTN_TRIGGER,0x120,,,,,,,,,,,,
+BTN_THUMB,0x121,,,,,,,,,,,,
+BTN_THUMB2,0x122,,,,,,,,,,,,
+BTN_TOP,0x123,,,,,,,,,,,,
+BTN_TOP2,0x124,,,,,,,,,,,,
+BTN_PINKIE,0x125,,,,,,,,,,,,
+BTN_BASE,0x126,,,,,,,,,,,,
+BTN_BASE2,0x127,,,,,,,,,,,,
+BTN_BASE3,0x128,,,,,,,,,,,,
+BTN_BASE4,0x129,,,,,,,,,,,,
+BTN_BASE5,0x12a,,,,,,,,,,,,
+BTN_BASE6,0x12b,,,,,,,,,,,,
+BTN_DEAD,0x12f,,,,,,,,,,,,
+BTN_GAMEPAD,0x130,,,,,,,,,,,,
+BTN_A,0x130,,,,,,,,,,,,
+BTN_B,0x131,,,,,,,,,,,,
+BTN_C,0x132,,,,,,,,,,,,
+BTN_X,0x133,,,,,,,,,,,,
+BTN_Y,0x134,,,,,,,,,,,,
+BTN_Z,0x135,,,,,,,,,,,,
+BTN_TL,0x136,,,,,,,,,,,,
+BTN_TR,0x137,,,,,,,,,,,,
+BTN_TL2,0x138,,,,,,,,,,,,
+BTN_TR2,0x139,,,,,,,,,,,,
+BTN_SELECT,0x13a,,,,,,,,,,,,
+BTN_START,0x13b,,,,,,,,,,,,
+BTN_MODE,0x13c,,,,,,,,,,,,
+BTN_THUMBL,0x13d,,,,,,,,,,,,
+BTN_THUMBR,0x13e,,,,,,,,,,,,
+BTN_DIGI,0x140,,,,,,,,,,,,
+BTN_TOOL_PEN,0x140,,,,,,,,,,,,
+BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,
+BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,
+BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,
+BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,
+BTN_TOOL_FINGER,0x145,,,,,,,,,,,,
+BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,
+BTN_TOOL_LENS,0x147,,,,,,,,,,,,
+BTN_TOUCH,0x14a,,,,,,,,,,,,
+BTN_STYLUS,0x14b,,,,,,,,,,,,
+BTN_STYLUS2,0x14c,,,,,,,,,,,,
+BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,
+BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,
+BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,
+BTN_WHEEL,0x150,,,,,,,,,,,,
+BTN_GEAR_DOWN,0x150,,,,,,,,,,,,
+BTN_GEAR_UP,0x151,,,,,,,,,,,,
+KEY_OK,0x160,,,,,,,,,,,,
+KEY_SELECT,0x161,,,,,,,,,VK_SELECT,0x29,,
+KEY_GOTO,0x162,,,,,,,,,,,,
+KEY_CLEAR,0x163,,,,,,,,,,,,
+KEY_POWER2,0x164,,,,,,,,,,,,
+KEY_OPTION,0x165,,,,,,,,,,,,
+KEY_INFO,0x166,,,,,,,,,,,,
+KEY_TIME,0x167,,,,,,,,,,,,
+KEY_VENDOR,0x168,,,,,,,,,,,,
+KEY_ARCHIVE,0x169,,,,,,,,,,,,
+KEY_PROGRAM,0x16a,,,,,,,,,,,,
+KEY_CHANNEL,0x16b,,,,,,,,,,,,
+KEY_FAVORITES,0x16c,,,,,,,,,VK_BROWSER_FAVOURITES,0xab,,
+KEY_EPG,0x16d,,,,,,,,,,,,
+KEY_PVR,0x16e,,,,,,,,,,,,
+KEY_MHP,0x16f,,,,,,,,,,,,
+KEY_LANGUAGE,0x170,,,,,,,,,,,,
+KEY_TITLE,0x171,,,,,,,,,,,,
+KEY_SUBTITLE,0x172,,,,,,,,,,,,
+KEY_ANGLE,0x173,,,,,,,,,,,,
+KEY_ZOOM,0x174,,,,,,,,,VK_ZOOM,0xfb,,
+KEY_MODE,0x175,,,,,,,,,,,,
+KEY_KEYBOARD,0x176,,,,,,,,,,,,
+KEY_SCREEN,0x177,,,,,,,,,,,,
+KEY_PC,0x178,,,,,,,,,,,,
+KEY_TV,0x179,,,,,,,,,,,,
+KEY_TV2,0x17a,,,,,,,,,,,,
+KEY_VCR,0x17b,,,,,,,,,,,,
+KEY_VCR2,0x17c,,,,,,,,,,,,
+KEY_SAT,0x17d,,,,,,,,,,,,
+KEY_SAT2,0x17e,,,,,,,,,,,,
+KEY_CD,0x17f,,,,,,,,,,,,
+KEY_TAPE,0x180,,,,,,,,,,,,
+KEY_RADIO,0x181,,,,,,,,,,,,
+KEY_TUNER,0x182,,,,,,,,,,,,
+KEY_PLAYER,0x183,,,,,,,,,,,,
+KEY_TEXT,0x184,,,,,,,,,,,,
+KEY_DVD,0x185,,,,,,,,,,,,
+KEY_AUX,0x186,,,,,,,,,,,,
+KEY_MP3,0x187,,,,,,,,,,,,
+KEY_AUDIO,0x188,,,,,,,,,,,,
+KEY_VIDEO,0x189,,,,,,,,,,,,
+KEY_DIRECTORY,0x18a,,,,,,,,,,,,
+KEY_LIST,0x18b,,,,,,,,,,,,
+KEY_MEMO,0x18c,,,,,,,,,,,,
+KEY_CALENDAR,0x18d,,,,,,,,,,,,
+KEY_RED,0x18e,,,,,,,,,,,,
+KEY_GREEN,0x18f,,,,,,,,,,,,
+KEY_YELLOW,0x190,,,,,,,,,,,,
+KEY_BLUE,0x191,,,,,,,,,,,,
+KEY_CHANNELUP,0x192,,,,,,,,,,,,
+KEY_CHANNELDOWN,0x193,,,,,,,,,,,,
+KEY_FIRST,0x194,,,,,,,,,,,,
+KEY_LAST,0x195,,,,,,,,,,,,
+KEY_AB,0x196,,,,,,,,,,,,
+KEY_NEXT,0x197,,,,,,,,,,,,
+KEY_RESTART,0x198,,,,,,,,,,,,
+KEY_SLOW,0x199,,,,,,,,,,,,
+KEY_SHUFFLE,0x19a,,,,,,,,,,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,
+KEY_PREVIOUS,0x19c,,,,,,,,,,,,
+KEY_DIGITS,0x19d,,,,,,,,,,,,
+KEY_TEEN,0x19e,,,,,,,,,,,,
+KEY_TWEN,0x19f,,,,,,,,,,,,
+KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,
+KEY_GAMES,0x1a1,,,,,,,,,,,,
+KEY_ZOOMIN,0x1a2,,,,,,,,,,,,
+KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,
+KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,
+KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,
+KEY_EDITOR,0x1a6,,,,,,,,,,,,
+KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,
+KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,
+KEY_PRESENTATION,0x1a9,,,,,,,,,,,,
+KEY_DATABASE,0x1aa,,,,,,,,,,,,
+KEY_NEWS,0x1ab,,,,,,,,,,,,
+KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,
+KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,
+KEY_MESSENGER,0x1ae,,,,,,,,,,,,
+KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,
+KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,
+KEY_LOGOFF,0x1b1,,,,,,,,,,,,
+KEY_DOLLAR,0x1b2,,,,,,,,,,,,
+KEY_EURO,0x1b3,,,,,,,,,,,,
+KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,
+KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,
+KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,
+KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,
+KEY_DEL_EOL,0x1c0,,,,,,,,,,,,
+KEY_DEL_EOS,0x1c1,,,,,,,,,,,,
+KEY_INS_LINE,0x1c2,,,,,,,,,,,,
+KEY_DEL_LINE,0x1c3,,,,,,,,,,,,
+KEY_FN,0x1d0,,,,,,,,,,,,
+KEY_FN_ESC,0x1d1,,,,,,,,,,,,
+KEY_FN_F1,0x1d2,,,,,,,,,,,,
+KEY_FN_F2,0x1d3,,,,,,,,,,,,
+KEY_FN_F3,0x1d4,,,,,,,,,,,,
+KEY_FN_F4,0x1d5,,,,,,,,,,,,
+KEY_FN_F5,0x1d6,,,,,,,,,,,,
+KEY_FN_F6,0x1d7,,,,,,,,,,,,
+KEY_FN_F7,0x1d8,,,,,,,,,,,,
+KEY_FN_F8,0x1d9,,,,,,,,,,,,
+KEY_FN_F9,0x1da,,,,,,,,,,,,
+KEY_FN_F10,0x1db,,,,,,,,,,,,
+KEY_FN_F11,0x1dc,,,,,,,,,,,,
+KEY_FN_F12,0x1dd,,,,,,,,,,,,
+KEY_FN_1,0x1de,,,,,,,,,,,,
+KEY_FN_2,0x1df,,,,,,,,,,,,
+KEY_FN_D,0x1e0,,,,,,,,,,,,
+KEY_FN_E,0x1e1,,,,,,,,,,,,
+KEY_FN_F,0x1e2,,,,,,,,,,,,
+KEY_FN_S,0x1e3,,,,,,,,,,,,
+KEY_FN_B,0x1e4,,,,,,,,,,,,
+KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,
+KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,
+KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,
+KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,
+KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,
+KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,
+KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,
+KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,
+KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,
+KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,
+KEY_NUMERIC_0,0x200,,,,,,,,,,,,
+KEY_NUMERIC_1,0x201,,,,,,,,,,,,
+KEY_NUMERIC_2,0x202,,,,,,,,,,,,
+KEY_NUMERIC_3,0x203,,,,,,,,,,,,
+KEY_NUMERIC_4,0x204,,,,,,,,,,,,
+KEY_NUMERIC_5,0x205,,,,,,,,,,,,
+KEY_NUMERIC_6,0x206,,,,,,,,,,,,
+KEY_NUMERIC_7,0x207,,,,,,,,,,,,
+KEY_NUMERIC_8,0x208,,,,,,,,,,,,
+KEY_NUMERIC_9,0x209,,,,,,,,,,,,
+KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,
+KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,
+KEY_RFKILL,0x20c,,,,,,,,,,,,
diff --git a/gtk/snappy.c b/gtk/snappy.c
new file mode 100644
index 0000000..812b9ea
--- /dev/null
+++ b/gtk/snappy.c
@@ -0,0 +1,172 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+/* config */
+static char *host = "localhost";
+static char *port = "5920";
+static char *tls_port = "5921";
+static char *password;
+static char *ca_file;
+static char *outf = "snappy.ppm";
+
+/* state */
+static SpiceSession *session;
+static GMainLoop *mainloop;
+
+enum SpiceSurfaceFmt d_format;
+gint d_width, d_height, d_stride;
+gpointer d_data;
+
+/* ------------------------------------------------------------------ */
+
+static void primary_create(SpiceChannel *channel, gint format,
+ gint width, gint height, gint stride,
+ gint shmid, gpointer imgdata, gpointer data)
+{
+ fprintf(stderr, "%s: %dx%d, format %d\n", __FUNCTION__, width, height, format);
+ d_format = format;
+ d_width = width;
+ d_height = height;
+ d_stride = stride;
+ d_data = imgdata;
+}
+
+static int write_ppm_32(void)
+{
+ FILE *fp;
+ uint8_t *p;
+ int n;
+
+ fp = fopen(outf,"w");
+ if (NULL == fp) {
+ fprintf(stderr,"snappy: can't open %s: %s\n", outf, strerror(errno));
+ return -1;
+ }
+ fprintf(fp,"P6\n%d %d\n255\n",
+ d_width, d_height);
+ n = d_width * d_height;
+ p = d_data;
+ while (n > 0) {
+ fputc(p[2], fp);
+ fputc(p[1], fp);
+ fputc(p[0], fp);
+ p += 4;
+ n--;
+ }
+ fclose(fp);
+ return 0;
+}
+
+static void invalidate(SpiceChannel *channel,
+ gint x, gint y, gint w, gint h, gpointer *data)
+{
+ int rc;
+
+ switch (d_format) {
+ case SPICE_SURFACE_FMT_32_xRGB:
+ rc = write_ppm_32();
+ break;
+ default:
+ fprintf(stderr, "unsupported spice surface format %d\n", d_format);
+ rc = -1;
+ break;
+ }
+ if (rc == 0)
+ fprintf(stderr, "wrote screen shot to %s\n", outf);
+ g_main_loop_quit(mainloop);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
+{
+ int id = spice_channel_id(channel);
+
+ if (!SPICE_IS_DISPLAY_CHANNEL(channel))
+ return;
+ if (id != 0)
+ return;
+
+ g_signal_connect(channel, "spice-display-primary-create",
+ G_CALLBACK(primary_create), NULL);
+ g_signal_connect(channel, "spice-display-invalidate",
+ G_CALLBACK(invalidate), NULL);
+ spice_channel_connect(channel);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+ fprintf(fp,
+ "usage:\n"
+ " snappy [ options ]\n"
+ "\n"
+ "options:\n"
+ " -h host [ %s ]\n"
+ " -p port [ %s ]\n"
+ " -o file [ %s ]\n"
+ "\n",
+ host, port, outf);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ /* parse opts */
+ for (;;) {
+ if (-1 == (c = getopt(argc, argv, "h:p:o:")))
+ break;
+ switch (c) {
+ case 'h':
+ host = optarg;
+ break;
+ case 'p':
+ port = optarg;
+ break;
+ case 'o':
+ outf = optarg;
+ break;
+#if 0 /* --help */
+ case 'h':
+ usage(stdout);
+ exit(0);
+#endif
+ default:
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ if (ca_file == NULL) {
+ char *home = getenv("HOME");
+ size_t size = strlen(home) + 32;
+ ca_file = malloc(size);
+ snprintf(ca_file, size, "%s/.spicec/spice_truststore.pem", home);
+ }
+
+ g_type_init();
+ mainloop = g_main_loop_new(NULL, false);
+
+ session = spice_session_new();
+ g_signal_connect(session, "spice-session-channel-new",
+ G_CALLBACK(channel_new), NULL);
+
+ if (host)
+ g_object_set(session, "host", host, NULL);
+ if (port)
+ g_object_set(session, "port", port, NULL);
+ if (tls_port)
+ g_object_set(session, "tls-port", tls_port, NULL);
+ if (password)
+ g_object_set(session, "password", password, NULL);
+ if (ca_file)
+ g_object_set(session, "ca-file", ca_file, NULL);
+
+ if (!spice_session_connect(session)) {
+ fprintf(stderr, "spice_session_connect failed\n");
+ exit(1);
+ }
+
+ g_main_loop_run(mainloop);
+ return 0;
+}
diff --git a/gtk/spice-channel-cache.h b/gtk/spice-channel-cache.h
new file mode 100644
index 0000000..dc33cce
--- /dev/null
+++ b/gtk/spice-channel-cache.h
@@ -0,0 +1,109 @@
+/* spice/common */
+#include "ring.h"
+
+typedef struct display_cache_item {
+ RingItem hash_link;
+ RingItem lru_link;
+ uint64_t id;
+ uint32_t refcount;
+ void *ptr;
+} display_cache_item;
+
+typedef struct display_cache {
+ const char *name;
+ Ring hash[64];
+ Ring lru;
+ int nitems;
+} display_cache;
+
+static inline void cache_init(display_cache *cache, const char *name)
+{
+ int i;
+
+ cache->name = name;
+ ring_init(&cache->lru);
+ for (i = 0; i < SPICE_N_ELEMENTS(cache->hash); i++) {
+ ring_init(&cache->hash[i]);
+ }
+}
+
+static inline Ring *cache_head(display_cache *cache, uint64_t id)
+{
+ return &cache->hash[id % SPICE_N_ELEMENTS(cache->hash)];
+}
+
+static inline void cache_used(display_cache *cache, display_cache_item *item)
+{
+ ring_remove(&item->lru_link);
+ ring_add(&cache->lru, &item->lru_link);
+}
+
+static inline display_cache_item *cache_get_lru(display_cache *cache)
+{
+ display_cache_item *item;
+ RingItem *ring;
+
+ if (ring_is_empty(&cache->lru))
+ return NULL;
+ ring = ring_get_tail(&cache->lru);
+ item = SPICE_CONTAINEROF(ring, display_cache_item, lru_link);
+ return item;
+}
+
+static inline display_cache_item *cache_find(display_cache *cache, uint64_t id)
+{
+ display_cache_item *item;
+ RingItem *ring;
+ Ring *head;
+
+ head = cache_head(cache, id);
+ for (ring = ring_get_head(head); ring != NULL; ring = ring_next(head, ring)) {
+ item = SPICE_CONTAINEROF(ring, display_cache_item, hash_link);
+ if (item->id == id) {
+ return item;
+ }
+ }
+ fprintf(stderr, "%s: %s %" PRIx64 " [not found]\n", __FUNCTION__,
+ cache->name, id);
+ return NULL;
+}
+
+static inline display_cache_item *cache_add(display_cache *cache, uint64_t id)
+{
+ display_cache_item *item;
+
+ item = spice_new0(display_cache_item, 1);
+ item->id = id;
+ item->refcount = 1;
+ ring_add(cache_head(cache, item->id), &item->hash_link);
+ ring_add(&cache->lru, &item->lru_link);
+ cache->nitems++;
+#if 0
+ fprintf(stderr, "%s: %s %" PRIx64 " (%d)\n", __FUNCTION__,
+ cache->name, id, cache->nitems);
+#endif
+ return item;
+}
+
+static inline void cache_del(display_cache *cache, display_cache_item *item)
+{
+#if 0
+ fprintf(stderr, "%s: %s %" PRIx64 "\n", __FUNCTION__,
+ cache->name, item->id);
+#endif
+ ring_remove(&item->hash_link);
+ ring_remove(&item->lru_link);
+ free(item);
+ cache->nitems--;
+}
+
+static inline void cache_ref(display_cache_item *item)
+{
+ item->refcount++;
+}
+
+static inline int cache_unref(display_cache_item *item)
+{
+ item->refcount--;
+ return item->refcount == 0;
+}
diff --git a/gtk/spice-channel-priv.h b/gtk/spice-channel-priv.h
new file mode 100644
index 0000000..43276c6
--- /dev/null
+++ b/gtk/spice-channel-priv.h
@@ -0,0 +1,54 @@
+#ifndef __SPICE_CLIENT_CHANNEL_PRIV_H__
+#define __SPICE_CLIENT_CHANNEL_PRIV_H__
+
+#include <openssl/ssl.h>
+
+struct spice_msg_in {
+ int refcount;
+ SpiceChannel *channel;
+ SpiceDataHeader header;
+ uint8_t *data;
+ int hpos,dpos;
+ uint8_t *parsed;
+ size_t psize;
+ message_destructor_t pfree;
+};
+
+enum spice_channel_state {
+ SPICE_CHANNEL_STATE_UNCONNECTED = 0,
+ SPICE_CHANNEL_STATE_TLS,
+ SPICE_CHANNEL_STATE_LINK_HDR,
+ SPICE_CHANNEL_STATE_LINK_MSG,
+ SPICE_CHANNEL_STATE_AUTH,
+ SPICE_CHANNEL_STATE_READY,
+};
+
+struct spice_channel {
+ SpiceSession *session;
+ char name[16];
+ enum spice_channel_state state;
+ int socket;
+ spice_parse_channel_func_t parser;
+ SpiceMessageMarshallers *marshallers;
+ spice_watch *watch;
+ SSL_CTX *ctx;
+ SSL *ssl;
+
+ int protocol;
+ int tls;
+
+ int connection_id;
+ int channel_id;
+ int channel_type;
+ int serial;
+ SpiceLinkHeader link_hdr;
+ SpiceLinkMess link_msg;
+ SpiceLinkHeader peer_hdr;
+ SpiceLinkReply* peer_msg;
+
+ spice_msg_in *msg_in;
+ int message_ack_window;
+ int message_ack_count;
+};
+
+#endif /* __SPICE_CLIENT_CHANNEL_PRIV_H__ */
diff --git a/gtk/spice-channel.c b/gtk/spice-channel.c
new file mode 100644
index 0000000..81e1e40
--- /dev/null
+++ b/gtk/spice-channel.c
@@ -0,0 +1,897 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-channel-priv.h"
+#include "spice-session-priv.h"
+#include "spice-marshal.h"
+
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <sys/socket.h>
+
+static void spice_channel_send_msg(SpiceChannel *channel, spice_msg_out *out);
+static void spice_channel_send_link(SpiceChannel *channel);
+
+/* ------------------------------------------------------------------ */
+/* gobject glue */
+
+#define SPICE_CHANNEL_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_CHANNEL, spice_channel))
+
+G_DEFINE_TYPE (SpiceChannel, spice_channel, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_SESSION,
+ PROP_CHANNEL_TYPE,
+ PROP_CHANNEL_ID,
+};
+
+/* Signals */
+enum {
+ SPICE_CHANNEL_EVENT,
+
+ SPICE_CHANNEL_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_CHANNEL_LAST_SIGNAL];
+
+static void spice_channel_init(SpiceChannel *channel)
+{
+ spice_channel *c;
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ c = channel->priv = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ c->serial = 1;
+ c->socket = -1;
+ c->protocol = SPICE_VERSION_MAJOR;
+ strcpy(c->name, "?");
+}
+
+static void spice_channel_constructed(GObject *gobject)
+{
+ SpiceChannel *channel = SPICE_CHANNEL(gobject);
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ snprintf(c->name, sizeof(c->name), "%d:%d",
+ c->channel_type, c->channel_id);
+ fprintf(stderr, "%s %s\n", __FUNCTION__, c->name);
+
+ c->connection_id = spice_session_get_connection_id(c->session);
+ spice_session_channel_new(c->session, channel);
+
+ /* Chain up to the parent class */
+ if (G_OBJECT_CLASS(spice_channel_parent_class)->constructed)
+ G_OBJECT_CLASS(spice_channel_parent_class)->constructed(gobject);
+}
+
+static void spice_channel_dispose(GObject *gobject)
+{
+ SpiceChannel *channel = SPICE_CHANNEL(gobject);
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ fprintf(stderr, "%s %s\n", __FUNCTION__, c->name);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+ spice_session_channel_destroy(c->session, channel);
+
+ /* Chain up to the parent class */
+ if (G_OBJECT_CLASS(spice_channel_parent_class)->dispose)
+ G_OBJECT_CLASS(spice_channel_parent_class)->dispose(gobject);
+}
+
+static void spice_channel_finalize(GObject *gobject)
+{
+ SpiceChannel *channel = SPICE_CHANNEL(gobject);
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ fprintf(stderr, "%s %s\n", __FUNCTION__, c->name);
+
+ /* Chain up to the parent class */
+ if (G_OBJECT_CLASS(spice_channel_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_channel_parent_class)->finalize(gobject);
+}
+
+static void spice_channel_get_property(GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceChannel *channel = SPICE_CHANNEL(gobject);
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ switch (prop_id) {
+ case PROP_SESSION:
+ g_value_set_object(value, c->session);
+ break;
+ case PROP_CHANNEL_TYPE:
+ g_value_set_int(value, c->channel_type);
+ break;
+ case PROP_CHANNEL_ID:
+ g_value_set_int(value, c->channel_id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_channel_set_property(GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceChannel *channel = SPICE_CHANNEL(gobject);
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ switch (prop_id) {
+ case PROP_SESSION:
+ c->session = g_value_get_object(value);
+ break;
+ case PROP_CHANNEL_TYPE:
+ c->channel_type = g_value_get_int(value);
+ break;
+ case PROP_CHANNEL_ID:
+ c->channel_id = g_value_get_int(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_channel_class_init(SpiceChannelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ fprintf(stderr, "%s\n", __FUNCTION__);
+
+ gobject_class->constructed = spice_channel_constructed;
+ gobject_class->dispose = spice_channel_dispose;
+ gobject_class->finalize = spice_channel_finalize;
+ gobject_class->get_property = spice_channel_get_property;
+ gobject_class->set_property = spice_channel_set_property;
+
+ g_object_class_install_property
+ (gobject_class, PROP_SESSION,
+ g_param_spec_object("spice-session",
+ "Spice session",
+ "",
+ SPICE_TYPE_SESSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_CHANNEL_TYPE,
+ g_param_spec_int("channel-type",
+ "Channel type",
+ "",
+ -1, INT_MAX, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_CHANNEL_ID,
+ g_param_spec_int("channel-id",
+ "Channel ID",
+ "",
+ -1, INT_MAX, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ signals[SPICE_CHANNEL_EVENT] =
+ g_signal_new("spice-channel-event",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceChannelClass, spice_channel_event),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ g_type_class_add_private(klass, sizeof(spice_channel));
+
+ SSL_library_init();
+ SSL_load_error_strings();
+}
+
+static void spice_channel_emit_event(SpiceChannel *channel,
+ enum SpiceChannelEvent event)
+{
+ g_signal_emit(channel, signals[SPICE_CHANNEL_EVENT], 0, event);
+}
+
+/* ---------------------------------------------------------------- */
+
+spice_msg_in *spice_msg_in_new(SpiceChannel *channel)
+{
+ spice_msg_in *in;
+
+ in = spice_new0(spice_msg_in, 1);
+ in->refcount = 1;
+ in->channel = channel;
+ return in;
+}
+
+void spice_msg_in_get(spice_msg_in *in)
+{
+ in->refcount++;
+}
+
+void spice_msg_in_put(spice_msg_in *in)
+{
+ in->refcount--;
+ if (in->refcount > 0)
+ return;
+ if (in->parsed)
+ in->pfree(in->parsed);
+ free(in->data);
+ free(in);
+}
+
+int spice_msg_in_type(spice_msg_in *in)
+{
+ return in->header.type;
+}
+
+void *spice_msg_in_parsed(spice_msg_in *in)
+{
+ return in->parsed;
+}
+
+void *spice_msg_in_raw(spice_msg_in *in, int *len)
+{
+ *len = in->dpos;
+ return in->data;
+}
+
+static void hexdump(char *prefix, unsigned char *data, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if (i % 16 == 0)
+ fprintf(stderr, "%s:", prefix);
+ if (i % 4 == 0)
+ fprintf(stderr, " ");
+ fprintf(stderr, " %02x", data[i]);
+ if (i % 16 == 15)
+ fprintf(stderr, "\n");
+ }
+ if (i % 16 != 0)
+ fprintf(stderr, "\n");
+}
+
+void spice_msg_in_hexdump(spice_msg_in *in)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(in->channel);
+
+ fprintf(stderr, "--\n<< hdr: %s serial %ld type %d size %d sub-list %d\n",
+ c->name, in->header.serial, in->header.type,
+ in->header.size, in->header.sub_list);
+ hexdump("<< msg", in->data, in->dpos);
+}
+
+void spice_msg_out_hexdump(spice_msg_out *out, unsigned char *data, int len)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(out->channel);
+
+ fprintf(stderr, "--\n>> hdr: %s serial %ld type %d size %d sub-list %d\n",
+ c->name, out->header->serial, out->header->type,
+ out->header->size, out->header->sub_list);
+ hexdump(">> msg", data, len);
+}
+
+/* ---------------------------------------------------------------- */
+
+spice_msg_out *spice_msg_out_new(SpiceChannel *channel, int type)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ spice_msg_out *out;
+
+ out = spice_new0(spice_msg_out, 1);
+ out->refcount = 1;
+ out->channel = channel;
+
+ out->marshallers = c->marshallers;
+ out->marshaller = spice_marshaller_new();
+ out->header = (SpiceDataHeader *)
+ spice_marshaller_reserve_space(out->marshaller, sizeof(SpiceDataHeader));
+ spice_marshaller_set_base(out->marshaller, sizeof(SpiceDataHeader));
+ out->header->serial = c->serial++;
+ out->header->type = type;
+ out->header->sub_list = 0;
+ return out;
+}
+
+void spice_msg_out_get(spice_msg_out *out)
+{
+ out->refcount++;
+}
+
+void spice_msg_out_put(spice_msg_out *out)
+{
+ out->refcount--;
+ if (out->refcount > 0)
+ return;
+ spice_marshaller_destroy(out->marshaller);
+ free(out);
+}
+
+void spice_msg_out_send(spice_msg_out *out)
+{
+ out->header->size =
+ spice_marshaller_get_total_size(out->marshaller) - sizeof(SpiceDataHeader);
+ spice_channel_send_msg(out->channel, out);
+}
+
+/* ---------------------------------------------------------------- */
+
+static int spice_channel_send(SpiceChannel *channel, void *buf, int len)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ if (c->tls) {
+ return SSL_write(c->ssl, buf, len);
+ } else {
+ return send(c->socket, buf, len, 0);
+ }
+}
+
+static int spice_channel_recv(SpiceChannel *channel, void *buf, int len)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, err;
+
+ if (c->tls) {
+ rc = SSL_read(c->ssl, buf, len);
+ if (rc > 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ fprintf(stderr, "channel/tls eof: %s\n", c->name);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+ return 0;
+ }
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ return 0;
+ }
+ fprintf(stderr, "channel/tls error: %s: %s\n",
+ c->name, ERR_error_string(err, NULL));
+ spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_IO);
+ return 0;
+ } else {
+ rc = recv(c->socket, buf, len, 0);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return 0;
+ fprintf(stderr, "channel error: %s: %s\n",
+ c->name, strerror(errno));
+ spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_IO);
+ return 0;
+ case 0:
+ fprintf(stderr, "channel eof: %s\n", c->name);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_CLOSED);
+ return 0;
+ default:
+ return rc;
+ }
+ }
+}
+
+static void spice_channel_tls_connect(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, err;
+
+ rc = SSL_connect(c->ssl);
+ if (rc <= 0) {
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ return;
+ }
+ fprintf(stderr, "SSL_connect: %s", ERR_error_string(err, NULL));
+ spice_channel_emit_event(channel, SPICE_CHANNEL_ERROR_TLS);
+ }
+ c->state = SPICE_CHANNEL_STATE_LINK_HDR;
+ spice_channel_send_link(channel);
+}
+
+static void spice_channel_send_auth(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ EVP_PKEY *pubkey;
+ int nRSASize;
+ BIO *bioKey;
+ RSA *rsa;
+ const char *password;
+ uint8_t *encrypted;
+ int rc;
+
+ bioKey = BIO_new(BIO_s_mem());
+ if (bioKey == NULL)
+ PANIC("Could not initiate BIO");
+
+ BIO_write(bioKey, c->peer_msg->pub_key, SPICE_TICKET_PUBKEY_BYTES);
+ pubkey = d2i_PUBKEY_bio(bioKey, NULL);
+ rsa = pubkey->pkey.rsa;
+ nRSASize = RSA_size(rsa);
+
+ encrypted = spice_malloc(nRSASize);
+ /*
+ The use of RSA encryption limit the potential maximum password length.
+ for RSA_PKCS1_OAEP_PADDING it is RSA_size(rsa) - 41.
+ */
+ g_object_get(c->session, "password", &password, NULL);
+ if (password == NULL)
+ password = "";
+ rc = RSA_public_encrypt(strlen(password) + 1, (uint8_t*)password,
+ encrypted, rsa, RSA_PKCS1_OAEP_PADDING);
+ if (rc <= 0)
+ PANIC("could not encrypt password");
+ spice_channel_send(channel, encrypted, nRSASize);
+ memset(encrypted, 0, nRSASize);
+ free(encrypted);
+ BIO_free(bioKey);
+}
+
+static void spice_channel_recv_auth(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ uint32_t link_res;
+ int rc;
+
+ rc = spice_channel_recv(channel, &link_res, sizeof(link_res));
+ if (rc != sizeof(link_res))
+ PANIC("incomplete auth reply (%d/%zd)", rc, sizeof(link_res));
+ if (link_res != SPICE_LINK_ERR_OK) {
+ spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_AUTH);
+ return;
+ }
+
+ fprintf(stderr, "channel up: %s\n", c->name);
+ c->state = SPICE_CHANNEL_STATE_READY;
+ spice_channel_emit_event(channel, SPICE_CHANNEL_OPENED);
+
+ if (SPICE_CHANNEL_GET_CLASS(channel)->channel_up)
+ SPICE_CHANNEL_GET_CLASS(channel)->channel_up(channel);
+}
+
+static void spice_channel_send_link(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ uint8_t *buffer, *p;
+
+ c->link_hdr.magic = SPICE_MAGIC;
+ c->link_hdr.size = sizeof(c->link_msg);
+
+ switch (c->protocol) {
+ case 1: /* protocol 1 == major 1, old 0.4 protocol, last active minor */
+ c->link_hdr.major_version = 1;
+ c->link_hdr.minor_version = 3;
+ c->parser = spice_get_server_channel_parser1(c->channel_type, NULL);
+ c->marshallers = spice_message_marshallers_get1();
+ break;
+ case SPICE_VERSION_MAJOR: /* protocol 2 == current */
+ c->link_hdr.major_version = SPICE_VERSION_MAJOR;
+ c->link_hdr.minor_version = SPICE_VERSION_MINOR;
+ c->parser = spice_get_server_channel_parser(c->channel_type, NULL);
+ c->marshallers = spice_message_marshallers_get();
+ break;
+ default:
+ PANIC("unknown major %d", c->protocol);
+ }
+
+ c->link_msg.connection_id = c->connection_id;
+ c->link_msg.channel_type = c->channel_type;
+ c->link_msg.channel_id = c->channel_id;
+ c->link_msg.caps_offset = sizeof(c->link_msg);
+
+#if 0 /* TODO */
+ c->link_msg.num_common_caps = get_common_caps().size();
+ c->link_msg.num_channel_caps = get_caps().size();
+ c->link_hdr.size += (c->link_msg.num_common_caps + c->link_msg.num_channel_caps) * sizeof(uint32_t);
+#endif
+
+ buffer = spice_malloc(sizeof(c->link_hdr) + c->link_hdr.size);
+ p = buffer;
+
+ memcpy(p, &c->link_hdr, sizeof(c->link_hdr)); p += sizeof(c->link_hdr);
+ memcpy(p, &c->link_msg, sizeof(c->link_msg)); p += sizeof(c->link_msg);
+
+#if 0
+ for (i = 0; i < _common_caps.size(); i++) {
+ *(uint32_t *)p = _common_caps[i];
+ p += sizeof(uint32_t);
+ }
+ for (i = 0; i < _caps.size(); i++) {
+ *(uint32_t *)p = _caps[i];
+ p += sizeof(uint32_t);
+ }
+#endif
+
+ spice_channel_send(channel, buffer, p - buffer);
+}
+
+static void spice_channel_recv_link_hdr(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc;
+
+ rc = spice_channel_recv(channel, &c->peer_hdr, sizeof(c->peer_hdr));
+ if (rc != sizeof(c->peer_hdr))
+ PANIC("incomplete link header (%d/%zd)", rc, sizeof(c->peer_hdr));
+ if (c->peer_hdr.magic != SPICE_MAGIC)
+ PANIC("bad magic");
+ if (c->peer_hdr.major_version != c->link_hdr.major_version) {
+ if (c->peer_hdr.major_version == 1) {
+ /* enter spice 0.4 mode */
+ c->protocol = 1;
+ fprintf(stderr, "switching to protocol 1 (spice 0.4)\n");
+ spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
+ spice_channel_connect(channel);
+ return;
+ }
+ PANIC("major mismatch (got %d, expected %d)",
+ c->peer_hdr.major_version, c->link_hdr.major_version);
+ }
+
+ c->peer_msg = spice_malloc(c->peer_hdr.size);
+ c->state = SPICE_CHANNEL_STATE_LINK_MSG;
+}
+
+static void spice_channel_recv_link_msg(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, num_caps;
+
+ rc = spice_channel_recv(channel, c->peer_msg, c->peer_hdr.size);
+ if (rc != c->peer_hdr.size)
+ PANIC("incomplete link reply (%d/%d)", rc, c->peer_hdr.size);
+ switch (c->peer_msg->error) {
+ case SPICE_LINK_ERR_OK:
+ /* nothing */
+ break;
+ case SPICE_LINK_ERR_NEED_SECURED:
+ c->tls = true;
+ fprintf(stderr, "switching to tls\n");
+ spice_channel_disconnect(channel, SPICE_CHANNEL_NONE);
+ spice_channel_connect(channel);
+ return;
+ default:
+ fprintf(stderr, "%s: unhandled error %d\n", __FUNCTION__,
+ c->peer_msg->error);
+ spice_channel_disconnect(channel, SPICE_CHANNEL_ERROR_LINK);
+ return;
+ }
+
+ num_caps = c->peer_msg->num_channel_caps + c->peer_msg->num_common_caps;
+ if (num_caps) {
+ fprintf(stderr, "%s: %d caps\n", __FUNCTION__, num_caps);
+ }
+
+#if 0
+ if ((uint8_t *)(reply + 1) > reply_buf.get() + header.size ||
+ (uint8_t *)reply + reply->caps_offset + num_caps * sizeof(uint32_t) >
+ reply_buf.get() + header.size) {
+ THROW_ERR(SPICEC_ERROR_CODE_CONNECT_FAILED, "access violation");
+ }
+
+ uint32_t *caps = (uint32_t *)((uint8_t *)reply + reply->caps_offset);
+
+ _remote_common_caps.clear();
+ for (i = 0; i < reply->num_common_caps; i++, caps++) {
+ _remote_common_caps.resize(i + 1);
+ _remote_common_caps[i] = *caps;
+ }
+
+ _remote_caps.clear();
+ for (i = 0; i < reply->num_channel_caps; i++, caps++) {
+ _remote_caps.resize(i + 1);
+ _remote_caps[i] = *caps;
+ }
+#endif
+
+ c->state = SPICE_CHANNEL_STATE_AUTH;
+ spice_channel_send_auth(channel);
+}
+
+void spice_channel_send_msg(SpiceChannel *channel, spice_msg_out *out)
+{
+ uint8_t *data;
+ int free_data;
+ size_t len;
+ uint32_t res;
+
+ data = spice_marshaller_linearize(out->marshaller, 0,
+ &len, &free_data);
+#if 0
+ spice_msg_out_hexdump(out, data, len);
+#endif
+ res = spice_channel_send(channel, data, len);
+ if (free_data) {
+ free(data);
+ }
+ if (res != len) {
+ /* TODO: queue up */
+ PANIC("sending message data failed");
+ }
+}
+
+static void spice_channel_recv_msg(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ spice_msg_in *in;
+ int rc;
+
+ if (!c->msg_in) {
+ c->msg_in = spice_msg_in_new(channel);
+ }
+ in = c->msg_in;
+
+ /* receive message */
+ if (in->hpos < sizeof(in->header)) {
+ rc = spice_channel_recv(channel, (uint8_t*)&in->header + in->hpos,
+ sizeof(in->header) - in->hpos);
+ if (rc < 0)
+ PANIC("recv hdr: %s", strerror(errno));
+ in->hpos += rc;
+ if (in->hpos < sizeof(in->header))
+ return;
+ in->data = spice_malloc(in->header.size);
+ }
+ if (in->dpos < in->header.size) {
+ rc = spice_channel_recv(channel, in->data + in->dpos,
+ in->header.size - in->dpos);
+ if (rc < 0)
+ PANIC("recv msg: %s", strerror(errno));
+ in->dpos += rc;
+ if (in->dpos < in->header.size)
+ return;
+ }
+
+ if (in->header.sub_list) {
+ fprintf(stderr, "msg: %s serial %ld type %d size %d sub-list %d\n",
+ c->name, in->header.serial, in->header.type,
+ in->header.size, in->header.sub_list);
+ }
+
+ /* ack message */
+ if (c->message_ack_count) {
+ c->message_ack_count--;
+ if (!c->message_ack_count) {
+ spice_msg_out *out = spice_msg_out_new(channel, SPICE_MSGC_ACK);
+ spice_msg_out_send(out);
+ spice_msg_out_put(out);
+ c->message_ack_count = c->message_ack_window;
+ }
+ }
+
+ /* parse message */
+ if (in->header.sub_list) {
+ PANIC("sub lists not handled");
+ }
+ in->parsed = c->parser(in->data, in->data + in->dpos, in->header.type,
+ c->peer_hdr.minor_version, &in->psize, &in->pfree);
+ if (in->parsed == NULL)
+ PANIC("failed to parse message: %s type %d",
+ c->name, in->header.type);
+
+ /* process message */
+ SPICE_CHANNEL_GET_CLASS(channel)->handle_msg(channel, in);
+
+ /* release message */
+ spice_msg_in_put(c->msg_in);
+ c->msg_in = NULL;
+}
+
+static void spice_channel_data(int event, void *opaque)
+{
+ SpiceChannel *channel = opaque;
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ switch (c->state) {
+ case SPICE_CHANNEL_STATE_TLS:
+ spice_channel_tls_connect(channel);
+ break;
+ case SPICE_CHANNEL_STATE_LINK_HDR:
+ spice_channel_recv_link_hdr(channel);
+ break;
+ case SPICE_CHANNEL_STATE_LINK_MSG:
+ spice_channel_recv_link_msg(channel);
+ break;
+ case SPICE_CHANNEL_STATE_AUTH:
+ spice_channel_recv_auth(channel);
+ break;
+ case SPICE_CHANNEL_STATE_READY:
+ spice_channel_recv_msg(channel);
+ break;
+ default:
+ PANIC("unknown state %d", c->state);
+ }
+}
+
+SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id)
+{
+ SpiceChannel *channel;
+ GType gtype = 0;
+
+ fprintf(stderr, "%s: %d:%d\n", __FUNCTION__, type, id);
+
+ switch (type) {
+ case SPICE_CHANNEL_MAIN:
+ gtype = SPICE_TYPE_MAIN_CHANNEL;
+ break;
+ case SPICE_CHANNEL_DISPLAY:
+ gtype = SPICE_TYPE_DISPLAY_CHANNEL;
+ break;
+ case SPICE_CHANNEL_CURSOR:
+ gtype = SPICE_TYPE_CURSOR_CHANNEL;
+ break;
+ case SPICE_CHANNEL_INPUTS:
+ gtype = SPICE_TYPE_INPUTS_CHANNEL;
+ break;
+ case SPICE_CHANNEL_PLAYBACK:
+ gtype = SPICE_TYPE_PLAYBACK_CHANNEL;
+ break;
+ default:
+ return NULL;
+ }
+ channel = SPICE_CHANNEL(g_object_new(gtype,
+ "spice-session", s,
+ "channel-type", type,
+ "channel-id", id,
+ NULL));
+ return channel;
+}
+
+void spice_channel_destroy(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ fprintf(stderr, "%s %s\n", __FUNCTION__, c->name);
+ g_object_unref(channel);
+}
+
+int spice_channel_id(SpiceChannel *channel)
+{
+ gint id;
+
+ g_object_get(G_OBJECT(channel), "channel-id", &id, NULL);
+ return id;
+}
+
+static int tls_verify(int preverify_ok, X509_STORE_CTX *ctx)
+{
+ spice_channel *c;
+ char *hostname;
+ SSL *ssl;
+
+ ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ c = SSL_get_app_data(ssl);
+
+ g_object_get(c->session, "host", &hostname, NULL);
+ /* TODO: check hostname */
+
+ return preverify_ok;
+}
+
+gboolean spice_channel_connect(SpiceChannel *channel)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+ int rc, err;
+
+ if (c->session == NULL || c->channel_type == -1 || c->channel_id == -1) {
+ /* unset properties or unknown channel type */
+ fprintf(stderr, "%s: channel setup incomplete\n", __FUNCTION__);
+ return false;
+ }
+ if (c->state != SPICE_CHANNEL_STATE_UNCONNECTED) {
+ return true;
+ }
+
+reconnect:
+ c->socket = spice_session_channel_connect(c->session, c->tls);
+ if (c->socket == -1) {
+ if (!c->tls) {
+ c->tls = true;
+ goto reconnect;
+ }
+ spice_channel_emit_event(channel, SPICE_CHANNEL_ERROR_CONNECT);
+ return false;
+ }
+ c->watch = spice_watch_new(c->socket, SPICE_WATCH_EVENT_READ,
+ spice_channel_data, channel);
+
+ if (c->tls) {
+ char *ca_file;
+
+ c->ctx = SSL_CTX_new(TLSv1_method());
+ if (c->ctx == NULL) {
+ PANIC("SSL_CTX_new failed");
+ }
+
+ g_object_get(c->session, "ca-file", &ca_file, NULL);
+ if (ca_file) {
+ rc = SSL_CTX_load_verify_locations(c->ctx, ca_file, NULL);
+ if (rc <= 0) {
+ fprintf(stderr, "loading ca certs from %s failed\n", ca_file);
+ }
+ }
+ SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, tls_verify);
+
+ c->ssl = SSL_new(c->ctx);
+ if (c->ssl == NULL) {
+ PANIC("SSL_new failed");
+ }
+ rc = SSL_set_fd(c->ssl, c->socket);
+ if (rc <= 0) {
+ PANIC("SSL_set_fd failed");
+ }
+ SSL_set_app_data(c->ssl, c);
+ rc = SSL_connect(c->ssl);
+ if (rc <= 0) {
+ err = SSL_get_error(c->ssl, rc);
+ if (err == SSL_ERROR_WANT_READ) {
+ c->state = SPICE_CHANNEL_STATE_TLS;
+ return 0;
+ }
+ fprintf(stderr, "SSL_connect: %s", ERR_error_string(err, NULL));
+ spice_channel_emit_event(channel, SPICE_CHANNEL_ERROR_TLS);
+ }
+ }
+
+ c->state = SPICE_CHANNEL_STATE_LINK_HDR;
+ spice_channel_send_link(channel);
+ return true;
+}
+
+void spice_channel_disconnect(SpiceChannel *channel, enum SpiceChannelEvent reason)
+{
+ spice_channel *c = SPICE_CHANNEL_GET_PRIVATE(channel);
+
+ if (c->state == SPICE_CHANNEL_STATE_UNCONNECTED) {
+ return;
+ }
+
+ if (c->tls) {
+ if (c->ssl) {
+ SSL_free(c->ssl);
+ c->ssl = NULL;
+ }
+ if (c->ctx) {
+ SSL_CTX_free(c->ctx);
+ c->ctx = NULL;
+ }
+ }
+ if (c->watch) {
+ spice_watch_put(c->watch);
+ c->watch = NULL;
+ }
+ if (c->socket != -1) {
+ close(c->socket);
+ c->socket = -1;
+ }
+ c->state = SPICE_CHANNEL_STATE_UNCONNECTED;
+ if (reason != SPICE_CHANNEL_NONE) {
+ spice_channel_emit_event(channel, reason);
+ }
+}
diff --git a/gtk/spice-channel.h b/gtk/spice-channel.h
new file mode 100644
index 0000000..1fb3f35
--- /dev/null
+++ b/gtk/spice-channel.h
@@ -0,0 +1,109 @@
+#ifndef __SPICE_CLIENT_CHANNEL_H__
+#define __SPICE_CLIENT_CHANNEL_H__
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_CHANNEL (spice_channel_get_type ())
+#define SPICE_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_CHANNEL, SpiceChannel))
+#define SPICE_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_CHANNEL, SpiceChannelClass))
+#define SPICE_IS_CHANNEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_CHANNEL))
+#define SPICE_IS_CHANNEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_CHANNEL))
+#define SPICE_CHANNEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_CHANNEL, SpiceChannelClass))
+
+enum SpiceAgentEvent {
+ SPICE_AGENT_NONE = 0,
+ SPICE_AGENT_CONNECT,
+ SPICE_AGENT_DISCONNECT,
+};
+
+enum SpiceChannelEvent {
+ SPICE_CHANNEL_NONE = 0,
+ SPICE_CHANNEL_OPENED = 10,
+ SPICE_CHANNEL_CLOSED,
+ SPICE_CHANNEL_ERROR_CONNECT = 20,
+ SPICE_CHANNEL_ERROR_TLS,
+ SPICE_CHANNEL_ERROR_LINK,
+ SPICE_CHANNEL_ERROR_AUTH,
+ SPICE_CHANNEL_ERROR_IO,
+};
+
+/* Hmm, should better be private ... */
+struct spice_msg_out {
+ int refcount;
+ SpiceChannel *channel;
+ SpiceMessageMarshallers *marshallers;
+ SpiceMarshaller *marshaller;
+ SpiceDataHeader *header;
+};
+
+struct _SpiceChannel
+{
+ GObject parent;
+ spice_channel *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceChannelClass
+{
+ GObjectClass parent_class;
+
+ /* virtual methods */
+ void (*handle_msg)(SpiceChannel *channel, spice_msg_in *msg);
+ void (*channel_up)(SpiceChannel *channel);
+
+ /* common signals */
+ void (*spice_channel_event)(SpiceChannel *channel, enum SpiceChannelEvent event);
+
+ /* display signals */
+ void (*spice_display_primary_create)(SpiceChannel *channel, gint format,
+ gint width, gint height, gint stride,
+ gint shmid, gpointer data);
+ void (*spice_display_primary_destroy)(SpiceChannel *channel);
+ void (*spice_display_invalidate)(SpiceChannel *channel,
+ gint x, gint y, gint w, gint h);
+
+#if 0
+ /*
+ * If adding fields to this struct, remove corresponding
+ * amount of padding to avoid changing overall struct size
+ */
+ gpointer _spice_reserved[42];
+#endif
+};
+
+GType spice_channel_get_type(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+typedef void (*spice_msg_handler)(SpiceChannel *channel, spice_msg_in *in);
+
+SpiceChannel *spice_channel_new(SpiceSession *s, int type, int id);
+void spice_channel_destroy(SpiceChannel *channel);
+gboolean spice_channel_connect(SpiceChannel *channel);
+void spice_channel_disconnect(SpiceChannel *channel, enum SpiceChannelEvent event);
+int spice_channel_id(SpiceChannel *channel);
+
+enum SpiceMouseMode spice_main_get_mouse_mode(SpiceChannel *channel);
+void spice_main_set_display(SpiceChannel *channel, int id,
+ int x, int y, int width, int height);
+
+spice_msg_in *spice_msg_in_new(SpiceChannel *channel);
+void spice_msg_in_get(spice_msg_in *in);
+void spice_msg_in_put(spice_msg_in *in);
+int spice_msg_in_type(spice_msg_in *in);
+void *spice_msg_in_parsed(spice_msg_in *in);
+void *spice_msg_in_raw(spice_msg_in *in, int *len);
+void spice_msg_in_hexdump(spice_msg_in *in);
+
+spice_msg_out *spice_msg_out_new(SpiceChannel *channel, int type);
+void spice_msg_out_get(spice_msg_out *out);
+void spice_msg_out_put(spice_msg_out *out);
+void spice_msg_out_send(spice_msg_out *out);
+void spice_msg_out_hexdump(spice_msg_out *out, unsigned char *data, int len);
+
+/* channel-base.c */
+void spice_channel_handle_set_ack(SpiceChannel *channel, spice_msg_in *in);
+void spice_channel_handle_ping(SpiceChannel *channel, spice_msg_in *in);
+void spice_channel_handle_notify(SpiceChannel *channel, spice_msg_in *in);
+
+#endif /* __SPICE_CLIENT_CHANNEL_H__ */
diff --git a/gtk/spice-client.h b/gtk/spice-client.h
new file mode 100644
index 0000000..63f0843
--- /dev/null
+++ b/gtk/spice-client.h
@@ -0,0 +1,39 @@
+#ifndef __SPICE_CLIENT_CLIENT_H__
+#define __SPICE_CLIENT_CLIENT_H__
+
+/* glib */
+#include <glib.h>
+#include <glib-object.h>
+
+/* spice-protocol */
+#include <spice/enums.h>
+#include <spice/protocol.h>
+
+/* spice/client -- FIXME */
+#include "marshallers.h"
+#include "demarshallers.h"
+
+/* spice/gtk */
+#include "spice-types.h"
+#include "spice-session.h"
+#include "spice-channel.h"
+
+#include "channel-main.h"
+#include "channel-display.h"
+#include "channel-cursor.h"
+#include "channel-inputs.h"
+#include "channel-playback.h"
+
+/* debug bits */
+#define PANIC(fmt, ...) \
+ { fprintf(stderr, "%s:%d " fmt "\n", \
+ __FUNCTION__, __LINE__, ## __VA_ARGS__); \
+ exit(1); }
+
+#define ASSERT(x) if (!(x)) { \
+ { fprintf(stderr,"%s::%d ASSERT(%s) failed\n", \
+ __FUNCTION__, __LINE__, #x); \
+ abort(); } \
+}
+
+#endif /* __SPICE_CLIENT_CLIENT_H__ */
diff --git a/gtk/spice-common.h b/gtk/spice-common.h
new file mode 100644
index 0000000..84cdb8a
--- /dev/null
+++ b/gtk/spice-common.h
@@ -0,0 +1,17 @@
+/* system */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <assert.h>
+
+/* spice/common */
+#include "mem.h"
+#include "messages.h"
+#include "marshaller.h"
+
+/* spice/gtk */
+#include "spice-events.h"
diff --git a/gtk/spice-events.c b/gtk/spice-events.c
new file mode 100644
index 0000000..0a5c0ac
--- /dev/null
+++ b/gtk/spice-events.c
@@ -0,0 +1,64 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+struct spice_watch {
+ GIOChannel *ch;
+ guint rd,wr;
+ spice_watch_func func;
+ void *opaque;
+};
+
+struct spice_timer {
+};
+
+static gboolean watch_rd(GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct spice_watch *watch = data;
+ watch->func(SPICE_WATCH_EVENT_READ, watch->opaque);
+ return TRUE;
+}
+
+static gboolean watch_wr(GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct spice_watch *watch = data;
+ watch->func(SPICE_WATCH_EVENT_WRITE, watch->opaque);
+ return TRUE;
+}
+
+spice_watch *spice_watch_new(int fd, int mask, spice_watch_func func, void *opaque)
+{
+ spice_watch *watch;
+
+ watch = spice_new0(spice_watch, 1);
+ watch->ch = g_io_channel_unix_new(fd);
+ if (mask & SPICE_WATCH_EVENT_READ)
+ watch->rd = g_io_add_watch(watch->ch, G_IO_IN, watch_rd, watch);
+ if (mask & SPICE_WATCH_EVENT_WRITE)
+ watch->wr = g_io_add_watch(watch->ch, G_IO_IN, watch_wr, watch);
+ watch->func = func;
+ watch->opaque = opaque;
+ return watch;
+}
+
+void spice_watch_put(spice_watch *watch)
+{
+ if (watch->rd)
+ g_source_destroy(g_main_context_find_source_by_id
+ (g_main_context_default(), watch->rd));
+ if (watch->wr)
+ g_source_destroy(g_main_context_find_source_by_id
+ (g_main_context_default(), watch->wr));
+ free(watch);
+}
+
+spice_timer *spice_timer_new(spice_timer_func func, void *opaque)
+{
+ /* to be implemented */
+ return NULL;
+}
+
+void spice_timer_put(spice_timer *timer)
+{
+}
diff --git a/gtk/spice-events.h b/gtk/spice-events.h
new file mode 100644
index 0000000..6803647
--- /dev/null
+++ b/gtk/spice-events.h
@@ -0,0 +1,19 @@
+#ifndef __SPICE_CLIENT_EVENTS_H__
+#define __SPICE_CLIENT_EVENTS_H__
+
+typedef struct spice_watch spice_watch;
+typedef struct spice_timer spice_timer;
+
+#define SPICE_WATCH_EVENT_READ (1 << 0)
+#define SPICE_WATCH_EVENT_WRITE (1 << 1)
+
+typedef void (*spice_watch_func)(int event, void *opaque);
+typedef void (*spice_timer_func)(void *opaque);
+
+spice_watch *spice_watch_new(int fd, int mask, spice_watch_func func, void *opaque);
+void spice_watch_put(spice_watch *watch);
+
+spice_timer *spice_timer_new(spice_timer_func func, void *opaque);
+void spice_timer_put(spice_timer *timer);
+
+#endif /* __SPICE_CLIENT_EVENTS_H__ */
diff --git a/gtk/spice-marshal.txt b/gtk/spice-marshal.txt
new file mode 100644
index 0000000..f11ce4d
--- /dev/null
+++ b/gtk/spice-marshal.txt
@@ -0,0 +1,6 @@
+VOID:INT,INT
+VOID:INT,INT,INT
+VOID:INT,INT,INT,INT
+VOID:INT,INT,INT,INT,POINTER
+VOID:INT,INT,INT,INT,INT,POINTER
+VOID:POINTER,INT
diff --git a/gtk/spice-pulse.c b/gtk/spice-pulse.c
new file mode 100644
index 0000000..7c363f1
--- /dev/null
+++ b/gtk/spice-pulse.c
@@ -0,0 +1,183 @@
+#include "spice-pulse.h"
+#include "spice-common.h"
+
+#include <pulse/glib-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/sample.h>
+
+#define SPICE_PULSE_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_PULSE, spice_pulse))
+
+struct stream {
+ pa_sample_spec spec;
+ pa_stream *stream;
+};
+
+struct spice_pulse {
+ SpiceSession *session;
+ SpiceChannel *pchannel;
+
+ pa_glib_mainloop *mainloop;
+ pa_context *context;
+ struct stream playback;
+};
+
+G_DEFINE_TYPE(SpicePulse, spice_pulse, G_TYPE_OBJECT)
+
+static const char *stream_state_names[] = {
+ [ PA_STREAM_UNCONNECTED ] = "unconnected",
+ [ PA_STREAM_CREATING ] = "creating",
+ [ PA_STREAM_READY ] = "ready",
+ [ PA_STREAM_FAILED ] = "failed",
+ [ PA_STREAM_TERMINATED ] = "terminated",
+};
+
+static const char *context_state_names[] = {
+ [ PA_CONTEXT_UNCONNECTED ] = "unconnected",
+ [ PA_CONTEXT_CONNECTING ] = "connecting",
+ [ PA_CONTEXT_AUTHORIZING ] = "authorizing",
+ [ PA_CONTEXT_SETTING_NAME ] = "setting_name",
+ [ PA_CONTEXT_READY ] = "ready",
+ [ PA_CONTEXT_FAILED ] = "failed",
+ [ PA_CONTEXT_TERMINATED ] = "terminated",
+};
+#define STATE_NAME(array, state) \
+ ((state < G_N_ELEMENTS(array)) ? array[state] : NULL)
+
+static void spice_pulse_finalize(GObject *obj)
+{
+ G_OBJECT_CLASS(spice_pulse_parent_class)->finalize(obj);
+}
+
+static void spice_pulse_init(SpicePulse *pulse)
+{
+ spice_pulse *p;
+
+ p = pulse->priv = SPICE_PULSE_GET_PRIVATE(pulse);
+ memset(p, 0, sizeof(*p));
+}
+
+static void spice_pulse_class_init(SpicePulseClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->finalize = spice_pulse_finalize;
+
+ g_type_class_add_private(klass, sizeof(spice_pulse));
+}
+
+/* ------------------------------------------------------------------ */
+
+static void playback_start(SpicePlaybackChannel *channel, gint format, gint channels,
+ gint frequency, gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_context_state_t state;
+
+ state = pa_context_get_state(p->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ if (p->playback.stream &&
+ (p->playback.spec.rate != frequency ||
+ p->playback.spec.channels != channels)) {
+ pa_stream_disconnect(p->playback.stream);
+ pa_stream_unref(p->playback.stream);
+ p->playback.stream = NULL;
+ }
+ if (p->playback.stream == NULL) {
+ assert(format == SPICE_AUDIO_FMT_S16);
+ p->playback.spec.format = PA_SAMPLE_S16LE;
+ p->playback.spec.rate = frequency;
+ p->playback.spec.channels = channels;
+ p->playback.stream = pa_stream_new(p->context, "playback",
+ &p->playback.spec, NULL);
+ pa_stream_connect_playback(p->playback.stream, NULL, NULL, 0, NULL, NULL);
+ }
+ if (pa_stream_is_corked(p->playback.stream)) {
+ pa_stream_cork(p->playback.stream, 0, NULL, NULL);
+ }
+ break;
+ default:
+ fprintf(stderr, "%s: pulse context not ready (%s)\n",
+ __FUNCTION__, STATE_NAME(context_state_names, state));
+ break;
+ }
+}
+
+static void playback_data(SpicePlaybackChannel *channel, gpointer *audio, gint size,
+ gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = SPICE_PULSE_GET_PRIVATE(pulse);
+ pa_stream_state_t state;
+
+ if (!p->playback.stream)
+ return;
+
+ state = pa_stream_get_state(p->playback.stream);
+ switch (state) {
+ case PA_STREAM_READY:
+ pa_stream_write(p->playback.stream, audio, size, NULL, 0, PA_SEEK_RELATIVE);
+ break;
+ default:
+ fprintf(stderr, "%s: pulse playback stream not ready (%s)\n",
+ __FUNCTION__, STATE_NAME(stream_state_names, state));
+ break;
+ }
+}
+
+static void playback_stop(SpicePlaybackChannel *channel, gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = pulse->priv;
+
+ if (!p->playback.stream)
+ return;
+
+ pa_stream_cork(p->playback.stream, 1, NULL, NULL);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+ SpicePulse *pulse = data;
+ spice_pulse *p = pulse->priv;
+
+ if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+ p->pchannel = channel;
+ g_signal_connect(channel, "spice-playback-start",
+ G_CALLBACK(playback_start), pulse);
+ g_signal_connect(channel, "spice-playback-data",
+ G_CALLBACK(playback_data), pulse);
+ g_signal_connect(channel, "spice-playback-stop",
+ G_CALLBACK(playback_stop), pulse);
+ spice_channel_connect(channel);
+ }
+}
+
+SpicePulse *spice_pulse_new(SpiceSession *session, GMainLoop *mainloop,
+ const char *name)
+{
+ SpicePulse *pulse;
+ spice_pulse *p;
+ SpiceChannel *channels[16];
+ int i, n;
+
+ pulse = g_object_new(SPICE_TYPE_PULSE, NULL);
+ p = SPICE_PULSE_GET_PRIVATE(pulse);
+ p->session = session;
+
+ g_signal_connect(session, "spice-session-channel-new",
+ G_CALLBACK(channel_new), pulse);
+ n = spice_session_get_channels(session, channels, SPICE_N_ELEMENTS(channels));
+ for (i = 0; i < n; i++) {
+ channel_new(session, channels[i], (gpointer*)pulse);
+ }
+
+ p->mainloop = pa_glib_mainloop_new(g_main_loop_get_context(mainloop));
+ p->context = pa_context_new(pa_glib_mainloop_get_api(p->mainloop), name);
+ pa_context_connect(p->context, NULL, 0, NULL);
+
+ return pulse;
+}
diff --git a/gtk/spice-pulse.h b/gtk/spice-pulse.h
new file mode 100644
index 0000000..b3785e6
--- /dev/null
+++ b/gtk/spice-pulse.h
@@ -0,0 +1,38 @@
+#ifndef __SPICE_CLIENT_PULSE_H__
+#define __SPICE_CLIENT_PULSE_H__
+
+#include "spice-client.h"
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_PULSE (spice_pulse_get_type())
+#define SPICE_PULSE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_PULSE, SpicePulse))
+#define SPICE_PULSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_PULSE, SpicePulseClass))
+#define SPICE_IS_PULSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_PULSE))
+#define SPICE_IS_PULSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_PULSE))
+#define SPICE_PULSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_PULSE, SpicePulseClass))
+
+
+typedef struct _SpicePulse SpicePulse;
+typedef struct _SpicePulseClass SpicePulseClass;
+typedef struct spice_pulse spice_pulse;
+
+struct _SpicePulse {
+ GObject parent;
+ spice_pulse *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpicePulseClass {
+ GObjectClass parent_class;
+ /* Do not add fields to this struct */
+};
+
+GType spice_pulse_get_type(void);
+
+SpicePulse *spice_pulse_new(SpiceSession *session, GMainLoop *mainloop,
+ const char *name);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_PULSE_H__ */
diff --git a/gtk/spice-session-priv.h b/gtk/spice-session-priv.h
new file mode 100644
index 0000000..2d62d48
--- /dev/null
+++ b/gtk/spice-session-priv.h
@@ -0,0 +1,11 @@
+#ifndef __SPICE_CLIENT_SESSION_PRIV_H__
+#define __SPICE_CLIENT_SESSION_PRIV_H__
+
+void spice_session_set_connection_id(SpiceSession *session, int id);
+int spice_session_get_connection_id(SpiceSession *session);
+
+int spice_session_channel_connect(SpiceSession *session, bool use_tls);
+void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel);
+void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel);
+
+#endif /* __SPICE_CLIENT_SESSION_PRIV_H__ */
diff --git a/gtk/spice-session.c b/gtk/spice-session.c
new file mode 100644
index 0000000..157a30e
--- /dev/null
+++ b/gtk/spice-session.c
@@ -0,0 +1,374 @@
+#include "spice-client.h"
+#include "spice-common.h"
+
+#include "spice-session-priv.h"
+#include "tcp.h"
+
+/* spice/common */
+#include "ring.h"
+
+struct channel {
+ SpiceChannel *channel;
+ RingItem link;
+};
+
+struct spice_session {
+ char *host;
+ char *port;
+ char *tls_port;
+ char *password;
+ char *ca_file;
+ struct addrinfo ai;
+ int connection_id;
+ SpiceChannel *cmain;
+ Ring channels;
+};
+
+/* ------------------------------------------------------------------ */
+/* gobject glue */
+
+#define SPICE_SESSION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), SPICE_TYPE_SESSION, spice_session))
+
+G_DEFINE_TYPE (SpiceSession, spice_session, G_TYPE_OBJECT);
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_HOST,
+ PROP_PORT,
+ PROP_TLS_PORT,
+ PROP_PASSWORD,
+ PROP_CA_FILE,
+ PROP_IPV4,
+ PROP_IPV6,
+};
+
+/* signals */
+enum {
+ SPICE_SESSION_CHANNEL_NEW,
+ SPICE_SESSION_CHANNEL_DESTROY,
+ SPICE_SESSION_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_SESSION_LAST_SIGNAL];
+
+static void spice_session_init(SpiceSession *session)
+{
+ spice_session *s;
+
+ s = session->priv = SPICE_SESSION_GET_PRIVATE(session);
+ memset(s, 0, sizeof(*s));
+
+ s->host = strdup("localhost");
+ s->ai.ai_socktype = SOCK_STREAM;
+ s->ai.ai_family = PF_UNSPEC;
+
+ ring_init(&s->channels);
+}
+
+static void
+spice_session_dispose(GObject *gobject)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+
+ spice_session_disconnect(session);
+
+ /* Chain up to the parent class */
+ if (G_OBJECT_CLASS(spice_session_parent_class)->dispose)
+ G_OBJECT_CLASS(spice_session_parent_class)->dispose(gobject);
+}
+
+static void
+spice_session_finalize(GObject *gobject)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ /* release stuff */
+ free(s->host);
+ free(s->port);
+ free(s->tls_port);
+ free(s->password);
+ free(s->ca_file);
+
+ /* Chain up to the parent class */
+ if (G_OBJECT_CLASS(spice_session_parent_class)->finalize)
+ G_OBJECT_CLASS(spice_session_parent_class)->finalize(gobject);
+}
+
+static void spice_session_get_property(GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ switch (prop_id) {
+ case PROP_HOST:
+ g_value_set_string(value, s->host);
+ break;
+ case PROP_PORT:
+ g_value_set_string(value, s->port);
+ break;
+ case PROP_TLS_PORT:
+ g_value_set_string(value, s->tls_port);
+ break;
+ case PROP_PASSWORD:
+ g_value_set_string(value, s->password);
+ break;
+ case PROP_CA_FILE:
+ g_value_set_string(value, s->ca_file);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_session_set_property(GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceSession *session = SPICE_SESSION(gobject);
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ const char *str;
+
+ switch (prop_id) {
+ case PROP_HOST:
+ free(s->host);
+ str = g_value_get_string(value);
+ s->host = str ? strdup(str) : NULL;
+ break;
+ case PROP_PORT:
+ free(s->port);
+ str = g_value_get_string(value);
+ s->port = str ? strdup(str) : NULL;
+ break;
+ case PROP_TLS_PORT:
+ free(s->tls_port);
+ str = g_value_get_string(value);
+ s->tls_port = str ? strdup(str) : NULL;
+ break;
+ case PROP_PASSWORD:
+ free(s->password);
+ str = g_value_get_string(value);
+ s->password = str ? strdup(str) : NULL;
+ break;
+ case PROP_CA_FILE:
+ free(s->ca_file);
+ str = g_value_get_string(value);
+ s->ca_file = str ? strdup(str) : NULL;
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_session_class_init(SpiceSessionClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->dispose = spice_session_dispose;
+ gobject_class->finalize = spice_session_finalize;
+ gobject_class->get_property = spice_session_get_property;
+ gobject_class->set_property = spice_session_set_property;
+
+ g_object_class_install_property
+ (gobject_class, PROP_HOST,
+ g_param_spec_string("host",
+ "Host",
+ "remote host",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_PORT,
+ g_param_spec_string("port",
+ "Port",
+ "remote port (plaintext)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_TLS_PORT,
+ g_param_spec_string("tls-port",
+ "TLS port",
+ "remote port (encrypted)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_PASSWORD,
+ g_param_spec_string("password",
+ "Password",
+ "",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_CA_FILE,
+ g_param_spec_string("ca-file",
+ "CA file",
+ "File holding the CA certificates",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ signals[SPICE_SESSION_CHANNEL_NEW] =
+ g_signal_new("spice-session-channel-new",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceSessionClass, spice_session_channel_new),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ SPICE_TYPE_CHANNEL);
+
+ signals[SPICE_SESSION_CHANNEL_DESTROY] =
+ g_signal_new("spice-session-channel-destroy",
+ G_OBJECT_CLASS_TYPE(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET(SpiceSessionClass, spice_session_channel_destroy),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ SPICE_TYPE_CHANNEL);
+
+ g_type_class_add_private(klass, sizeof(spice_session));
+}
+
+/* ------------------------------------------------------------------ */
+/* public functions */
+
+SpiceSession *spice_session_new()
+{
+ return SPICE_SESSION(g_object_new(SPICE_TYPE_SESSION,
+ NULL));
+}
+
+gboolean spice_session_connect(SpiceSession *session)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ spice_session_disconnect(session);
+ s->cmain = spice_channel_new(session, SPICE_CHANNEL_MAIN, 0);
+ return spice_channel_connect(s->cmain);
+}
+
+void spice_session_disconnect(SpiceSession *session)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ struct channel *item;
+ RingItem *ring, *next;
+
+ if (s->cmain == NULL) {
+ return;
+ }
+
+ for (ring = ring_get_head(&s->channels); ring != NULL; ring = next) {
+ next = ring_next(&s->channels, ring);
+ item = SPICE_CONTAINEROF(ring, struct channel, link);
+ spice_channel_destroy(item->channel);
+ }
+
+ s->connection_id = 0;
+ s->cmain = NULL;
+}
+
+int spice_session_get_channels(SpiceSession *session, SpiceChannel **channels, int max)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ struct channel *item;
+ RingItem *ring;
+ int i;
+
+ for (i = 0, ring = ring_get_head(&s->channels);
+ i < max && ring != NULL;
+ i++, ring = ring_next(&s->channels, ring)) {
+ item = SPICE_CONTAINEROF(ring, struct channel, link);
+ channels[i] = item->channel;
+ }
+ return i;
+}
+
+/* ------------------------------------------------------------------ */
+/* private functions */
+
+int spice_session_channel_connect(SpiceSession *session, bool use_tls)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ char *port = use_tls ? s->tls_port : s->port;
+
+ if (port == NULL) {
+ return -1;
+ }
+ return tcp_connect(&s->ai, NULL, NULL, s->host, port);
+}
+
+void spice_session_channel_new(SpiceSession *session, SpiceChannel *channel)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ struct channel *item;
+
+ item = spice_new0(struct channel, 1);
+ item->channel = channel;
+ ring_add(&s->channels, &item->link);
+ g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_NEW], 0, channel);
+}
+
+void spice_session_channel_destroy(SpiceSession *session, SpiceChannel *channel)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+ struct channel *item = NULL;
+ RingItem *ring;
+
+ for (ring = ring_get_head(&s->channels); ring != NULL;
+ ring = ring_next(&s->channels, ring)) {
+ item = SPICE_CONTAINEROF(ring, struct channel, link);
+ if (item->channel == channel) {
+ ring_remove(&item->link);
+ free(item);
+ g_signal_emit(session, signals[SPICE_SESSION_CHANNEL_DESTROY], 0, channel);
+ return;
+ }
+ }
+}
+
+void spice_session_set_connection_id(SpiceSession *session, int id)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ s->connection_id = id;
+}
+
+int spice_session_get_connection_id(SpiceSession *session)
+{
+ spice_session *s = SPICE_SESSION_GET_PRIVATE(session);
+
+ return s->connection_id;
+}
diff --git a/gtk/spice-session.h b/gtk/spice-session.h
new file mode 100644
index 0000000..4e1269a
--- /dev/null
+++ b/gtk/spice-session.h
@@ -0,0 +1,46 @@
+#ifndef __SPICE_CLIENT_SESSION_H__
+#define __SPICE_CLIENT_SESSION_H__
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_SESSION (spice_session_get_type ())
+#define SPICE_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SPICE_TYPE_SESSION, SpiceSession))
+#define SPICE_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SPICE_TYPE_SESSION, SpiceSessionClass))
+#define SPICE_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SPICE_TYPE_SESSION))
+#define SPICE_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SPICE_TYPE_SESSION))
+#define SPICE_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SPICE_TYPE_SESSION, SpiceSessionClass))
+
+struct _SpiceSession
+{
+ GObject parent;
+ spice_session *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceSessionClass
+{
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*spice_session_channel_new)(SpiceSession *session, SpiceChannel *channel);
+ void (*spice_session_channel_destroy)(SpiceSession *session, SpiceChannel *channel);
+
+#if 0
+ /*
+ * If adding fields to this struct, remove corresponding
+ * amount of padding to avoid changing overall struct size
+ */
+ gpointer _spice_reserved[42];
+#endif
+};
+
+GType spice_session_get_type(void) G_GNUC_CONST;
+
+SpiceSession *spice_session_new(void);
+gboolean spice_session_connect(SpiceSession *session);
+void spice_session_disconnect(SpiceSession *session);
+int spice_session_get_channels(SpiceSession *session, SpiceChannel **channels, int max);
+
+G_END_DECLS
+
+#endif /* __SPICE_CLIENT_SESSION_H__ */
diff --git a/gtk/spice-types.h b/gtk/spice-types.h
new file mode 100644
index 0000000..e07a924
--- /dev/null
+++ b/gtk/spice-types.h
@@ -0,0 +1,17 @@
+#ifndef __SPICE_CLIENT_TYPES_H__
+#define __SPICE_CLIENT_TYPES_H__
+
+/* SpiceSession */
+typedef struct _SpiceSession SpiceSession;
+typedef struct _SpiceSessionClass SpiceSessionClass;
+typedef struct spice_session spice_session;
+
+/* SpiceChannel */
+typedef struct _SpiceChannel SpiceChannel;
+typedef struct _SpiceChannelClass SpiceChannelClass;
+typedef struct spice_channel spice_channel;
+
+typedef struct spice_msg_in spice_msg_in;
+typedef struct spice_msg_out spice_msg_out;
+
+#endif /* __SPICE_CLIENT_TYPES_H__ */
diff --git a/gtk/spice-widget.c b/gtk/spice-widget.c
new file mode 100644
index 0000000..8061025
--- /dev/null
+++ b/gtk/spice-widget.c
@@ -0,0 +1,1045 @@
+#include "spice-widget.h"
+#include "spice-common.h"
+
+#include "vncdisplaykeymap.h"
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XShm.h>
+
+#include <gdk/gdkx.h>
+
+#define SPICE_DISPLAY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((obj), SPICE_TYPE_DISPLAY, spice_display))
+
+struct spice_display {
+ gint channel_id;
+
+ /* options */
+ bool keyboard_grab_enable;
+ bool mouse_grab_enable;
+ bool resize_guest_enable;
+
+ /* state */
+ enum SpiceSurfaceFmt format;
+ gint width, height, stride;
+ gint shmid;
+ gpointer data;
+
+ gint ww, wh, mx, my;
+
+ bool convert;
+ bool have_mitshm;
+ Display *dpy;
+ XVisualInfo *vi;
+ XImage *ximage;
+ XShmSegmentInfo *shminfo;
+ GC gc;
+
+ SpiceSession *session;
+ SpiceChannel *main;
+ SpiceChannel *display;
+ SpiceCursorChannel *cursor;
+ SpiceInputsChannel *inputs;
+
+ enum SpiceMouseMode mouse_mode;
+ int mouse_grab_active;
+ bool mouse_have_pointer;
+ GdkCursor *mouse_cursor;
+ int mouse_last_x;
+ int mouse_last_y;
+
+ bool keyboard_grab_active;
+ bool keyboard_have_focus;
+ int keyboard_grab_count;
+ time_t keyboard_grab_time;
+
+ const guint16 const *keycode_map;
+ size_t keycode_maplen;
+
+ gint timer_id;
+};
+
+G_DEFINE_TYPE(SpiceDisplay, spice_display, GTK_TYPE_DRAWING_AREA)
+
+/* Properties */
+enum {
+ PROP_0,
+ PROP_KEYBOARD_GRAB,
+ PROP_MOUSE_GRAB,
+ PROP_RESIZE_GUEST,
+};
+
+#if 0
+/* Signals */
+enum {
+ SPICE_DISPLAY_FOO,
+ SPICE_DISPLAY_LAST_SIGNAL,
+};
+
+static guint signals[SPICE_DISPLAY_LAST_SIGNAL];
+#endif
+
+static bool no_mitshm;
+
+static void try_keyboard_grab(GtkWidget *widget);
+static void try_keyboard_ungrab(GtkWidget *widget);
+static void try_mouse_grab(GtkWidget *widget);
+static void try_mouse_ungrab(GtkWidget *widget);
+static void recalc_geometry(GtkWidget *widget);
+
+/* ---------------------------------------------------------------- */
+
+static struct format_table {
+ enum SpiceSurfaceFmt spice;
+ XVisualInfo xvisual;
+} format_table[] = {
+ {
+ .spice = SPICE_SURFACE_FMT_32_xRGB,
+ .xvisual = {
+ .depth = 24,
+ .red_mask = 0xff0000,
+ .green_mask = 0x00ff00,
+ .blue_mask = 0x0000ff,
+ },
+ },{
+ .spice = SPICE_SURFACE_FMT_16_555,
+ .xvisual = {
+ .depth = 16,
+ .red_mask = 0x7c00,
+ .green_mask = 0x03e0,
+ .blue_mask = 0x001f,
+ },
+ },{
+ .spice = SPICE_SURFACE_FMT_16_565,
+ .xvisual = {
+ .depth = 16,
+ .red_mask = 0xf800,
+ .green_mask = 0x07e0,
+ .blue_mask = 0x001f,
+ },
+ }
+};
+
+/* ---------------------------------------------------------------- */
+
+static void spice_display_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(object);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ switch (prop_id) {
+ case PROP_KEYBOARD_GRAB:
+ g_value_set_boolean(value, d->keyboard_grab_enable);
+ break;
+ case PROP_MOUSE_GRAB:
+ g_value_set_boolean(value, d->mouse_grab_enable);
+ break;
+ case PROP_RESIZE_GUEST:
+ g_value_set_boolean(value, d->resize_guest_enable);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_display_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(object);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ switch (prop_id) {
+ case PROP_KEYBOARD_GRAB:
+ d->keyboard_grab_enable = g_value_get_boolean(value);
+ if (d->keyboard_grab_enable) {
+ try_keyboard_grab(GTK_WIDGET(display));
+ } else {
+ try_keyboard_ungrab(GTK_WIDGET(display));
+ }
+ break;
+ case PROP_MOUSE_GRAB:
+ d->mouse_grab_enable = g_value_get_boolean(value);
+ if (!d->mouse_grab_enable) {
+ try_mouse_ungrab(GTK_WIDGET(display));
+ }
+ break;
+ case PROP_RESIZE_GUEST:
+ d->resize_guest_enable = g_value_get_boolean(value);
+ if (d->resize_guest_enable) {
+ gtk_widget_set_size_request(GTK_WIDGET(display), 640, 480);
+ recalc_geometry(GTK_WIDGET(display));
+ } else {
+ gtk_widget_set_size_request(GTK_WIDGET(display),
+ d->width, d->height);
+ }
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void spice_display_destroy(GtkObject *obj)
+{
+ GTK_OBJECT_CLASS(spice_display_parent_class)->destroy(obj);
+}
+
+static void spice_display_finalize(GObject *obj)
+{
+ G_OBJECT_CLASS(spice_display_parent_class)->finalize(obj);
+}
+
+static void spice_display_init(SpiceDisplay *display)
+{
+ GtkWidget *widget = GTK_WIDGET(display);
+ spice_display *d;
+
+ d = display->priv = SPICE_DISPLAY_GET_PRIVATE(display);
+ memset(d, 0, sizeof(*d));
+
+ gtk_widget_add_events(widget,
+ GDK_STRUCTURE_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_KEY_PRESS_MASK);
+ gtk_widget_set_double_buffered(widget, false);
+ gtk_widget_set_can_focus(widget, true);
+
+ d->keycode_map = vnc_display_keymap_gdk2xtkbd_table(&d->keycode_maplen);
+
+ d->have_mitshm = true;
+}
+
+static void try_keyboard_grab(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ time_t now;
+
+ if (d->keyboard_grab_active)
+ return;
+
+ if (!d->keyboard_grab_enable)
+ return;
+ if (!d->keyboard_have_focus)
+ return;
+ if (!d->mouse_have_pointer)
+ return;
+
+#if 1
+ /*
+ * == DEBUG ==
+ * focus / keyboard grab behavior is funky
+ * when going fullscreen (with KDE):
+ * focus-in-event -> grab -> focus-out-event -> ungrab -> repeat
+ * I have no idea why the grab triggers focus-out :-(
+ */
+ assert(gtk_widget_is_focus(widget));
+ assert(gtk_widget_has_focus(widget));
+
+ now = time(NULL);
+ if (d->keyboard_grab_time != now) {
+ d->keyboard_grab_time = now;
+ d->keyboard_grab_count = 0;
+ }
+ if (d->keyboard_grab_count++ > 32) {
+ fprintf(stderr, "%s: 32 grabs last second -> emergency exit\n",
+ __FUNCTION__);
+ return;
+ }
+#endif
+
+#if 0
+ fprintf(stderr, "grab keyboard\n");
+#endif
+
+ gdk_keyboard_grab(gtk_widget_get_window(widget), FALSE,
+ GDK_CURRENT_TIME);
+ d->keyboard_grab_active = true;
+}
+
+
+static void try_keyboard_ungrab(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ if (!d->keyboard_grab_active)
+ return;
+
+#if 0
+ fprintf(stderr, "ungrab keyboard\n");
+#endif
+ gdk_keyboard_ungrab(GDK_CURRENT_TIME);
+ d->keyboard_grab_active = false;
+}
+
+static void try_mouse_grab(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ if (!d->mouse_grab_enable)
+ return;
+ if (d->mouse_mode != SPICE_MOUSE_MODE_SERVER)
+ return;
+ if (d->mouse_grab_active)
+ return;
+
+ gdk_pointer_grab(gtk_widget_get_window(widget),
+ FALSE, /* All events to come to our window directly */
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK,
+ NULL, /* Allow cursor to move over entire desktop */
+ gdk_cursor_new(GDK_BLANK_CURSOR),
+ GDK_CURRENT_TIME);
+ d->mouse_grab_active = true;
+ d->mouse_last_x = -1;
+ d->mouse_last_y = -1;
+}
+
+static void mouse_check_edges(GtkWidget *widget, GdkEventMotion *motion)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ GdkDrawable *drawable = GDK_DRAWABLE(gtk_widget_get_window(widget));
+ GdkScreen *screen = gdk_drawable_get_screen(drawable);
+ int x = (int)motion->x_root;
+ int y = (int)motion->y_root;
+
+ /* In relative mode check to see if client pointer hit
+ * one of the screen edges, and if so move it back by
+ * 200 pixels. This is important because the pointer
+ * in the server doesn't correspond 1-for-1, and so
+ * may still be only half way across the screen. Without
+ * this warp, the server pointer would thus appear to hit
+ * an invisible wall */
+ if (x == 0) x += 200;
+ if (y == 0) y += 200;
+ if (x == (gdk_screen_get_width(screen) - 1)) x -= 200;
+ if (y == (gdk_screen_get_height(screen) - 1)) y -= 200;
+
+ if (x != (int)motion->x_root || y != (int)motion->y_root) {
+ gdk_display_warp_pointer(gdk_drawable_get_display(drawable),
+ screen, x, y);
+ d->mouse_last_x = -1;
+ d->mouse_last_y = -1;
+ }
+}
+
+static void try_mouse_ungrab(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ if (!d->mouse_grab_active)
+ return;
+
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+ gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
+ d->mouse_grab_active = false;
+}
+
+static gboolean geometry_timer(gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ d->timer_id = 0;
+ spice_main_set_display(d->main, d->channel_id,
+ 0, 0, d->ww, d->wh);
+ return false;
+}
+
+static void recalc_geometry(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ d->mx = 0;
+ d->my = 0;
+ if (d->ww > d->width)
+ d->mx = (d->ww - d->width) / 2;
+ if (d->wh > d->height)
+ d->my = (d->wh - d->height) / 2;
+
+#if 0
+ fprintf(stderr, "%s: guest %dx%d, window %dx%d, offset +%d+%d\n", __FUNCTION__,
+ d->width, d->height, d->ww, d->wh, d->mx, d->my);
+#endif
+
+ if (d->timer_id) {
+ g_source_remove(d->timer_id);
+ }
+ if (d->resize_guest_enable) {
+ d->timer_id = g_timeout_add_seconds(1, geometry_timer, display);
+ }
+}
+
+static XVisualInfo *get_visual_for_format(GtkWidget *widget, enum SpiceSurfaceFmt format)
+{
+ GdkDrawable *drawable = gtk_widget_get_window(widget);
+ GdkDisplay *display = gdk_drawable_get_display(drawable);
+ GdkScreen *screen = gdk_drawable_get_screen(drawable);
+ XVisualInfo template;
+ int found, i;
+
+ for (i = 0; i < SPICE_N_ELEMENTS(format_table); i++) {
+ if (format == format_table[i].spice)
+ break;
+ }
+ if (i == SPICE_N_ELEMENTS(format_table))
+ return NULL;
+
+ template = format_table[i].xvisual;
+ template.screen = gdk_x11_screen_get_screen_number(screen);
+ return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
+ VisualScreenMask | VisualDepthMask |
+ VisualRedMaskMask | VisualGreenMaskMask | VisualBlueMaskMask,
+ &template, &found);
+}
+
+static XVisualInfo *get_visual_default(GtkWidget *widget)
+{
+ GdkDrawable *drawable = gtk_widget_get_window(widget);
+ GdkDisplay *display = gdk_drawable_get_display(drawable);
+ GdkScreen *screen = gdk_drawable_get_screen(drawable);
+ XVisualInfo template;
+ int found;
+
+ template.screen = gdk_x11_screen_get_screen_number(screen);
+ return XGetVisualInfo(gdk_x11_display_get_xdisplay(display),
+ VisualScreenMask,
+ &template, &found);
+}
+
+static int catch_no_mitshm(Display * dpy, XErrorEvent * event)
+{
+ no_mitshm = true;
+ return 0;
+}
+
+static int ximage_create(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ GdkDrawable *window = gtk_widget_get_window(widget);
+ GdkDisplay *gtkdpy = gdk_drawable_get_display(window);
+ void *old_handler = NULL;
+ XGCValues gcval = {
+ .foreground = 0,
+ .background = 0,
+ };
+
+ d->dpy = gdk_x11_display_get_xdisplay(gtkdpy);
+ d->convert = false;
+ d->vi = get_visual_for_format(widget, d->format);
+ if (d->vi == NULL) {
+ d->convert = true;
+ d->vi = get_visual_default(widget);
+ ASSERT(d->vi != NULL);
+ }
+ if (d->convert) {
+ PANIC("format conversion not implemented yet");
+ }
+
+ d->gc = XCreateGC(d->dpy, gdk_x11_drawable_get_xid(window),
+ GCForeground | GCBackground, &gcval);
+
+ if (d->have_mitshm && d->shmid != -1) {
+ if (!XShmQueryExtension(d->dpy)) {
+ goto shm_fail;
+ }
+ no_mitshm = false;
+ old_handler = XSetErrorHandler(catch_no_mitshm);
+ d->shminfo = spice_new0(XShmSegmentInfo, 1);
+ d->ximage = XShmCreateImage(d->dpy, d->vi->visual, d->vi->depth,
+ ZPixmap, d->data, d->shminfo, d->width, d->height);
+ if (d->ximage == NULL)
+ goto shm_fail;
+ d->shminfo->shmaddr = d->data;
+ d->shminfo->shmid = d->shmid;
+ d->shminfo->readOnly = false;
+ XShmAttach(d->dpy, d->shminfo);
+ XSync(d->dpy, False);
+ shmctl(d->shmid, IPC_RMID, 0);
+ if (no_mitshm)
+ goto shm_fail;
+ XSetErrorHandler(old_handler);
+ return 0;
+ }
+
+shm_fail:
+ d->have_mitshm = false;
+ if (old_handler)
+ XSetErrorHandler(old_handler);
+ d->ximage = XCreateImage(d->dpy, d->vi->visual, d->vi->depth, ZPixmap, 0,
+ d->data, d->width, d->height, 32, d->stride);
+ return 0;
+}
+
+static void ximage_destroy(GtkWidget *widget)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ if (d->ximage) {
+ XDestroyImage(d->ximage);
+ d->ximage = NULL;
+ }
+ if (d->shminfo) {
+ XShmDetach(d->dpy, d->shminfo);
+ free(d->shminfo);
+ d->shminfo = NULL;
+ }
+ if (d->gc) {
+ XFreeGC(d->dpy, d->gc);
+ d->gc = NULL;
+ }
+}
+
+static gboolean expose_event(GtkWidget *widget, GdkEventExpose *expose)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ GdkDrawable *window = gtk_widget_get_window(widget);
+
+#if 0
+ fprintf(stderr, "%s: area %dx%d at %d,%d\n", __FUNCTION__,
+ expose->area.width,
+ expose->area.height,
+ expose->area.x,
+ expose->area.y);
+#endif
+
+ if (d->data == NULL)
+ return true;
+ if (!d->ximage) {
+ ximage_create(widget);
+ }
+
+ if (expose->area.x >= d->mx &&
+ expose->area.y >= d->my &&
+ expose->area.x + expose->area.width <= d->mx + d->width &&
+ expose->area.y + expose->area.height <= d->my + d->height) {
+ /* area is completely inside the guest screen -- blit it */
+ if (d->have_mitshm) {
+ XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, d->ximage,
+ expose->area.x - d->mx, expose->area.y - d->my,
+ expose->area.x, expose->area.y,
+ expose->area.width, expose->area.height,
+ true);
+ } else {
+ XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, d->ximage,
+ expose->area.x - d->mx, expose->area.y - d->my,
+ expose->area.x, expose->area.y,
+ expose->area.width, expose->area.height);
+ }
+ } else {
+ /* complete window update */
+ if (d->ww > d->width || d->wh > d->height) {
+ int x1 = d->mx;
+ int x2 = d->mx + d->width;
+ int y1 = d->my;
+ int y2 = d->my + d->height;
+ XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, 0, 0, x1, d->wh);
+ XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, x2, 0, d->ww - x2, d->wh);
+ XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, 0, 0, d->ww, y1);
+ XFillRectangle(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, 0, y2, d->ww, d->wh - y2);
+ }
+ if (d->have_mitshm) {
+ XShmPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, d->ximage,
+ 0, 0, d->mx, d->my, d->width, d->height,
+ true);
+ } else {
+ XPutImage(d->dpy, gdk_x11_drawable_get_xid(window),
+ d->gc, d->ximage,
+ 0, 0, d->mx, d->my, d->width, d->height);
+ }
+ }
+
+ return true;
+}
+
+static gboolean key_event(GtkWidget *widget, GdkEventKey *key)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ int scancode;
+
+#if 0
+ fprintf(stderr, "%s %s: keycode: %d state: %d group %d\n",
+ __FUNCTION__, key->type == GDK_KEY_PRESS ? "press" : "release",
+ key->hardware_keycode, key->state, key->group);
+#endif
+
+ if (!d->inputs)
+ return true;
+
+ scancode = vnc_display_keymap_gdk2xtkbd(d->keycode_map, d->keycode_maplen,
+ key->hardware_keycode);
+ switch (key->type) {
+ case GDK_KEY_PRESS:
+ spice_inputs_key_press(d->inputs, scancode);
+ break;
+ case GDK_KEY_RELEASE:
+ spice_inputs_key_release(d->inputs, scancode);
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static gboolean enter_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s\n", __FUNCTION__);
+#endif
+ d->mouse_have_pointer = true;
+ try_keyboard_grab(widget);
+ return true;
+}
+
+static gboolean leave_event(GtkWidget *widget, GdkEventCrossing *crossing G_GNUC_UNUSED)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s\n", __FUNCTION__);
+#endif
+ d->mouse_have_pointer = false;
+ try_keyboard_ungrab(widget);
+ return true;
+}
+
+static gboolean focus_in_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s\n", __FUNCTION__);
+#endif
+ d->keyboard_have_focus = true;
+ try_keyboard_grab(widget);
+ return true;
+}
+
+static gboolean focus_out_event(GtkWidget *widget, GdkEventFocus *focus G_GNUC_UNUSED)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s\n", __FUNCTION__);
+#endif
+ d->keyboard_have_focus = false;
+ try_keyboard_ungrab(widget);
+ return true;
+}
+
+static int button_gdk_to_spice(int gdk)
+{
+ static const int map[] = {
+ [ 1 ] = SPICE_MOUSE_BUTTON_LEFT,
+ [ 2 ] = SPICE_MOUSE_BUTTON_MIDDLE,
+ [ 3 ] = SPICE_MOUSE_BUTTON_RIGHT,
+ [ 4 ] = SPICE_MOUSE_BUTTON_UP,
+ [ 5 ] = SPICE_MOUSE_BUTTON_DOWN,
+ };
+
+ if (gdk < SPICE_N_ELEMENTS(map)) {
+ return map [ gdk ];
+ }
+ return 0;
+}
+
+static int button_mask_gdk_to_spice(int gdk)
+{
+ int spice = 0;
+
+ if (gdk & GDK_BUTTON1_MASK)
+ spice |= SPICE_MOUSE_BUTTON_MASK_LEFT;
+ if (gdk & GDK_BUTTON2_MASK)
+ spice |= SPICE_MOUSE_BUTTON_MASK_MIDDLE;
+ if (gdk & GDK_BUTTON3_MASK)
+ spice |= SPICE_MOUSE_BUTTON_MASK_RIGHT;
+ return spice;
+}
+
+static gboolean motion_event(GtkWidget *widget, GdkEventMotion *motion)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s: +%.0f+%.0f\n", __FUNCTION__, motion->x, motion->y);
+#endif
+
+ if (!d->inputs)
+ return true;
+ switch (d->mouse_mode) {
+ case SPICE_MOUSE_MODE_CLIENT:
+ if (motion->x >= d->mx &&
+ motion->x < d->mx + d->width &&
+ motion->y >= d->my &&
+ motion->y < d->my + d->height) {
+ spice_inputs_position(d->inputs,
+ motion->x - d->mx, motion->y - d->my,
+ d->channel_id,
+ button_mask_gdk_to_spice(motion->state));
+ }
+ break;
+ case SPICE_MOUSE_MODE_SERVER:
+ if (d->mouse_grab_active) {
+ if (d->mouse_last_x != -1 &&
+ d->mouse_last_y != -1) {
+ spice_inputs_motion(d->inputs,
+ motion->x - d->mouse_last_x,
+ motion->y - d->mouse_last_y,
+ button_mask_gdk_to_spice(motion->state));
+ }
+ d->mouse_last_x = motion->x;
+ d->mouse_last_y = motion->y;
+ mouse_check_edges(widget, motion);
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static gboolean button_event(GtkWidget *widget, GdkEventButton *button)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+#if 0
+ fprintf(stderr, "%s %s: button %d\n", __FUNCTION__,
+ button->type == GDK_BUTTON_PRESS ? "press" : "release",
+ button->button);
+#endif
+
+ gtk_widget_grab_focus(widget);
+ try_mouse_grab(widget);
+
+ if (!d->inputs)
+ return true;
+
+ switch (button->type) {
+ case GDK_BUTTON_PRESS:
+ spice_inputs_button_press(d->inputs,
+ button_gdk_to_spice(button->button),
+ button_mask_gdk_to_spice(button->state));
+ break;
+ case GDK_BUTTON_RELEASE:
+ spice_inputs_button_release(d->inputs,
+ button_gdk_to_spice(button->button),
+ button_mask_gdk_to_spice(button->state));
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+static gboolean configure_event(GtkWidget *widget, GdkEventConfigure *conf)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(widget);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ if (conf->width != d->ww || conf->height != d->wh) {
+ d->ww = conf->width;
+ d->wh = conf->height;
+ recalc_geometry(widget);
+ }
+ return true;
+}
+
+static void spice_display_class_init(SpiceDisplayClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS(klass);
+ GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
+
+ gtkwidget_class->expose_event = expose_event;
+ gtkwidget_class->key_press_event = key_event;
+ gtkwidget_class->key_release_event = key_event;
+ gtkwidget_class->enter_notify_event = enter_event;
+ gtkwidget_class->leave_notify_event = leave_event;
+ gtkwidget_class->focus_in_event = focus_in_event;
+ gtkwidget_class->focus_out_event = focus_out_event;
+ gtkwidget_class->motion_notify_event = motion_event;
+ gtkwidget_class->button_press_event = button_event;
+ gtkwidget_class->button_release_event = button_event;
+ gtkwidget_class->configure_event = configure_event;
+#if 0
+ gtkwidget_class->scroll_event = scroll_event;
+#endif
+
+ gtkobject_class->destroy = spice_display_destroy;
+
+ gobject_class->finalize = spice_display_finalize;
+ gobject_class->get_property = spice_display_get_property;
+ gobject_class->set_property = spice_display_set_property;
+
+ g_object_class_install_property
+ (gobject_class, PROP_KEYBOARD_GRAB,
+ g_param_spec_boolean("grab-keyboard",
+ "Grab Keyboard",
+ "Whether we should grab the keyboard.",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_MOUSE_GRAB,
+ g_param_spec_boolean("grab-mouse",
+ "Grab Mouse",
+ "Whether we should grab the mouse.",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property
+ (gobject_class, PROP_RESIZE_GUEST,
+ g_param_spec_boolean("resize-guest",
+ "Resize guest",
+ "Try to adapt guest display on window resize. "
+ "Requires guest cooperation.",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(spice_display));
+}
+
+/* ---------------------------------------------------------------- */
+
+static void mouse_mode(SpiceChannel *channel, enum SpiceMouseMode mode,
+ gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ d->mouse_mode = mode;
+}
+
+static void primary_create(SpiceChannel *channel, gint format,
+ gint width, gint height, gint stride,
+ gint shmid, gpointer imgdata, gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ d->format = format;
+ d->stride = stride;
+ d->shmid = shmid;
+ d->data = imgdata;
+
+ if (d->width != width || d->height != height) {
+ d->width = width;
+ d->height = height;
+ recalc_geometry(GTK_WIDGET(display));
+ if (!d->resize_guest_enable) {
+ gtk_widget_set_size_request(GTK_WIDGET(display), width, height);
+ }
+ }
+}
+
+static void primary_destroy(SpiceChannel *channel, gpointer data)
+{
+ SpiceDisplay *display = SPICE_DISPLAY(data);
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+
+ d->format = 0;
+ d->width = 0;
+ d->height = 0;
+ d->stride = 0;
+ d->shmid = 0;
+ d->data = 0;
+ ximage_destroy(GTK_WIDGET(display));
+}
+
+static void invalidate(SpiceChannel *channel,
+ gint x, gint y, gint w, gint h, gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ gtk_widget_queue_draw_area(GTK_WIDGET(display),
+ x + d->mx, y + d->my, w, h);
+}
+
+static void cursor_set(SpiceCursorChannel *channel,
+ gint width, gint height, gint hot_x, gint hot_y,
+ gpointer rgba, gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ GdkDrawable *window;
+ GdkDisplay *gtkdpy;
+ GdkPixbuf *pixbuf;
+
+ window = gtk_widget_get_window(GTK_WIDGET(display));
+ if (!window)
+ return;
+ gtkdpy = gdk_drawable_get_display(window);
+
+ pixbuf = gdk_pixbuf_new_from_data(rgba,
+ GDK_COLORSPACE_RGB,
+ TRUE, 8,
+ width,
+ height,
+ width * 4,
+ NULL, NULL);
+ d->mouse_cursor = gdk_cursor_new_from_pixbuf(gtkdpy, pixbuf,
+ hot_x, hot_y);
+ g_object_unref(pixbuf);
+ gdk_window_set_cursor(window, d->mouse_cursor);
+}
+
+static void cursor_hide(SpiceCursorChannel *channel, gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ GdkDrawable *window;
+
+ window = gtk_widget_get_window(GTK_WIDGET(display));
+ if (!window)
+ return;
+
+ d->mouse_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
+ gdk_window_set_cursor(window, d->mouse_cursor);
+}
+
+static void cursor_move(SpiceCursorChannel *channel, gint x, gint y, gpointer data)
+{
+ fprintf(stderr, "%s: TODO (+%d+%d)\n", __FUNCTION__, x, y);
+}
+
+static void cursor_reset(SpiceCursorChannel *channel, gpointer data)
+{
+ fprintf(stderr, "%s: TODO\n", __FUNCTION__);
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data)
+{
+ SpiceDisplay *display = data;
+ spice_display *d = SPICE_DISPLAY_GET_PRIVATE(display);
+ int id = spice_channel_id(channel);
+
+ if (SPICE_IS_MAIN_CHANNEL(channel)) {
+ fprintf(stderr, "%s: main channel\n", __FUNCTION__);
+ d->main = channel;
+ g_signal_connect(channel, "spice-main-mouse-mode",
+ G_CALLBACK(mouse_mode), display);
+ mouse_mode(channel, spice_main_get_mouse_mode(channel), display);
+ return;
+ }
+
+ if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+ fprintf(stderr, "%s: display channel\n", __FUNCTION__);
+ if (id != d->channel_id)
+ return;
+ d->display = channel;
+ g_signal_connect(channel, "spice-display-primary-create",
+ G_CALLBACK(primary_create), display);
+ g_signal_connect(channel, "spice-display-primary-destroy",
+ G_CALLBACK(primary_destroy), display);
+ g_signal_connect(channel, "spice-display-invalidate",
+ G_CALLBACK(invalidate), display);
+ spice_channel_connect(channel);
+ return;
+ }
+
+ if (SPICE_IS_CURSOR_CHANNEL(channel)) {
+ fprintf(stderr, "%s: cursor channel\n", __FUNCTION__);
+ if (id != d->channel_id)
+ return;
+ d->cursor = SPICE_CURSOR_CHANNEL(channel);
+ g_signal_connect(channel, "spice-cursor-set",
+ G_CALLBACK(cursor_set), display);
+ g_signal_connect(channel, "spice-cursor-move",
+ G_CALLBACK(cursor_move), display);
+ g_signal_connect(channel, "spice-cursor-hide",
+ G_CALLBACK(cursor_hide), display);
+ g_signal_connect(channel, "spice-cursor-reset",
+ G_CALLBACK(cursor_reset), display);
+ spice_channel_connect(channel);
+ return;
+ }
+
+ if (SPICE_IS_INPUTS_CHANNEL(channel)) {
+ fprintf(stderr, "%s: inputs channel\n", __FUNCTION__);
+ d->inputs = SPICE_INPUTS_CHANNEL(channel);
+ spice_channel_connect(channel);
+ return;
+ }
+
+ fprintf(stderr, "%s: unknown channel object\n", __FUNCTION__);
+ return;
+}
+
+GtkWidget *spice_display_new(SpiceSession *session, int id)
+{
+ SpiceDisplay *display;
+ spice_display *d;
+ SpiceChannel *channels[16];
+ int i, n;
+
+ display = g_object_new(SPICE_TYPE_DISPLAY, NULL);
+ d = SPICE_DISPLAY_GET_PRIVATE(display);
+ d->session = session;
+ d->channel_id = id;
+
+ g_signal_connect(session, "spice-session-channel-new",
+ G_CALLBACK(channel_new), display);
+ n = spice_session_get_channels(session, channels, SPICE_N_ELEMENTS(channels));
+ for (i = 0; i < n; i++) {
+ channel_new(session, channels[i], (gpointer*)display);
+ }
+
+ return GTK_WIDGET(display);
+}
+
+void spice_display_mouse_ungrab(GtkWidget *widget)
+{
+ try_mouse_ungrab(widget);
+}
diff --git a/gtk/spice-widget.h b/gtk/spice-widget.h
new file mode 100644
index 0000000..a54e236
--- /dev/null
+++ b/gtk/spice-widget.h
@@ -0,0 +1,39 @@
+#ifndef __SPICE_CLIENT_WIDGET_H__
+#define __SPICE_CLIENT_WIDGET_H__
+
+#include "spice-client.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SPICE_TYPE_DISPLAY (spice_display_get_type())
+#define SPICE_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SPICE_TYPE_DISPLAY, SpiceDisplay))
+#define SPICE_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
+#define SPICE_IS_DISPLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SPICE_TYPE_DISPLAY))
+#define SPICE_IS_DISPLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SPICE_TYPE_DISPLAY))
+#define SPICE_DISPLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SPICE_TYPE_DISPLAY, SpiceDisplayClass))
+
+
+typedef struct _SpiceDisplay SpiceDisplay;
+typedef struct _SpiceDisplayClass SpiceDisplayClass;
+typedef struct spice_display spice_display;
+
+struct _SpiceDisplay {
+ GtkDrawingArea parent;
+ spice_display *priv;
+ /* Do not add fields to this struct */
+};
+
+struct _SpiceDisplayClass {
+ GtkDrawingAreaClass parent_class;
+ /* Do not add fields to this struct */
+};
+
+GType spice_display_get_type(void);
+G_END_DECLS
+
+GtkWidget* spice_display_new(SpiceSession *session, int id);
+void spice_display_mouse_ungrab(GtkWidget *widget);
+
+#endif /* __SPICE_CLIENT_WIDGET_H__ */
diff --git a/gtk/spicy.c b/gtk/spicy.c
new file mode 100644
index 0000000..21710be
--- /dev/null
+++ b/gtk/spicy.c
@@ -0,0 +1,498 @@
+#include "spice-widget.h"
+#include "spice-pulse.h"
+#include "spice-common.h"
+
+typedef struct spice_window spice_window;
+struct spice_window {
+ int id;
+ GtkWidget *toplevel, *spice;
+ GtkWidget *fstatus, *status;
+ GtkWidget *menubar, *toolbar;
+ GtkActionGroup *ag;
+ GtkAccelGroup *accel;
+ GtkUIManager *ui;
+ bool fullscreen;
+};
+
+/* config */
+static char *host = "localhost";
+static char *port = "5920";
+static char *tls_port = "5921";
+static char *password;
+static char *ca_file;
+static bool fullscreen = false;
+
+/* state */
+static GMainLoop *mainloop;
+static SpiceSession *session;
+static spice_window *wins[4];
+static SpicePulse *pulse;
+
+/* ------------------------------------------------------------------ */
+
+static int ask_user(GtkWidget *parent, char *title, char *message,
+ char *dest, int dlen, int hide)
+{
+ GtkWidget *dialog, *area, *label, *entry;
+ const char *txt;
+ int retval;
+
+ /* Create the widgets */
+ dialog = gtk_dialog_new_with_buttons(title,
+ parent ? GTK_WINDOW(parent) : NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_ACCEPT,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_REJECT,
+ NULL);
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
+ area = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+
+ label = gtk_label_new(message);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ gtk_container_add(GTK_CONTAINER(area), label);
+
+ entry = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(entry), dest);
+ gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+ if (hide)
+ gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+ gtk_container_add(GTK_CONTAINER(area), entry);
+
+ gtk_box_set_spacing(GTK_BOX(area), 10);
+ gtk_container_set_border_width(GTK_CONTAINER(area), 10);
+
+ /* show and wait for response */
+ gtk_widget_show_all(dialog);
+ switch (gtk_dialog_run(GTK_DIALOG(dialog))) {
+ case GTK_RESPONSE_ACCEPT:
+ txt = gtk_entry_get_text(GTK_ENTRY(entry));
+ snprintf(dest, dlen, "%s", txt);
+ retval = 0;
+ break;
+ default:
+ retval = -1;
+ break;
+ }
+ gtk_widget_destroy(dialog);
+ return retval;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void menu_cb_quit(GtkAction *action, void *data)
+{
+ struct spice_window *win = data;
+
+ gtk_widget_destroy(win->toplevel);
+}
+
+static void menu_cb_fullscreen(GtkAction *action, void *data)
+{
+ struct spice_window *win = data;
+
+ if (win->fullscreen) {
+ gtk_window_unfullscreen(GTK_WINDOW(win->toplevel));
+ } else {
+ gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
+ }
+}
+
+static void menu_cb_ungrab(GtkAction *action, void *data)
+{
+ struct spice_window *win = data;
+
+ spice_display_mouse_ungrab(win->spice);
+}
+
+static void menu_cb_bool_prop(GtkToggleAction *action, gpointer data)
+{
+ struct spice_window *win = data;
+ gboolean state = gtk_toggle_action_get_active(action);
+
+ fprintf(stderr, "%s: %s = %s\n", __FUNCTION__,
+ gtk_action_get_name(GTK_ACTION(action)), state ? "yes" : "no");
+ g_object_set(G_OBJECT(win->spice),
+ gtk_action_get_name(GTK_ACTION(action)), state,
+ NULL);
+}
+
+static void menu_cb_about(GtkAction *action, void *data)
+{
+ static char *comments = "gtk client app for the\n"
+ "spice remote desktop protocol";
+ static char *copyright = "(c) 2010 Red Hat";
+ static char *website = "http://www.spice-space.org";
+ static char *authors[] = { "Gerd Hoffmann <kraxel@redhat.com>", NULL };
+ struct spice_window *win = data;
+
+ gtk_show_about_dialog(GTK_WINDOW(win->toplevel),
+ "authors", authors,
+ "comments", comments,
+ "copyright", copyright,
+ "logo-icon-name", GTK_STOCK_ABOUT,
+ "website", website,
+// "version", VERSION,
+// "license", "GPLv2+",
+ NULL);
+}
+
+static void destroy_cb(GtkWidget *widget, gpointer data)
+{
+ struct spice_window *win = data;
+
+ if (win->id == 0) {
+ g_main_loop_quit(mainloop);
+ }
+}
+
+static gboolean window_state_cb(GtkWidget *widget, GdkEventWindowState *event,
+ gpointer data)
+{
+ struct spice_window *win = data;
+
+ if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ win->fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
+ if (win->fullscreen) {
+ gtk_widget_hide(win->menubar);
+ gtk_widget_hide(win->toolbar);
+ gtk_widget_hide(win->fstatus);
+ gtk_widget_grab_focus(win->spice);
+ } else {
+ gtk_widget_show(win->menubar);
+ gtk_widget_show(win->toolbar);
+ gtk_widget_show(win->fstatus);
+ }
+ }
+ return TRUE;
+}
+
+/* ------------------------------------------------------------------ */
+
+static const GtkActionEntry entries[] = {
+ {
+ .name = "FileMenu",
+ .label = "_File",
+ },{
+ .name = "ViewMenu",
+ .label = "_View",
+ },{
+ .name = "InputMenu",
+ .label = "_Input",
+ },{
+ .name = "OptionMenu",
+ .label = "_Options",
+ },{
+ .name = "HelpMenu",
+ .label = "_Help",
+ },{
+
+ /* File menu */
+ .name = "Quit",
+ .stock_id = GTK_STOCK_QUIT,
+ .label = "_Quit",
+ .callback = G_CALLBACK(menu_cb_quit),
+ .accelerator = "", /* none (disable default "<control>Q") */
+ },{
+
+ /* View menu */
+ .name = "Fullscreen",
+ .stock_id = GTK_STOCK_FULLSCREEN,
+ .label = "_Fullscreen",
+ .callback = G_CALLBACK(menu_cb_fullscreen),
+ .accelerator = "<shift>F11",
+ },{
+
+ /* Input menu */
+ .name = "UngrabMouse",
+ .label = "_Ungrab mouse",
+ .callback = G_CALLBACK(menu_cb_ungrab),
+ .accelerator = "<shift>F12",
+ },{
+
+ /* Help menu */
+ .name = "About",
+ .stock_id = GTK_STOCK_ABOUT,
+ .label = "_About ...",
+ .callback = G_CALLBACK(menu_cb_about),
+ }
+};
+
+static const GtkToggleActionEntry tentries[] = {
+ {
+ .name = "grab-keyboard",
+ .label = "Grab keyboard",
+ .callback = G_CALLBACK(menu_cb_bool_prop),
+ },{
+ .name = "grab-mouse",
+ .label = "Grab mouse",
+ .callback = G_CALLBACK(menu_cb_bool_prop),
+ },{
+ .name = "resize-guest",
+ .label = "Resize guest",
+ .callback = G_CALLBACK(menu_cb_bool_prop),
+ }
+};
+
+static char ui_xml[] =
+"<ui>\n"
+" <menubar action='MainMenu'>\n"
+" <menu action='FileMenu'>\n"
+" <menuitem action='Quit'/>\n"
+" </menu>\n"
+" <menu action='ViewMenu'>\n"
+" <menuitem action='Fullscreen'/>\n"
+" </menu>\n"
+" <menu action='InputMenu'>\n"
+" <menuitem action='UngrabMouse'/>\n"
+" </menu>\n"
+" <menu action='OptionMenu'>\n"
+" <menuitem action='grab-keyboard'/>\n"
+" <menuitem action='grab-mouse'/>\n"
+" <menuitem action='resize-guest'/>\n"
+" </menu>\n"
+" <menu action='HelpMenu'>\n"
+" <menuitem action='About'/>\n"
+" </menu>\n"
+" </menubar>\n"
+" <toolbar action='ToolBar'>\n"
+" <toolitem action='Quit'/>\n"
+" <separator/>\n"
+" <toolitem action='Fullscreen'/>\n"
+" </toolbar>\n"
+"</ui>\n";
+
+static spice_window *create_spice_window(SpiceSession *s, int id)
+{
+ char title[32];
+ struct spice_window *win;
+ GtkWidget *vbox;
+ GError *err = NULL;
+ int i;
+
+ win = malloc(sizeof(*win));
+ if (NULL == win)
+ return NULL;
+ memset(win,0,sizeof(*win));
+ win->id = id;
+
+ /* toplevel */
+ win->toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ snprintf(title, sizeof(title), "spice display %d", id);
+ gtk_window_set_title(GTK_WINDOW(win->toplevel), title);
+ g_signal_connect(G_OBJECT(win->toplevel), "destroy",
+ G_CALLBACK(destroy_cb), win);
+ g_signal_connect(G_OBJECT(win->toplevel), "window-state-event",
+ G_CALLBACK(window_state_cb), win);
+
+ /* menu + toolbar */
+ win->ui = gtk_ui_manager_new();
+ win->ag = gtk_action_group_new("MenuActions");
+ gtk_action_group_add_actions(win->ag, entries, G_N_ELEMENTS(entries), win);
+ gtk_action_group_add_toggle_actions(win->ag, tentries,
+ G_N_ELEMENTS(tentries), win);
+ gtk_ui_manager_insert_action_group(win->ui, win->ag, 0);
+ win->accel = gtk_ui_manager_get_accel_group(win->ui);
+ gtk_window_add_accel_group(GTK_WINDOW(win->toplevel), win->accel);
+
+ err = NULL;
+ if (!gtk_ui_manager_add_ui_from_string(win->ui, ui_xml, -1, &err)) {
+ g_message("building menus failed: %s", err->message);
+ g_error_free(err);
+ exit(1);
+ }
+ win->menubar = gtk_ui_manager_get_widget(win->ui, "/MainMenu");
+ win->toolbar = gtk_ui_manager_get_widget(win->ui, "/ToolBar");
+
+ /* spice display */
+ win->spice = spice_display_new(s, id);
+
+ /* 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);
+
+ /* Make a vbox and put stuff in */
+ vbox = gtk_vbox_new(FALSE, 1);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 0);
+ gtk_container_add(GTK_CONTAINER(win->toplevel), vbox);
+ gtk_box_pack_start(GTK_BOX(vbox), win->menubar, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), win->toolbar, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), win->spice, TRUE, TRUE, 0);
+ win->fstatus = gtk_frame_new(NULL);
+ gtk_box_pack_end(GTK_BOX(vbox), win->fstatus, FALSE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(win->fstatus), win->status);
+
+ /* init toggle actions */
+ for (i = 0; i < G_N_ELEMENTS(tentries); i++) {
+ GtkAction *toggle;
+ gboolean state;
+ toggle = gtk_action_group_get_action(win->ag, tentries[i].name);
+ g_object_get(win->spice, tentries[i].name, &state, NULL);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(toggle), state);
+ }
+
+ /* show window */
+ if (fullscreen)
+ gtk_window_fullscreen(GTK_WINDOW(win->toplevel));
+ gtk_widget_show_all(win->toplevel);
+ return win;
+}
+
+/* ------------------------------------------------------------------ */
+
+static void main_channel_event(SpiceChannel *channel, enum SpiceChannelEvent event,
+ gpointer *data)
+{
+ char message[256];
+ char password[64];
+ int rc;
+
+ switch (event) {
+ case SPICE_CHANNEL_OPENED:
+ /* nothing */
+ break;
+ case SPICE_CHANNEL_CLOSED:
+ fprintf(stderr, "main channel: closed\n");
+ g_main_loop_quit(mainloop);
+ break;
+ case SPICE_CHANNEL_ERROR_AUTH:
+ fprintf(stderr, "main channel: auth failure (wrong password?)\n");
+ snprintf(message, sizeof(message),
+ "Please enter the password for the\n"
+ "spice session to %s", host);
+ strcpy(password, "");
+ rc = ask_user(NULL, "Authentication", message,
+ password, sizeof(password), true);
+ if (rc == 0) {
+ g_object_set(session, "password", password, NULL);
+ spice_session_connect(session);
+ } else {
+ g_main_loop_quit(mainloop);
+ }
+ break;
+ default:
+ /* TODO: more sophisticated error handling */
+ fprintf(stderr, "unknown main channel event: %d\n", event);
+ g_main_loop_quit(mainloop);
+ break;
+ }
+}
+
+static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer *data)
+{
+ int id = spice_channel_id(channel);
+
+ if (SPICE_IS_MAIN_CHANNEL(channel)) {
+ g_signal_connect(channel, "spice-channel-event",
+ G_CALLBACK(main_channel_event), NULL);
+ fprintf(stderr, "new main channel\n");
+ return;
+ }
+
+ if (SPICE_IS_DISPLAY_CHANNEL(channel)) {
+ if (id >= SPICE_N_ELEMENTS(wins))
+ return;
+ if (wins[id] != NULL)
+ return;
+ fprintf(stderr, "new display channel (#%d), creating window\n", id);
+ wins[id] = create_spice_window(s, id);
+ return;
+ }
+
+ if (SPICE_IS_PLAYBACK_CHANNEL(channel)) {
+ if (pulse != NULL)
+ return;
+ pulse = spice_pulse_new(s, mainloop, "spice");
+ return;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+
+static void usage(FILE *fp)
+{
+ fprintf(fp,
+ "usage:\n"
+ " spicy [ options ]\n"
+ "\n"
+ "options:\n"
+ " -h <host> remote host [ %s ]\n"
+ " -p <port> remote port [ %s ]\n"
+ " -s <port> remote tls port [ %s ]\n"
+ " -w <secret> password\n"
+ " -f fullscreen\n"
+ "\n",
+ host, port, tls_port);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ /* parse opts */
+ gtk_init(&argc, &argv);
+ for (;;) {
+ if (-1 == (c = getopt(argc, argv, "h:p:s:w:f")))
+ break;
+ switch (c) {
+ case 'h':
+ host = optarg;
+ break;
+ case 'p':
+ port = optarg;
+ break;
+ case 's':
+ tls_port = optarg;
+ break;
+ case 'w':
+ password = optarg;
+ break;
+ case 'f':
+ fullscreen = true;
+ break;
+#if 0 /* --help */
+ case 'h':
+ usage(stdout);
+ exit(0);
+#endif
+ default:
+ usage(stderr);
+ exit(1);
+ }
+ }
+
+ if (ca_file == NULL) {
+ char *home = getenv("HOME");
+ size_t size = strlen(home) + 32;
+ ca_file = malloc(size);
+ snprintf(ca_file, size, "%s/.spicec/spice_truststore.pem", home);
+ }
+
+ g_type_init();
+ mainloop = g_main_loop_new(NULL, false);
+
+ session = spice_session_new();
+ g_signal_connect(session, "spice-session-channel-new",
+ G_CALLBACK(channel_new), NULL);
+
+ if (host)
+ g_object_set(session, "host", host, NULL);
+ if (port)
+ g_object_set(session, "port", port, NULL);
+ if (tls_port)
+ g_object_set(session, "tls-port", tls_port, NULL);
+ if (password)
+ g_object_set(session, "password", password, NULL);
+ if (ca_file)
+ g_object_set(session, "ca-file", ca_file, NULL);
+
+ if (!spice_session_connect(session)) {
+ fprintf(stderr, "spice_session_connect failed\n");
+ exit(1);
+ }
+
+ g_main_loop_run(mainloop);
+ return 0;
+}
diff --git a/gtk/tcp.c b/gtk/tcp.c
new file mode 100644
index 0000000..722abd8
--- /dev/null
+++ b/gtk/tcp.c
@@ -0,0 +1,173 @@
+/*
+ * TCP helper functions.
+ *
+ * Copyright (C) 2007 Gerd Hoffmann <kraxel@redhat.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#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;
+}
diff --git a/gtk/tcp.h b/gtk/tcp.h
new file mode 100644
index 0000000..f0224c0
--- /dev/null
+++ b/gtk/tcp.h
@@ -0,0 +1,11 @@
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+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);
diff --git a/gtk/vncdisplaykeymap.c b/gtk/vncdisplaykeymap.c
new file mode 100644
index 0000000..0229859
--- /dev/null
+++ b/gtk/vncdisplaykeymap.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2008 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include "vncdisplaykeymap.h"
+
+#define VNC_DEBUG(message) fprintf(stderr, "%s\n", message);
+
+/*
+ * This table is taken from QEMU x_keymap.c, under the terms:
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* keycode translation for sending ISO_Left_Send
+ * to vncserver
+ */
+static struct {
+ GdkKeymapKey *keys;
+ gint n_keys;
+ guint keyval;
+} untranslated_keys[] = {{NULL, 0, GDK_Tab}};
+
+static unsigned int ref_count_for_untranslated_keys = 0;
+
+#if defined(GDK_WINDOWING_X11)
+#include <gdk/gdkx.h>
+#include <X11/XKBlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+/* Xorg Linux + kbd (offset + mangled XT keycodes) */
+#include "vncdisplaykeymap_xorgkbd2xtkbd.c"
+/* Xorg Linux + evdev (offset evdev keycodes) */
+#include "vncdisplaykeymap_xorgevdev2xtkbd.c"
+/* Xorg OS-X aka XQuartz (offset OS-X keycodes) */
+#include "vncdisplaykeymap_xorgxquartz2xtkbd.c"
+/* Xorg Cygwin aka XWin (offset + mangled XT keycodes) */
+#include "vncdisplaykeymap_xorgxwin2xtkbd.c"
+
+#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+
+static gboolean check_for_xwin(void)
+{
+ char *vendor = ServerVendor(GDK_DISPLAY());
+
+ if (strstr(vendor, "Cygwin/X"))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean check_for_xquartz(void)
+{
+ int nextensions;
+ int i;
+ gboolean match = FALSE;
+ char **extensions = XListExtensions(GDK_DISPLAY(), &nextensions);
+ for (i = 0 ; extensions != NULL && i < nextensions ; i++) {
+ if (strcmp(extensions[i], "Apple-WM") == 0 ||
+ strcmp(extensions[i], "Apple-DRI") == 0)
+ match = TRUE;
+ }
+ if (extensions)
+ XFreeExtensionList(extensions);
+
+ return match;
+}
+
+const guint16 const *vnc_display_keymap_gdk2xtkbd_table(size_t *maplen)
+{
+ XkbDescPtr desc;
+ const gchar *keycodes = NULL;
+
+ /* There is no easy way to determine what X11 server
+ * and platform & keyboard driver is in use. Thus we
+ * do best guess heuristics.
+ *
+ * This will need more work for people with other
+ * X servers..... patches welcomed.
+ */
+
+ desc = XkbGetKeyboard(GDK_DISPLAY(), XkbGBN_AllComponentsMask,
+ XkbUseCoreKbd);
+ if (desc) {
+ if (desc->names) {
+ keycodes = gdk_x11_get_xatom_name(desc->names->keycodes);
+ if (!keycodes)
+ g_warning("could not lookup keycode name");
+ }
+ XkbFreeClientMap(desc, XkbGBN_AllComponentsMask, True);
+ }
+
+ if (check_for_xwin()) {
+ VNC_DEBUG("Using xwin keycode mapping");
+ *maplen = G_N_ELEMENTS(keymap_xorgxwin2xtkbd);
+ return keymap_xorgxwin2xtkbd;
+ } else if (check_for_xquartz()) {
+ VNC_DEBUG("Using xquartz keycode mapping");
+ *maplen = G_N_ELEMENTS(keymap_xorgxquartz2xtkbd);
+ return keymap_xorgxquartz2xtkbd;
+ } else if (keycodes && STRPREFIX(keycodes, "evdev_")) {
+ VNC_DEBUG("Using evdev keycode mapping");
+ *maplen = G_N_ELEMENTS(keymap_xorgevdev2xtkbd);
+ return keymap_xorgevdev2xtkbd;
+ } else if (keycodes && STRPREFIX(keycodes, "xfree86_")) {
+ VNC_DEBUG("Using xfree86 keycode mapping");
+ *maplen = G_N_ELEMENTS(keymap_xorgkbd2xtkbd);
+ return keymap_xorgkbd2xtkbd;
+ } else {
+ g_warning("Unknown keycode mapping '%s'.\n"
+ "Please report to gtk-vnc-list@gnome.org\n"
+ "including the following information:\n"
+ "\n"
+ " - Operating system\n"
+ " - GTK build\n"
+ " - X11 Server\n"
+ " - xprop -root\n"
+ " - xdpyinfo\n",
+ keycodes);
+ return NULL;
+ }
+}
+
+#elif defined(GDK_WINDOWING_WIN32)
+/* Win32 native virtual keycodes */
+#include "vncdisplaykeymap_win322xtkbd.c"
+
+const guint16 const *vnc_display_keymap_gdk2xtkbd_table(size_t *maplen)
+{
+ VNC_DEBUG("Using Win32 virtual keycode mapping");
+ *maplen = sizeof(keymap_win322xtkbd);
+ return keymap_win322xtkbd;
+}
+
+#elif defined(GDK_WINDOWING_QUARTZ)
+/* OS-X native keycodes */
+#include "vncdisplaykeymap_osx2xtkbd.c"
+
+const guint16 const *vnc_display_keymap_gdk2xtkbd_table(size_t *maplen)
+{
+ VNC_DEBUG("Using OS-X virtual keycode mapping");
+ *maplen = sizeof(keymap_osx2xtkbd);
+ return keymap_osx2xtkbd;
+}
+
+
+#else
+
+const guint16 const *vnc_display_keymap_gdk2xtkbd_table(size_t *maplen)
+{
+ g_warning("Unsupported GDK Windowing platform.\n"
+ "Please report to gtk-vnc-list@gnome.org\n"
+ "including the following information:\n"
+ "\n"
+ " - Operating system\n"
+ " - GTK Windowing system build\n");
+ return NULL;
+}
+#endif
+
+guint16 vnc_display_keymap_gdk2xtkbd(const guint16 const *keycode_map,
+ size_t keycode_maplen,
+ guint16 keycode)
+{
+ if (!keycode_map)
+ return 0;
+ if (keycode >= keycode_maplen)
+ return 0;
+ return keycode_map[keycode];
+}
+
+/* Set the keymap entries */
+void vnc_display_keyval_set_entries(void)
+{
+ size_t i;
+ if (ref_count_for_untranslated_keys == 0)
+ for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
+ gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
+ untranslated_keys[i].keyval,
+ &untranslated_keys[i].keys,
+ &untranslated_keys[i].n_keys);
+ ref_count_for_untranslated_keys++;
+}
+
+/* Free the keymap entries */
+void vnc_display_keyval_free_entries(void)
+{
+ size_t i;
+
+ if (ref_count_for_untranslated_keys == 0)
+ return;
+
+ ref_count_for_untranslated_keys--;
+ if (ref_count_for_untranslated_keys == 0)
+ for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++)
+ g_free(untranslated_keys[i].keys);
+
+}
+
+/* Get the keyval from the keycode without the level. */
+guint vnc_display_keyval_from_keycode(guint keycode, guint keyval)
+{
+ size_t i;
+ for (i = 0; i < sizeof(untranslated_keys) / sizeof(untranslated_keys[0]); i++) {
+ if (keycode == untranslated_keys[i].keys[0].keycode) {
+ return untranslated_keys[i].keyval;
+ }
+ }
+
+ return keyval;
+}
+/*
+ * Local variables:
+ * c-indent-level: 8
+ * c-basic-offset: 8
+ * tab-width: 8
+ * End:
+ */
diff --git a/gtk/vncdisplaykeymap.h b/gtk/vncdisplaykeymap.h
new file mode 100644
index 0000000..cee1b5a
--- /dev/null
+++ b/gtk/vncdisplaykeymap.h
@@ -0,0 +1,35 @@
+/*
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef VNC_DISPLAY_KEYMAP_H
+#define VNC_DISPLAY_KEYMAP_H
+
+#include <glib.h>
+
+const guint16 const *vnc_display_keymap_gdk2xtkbd_table(size_t *maplen);
+guint16 vnc_display_keymap_gdk2xtkbd(const guint16 *keycode_map,
+ size_t keycode_maplen,
+ guint16 keycode);
+void vnc_display_keyval_set_entries(void);
+void vnc_display_keyval_free_entries(void);
+guint vnc_display_keyval_from_keycode(guint keycode, guint keyval);
+
+#endif /* VNC_DISPLAY_KEYMAP_H */