diff options
author | Gerd Hoffmann <kraxel@redhat.com> | 2010-08-25 18:18:34 +0200 |
---|---|---|
committer | Marc-André Lureau <marcandre.lureau@gmail.com> | 2010-11-23 17:09:45 +0100 |
commit | 75c1a7dc1a7b1ec16515912f6e0618b7e820249f (patch) | |
tree | eb8863a70a2f8e1d0fdf896fbbd6820223a6de25 | |
parent | d960229a091a7bd5b3ce4a29ae5f5648978af51c (diff) |
Add glib objects + gtk widgets for spice.
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 */ |