diff --git a/Makefile.am b/Makefile.am index 6ce3814a..9c62b6f6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,6 +37,7 @@ DIST_SUBDIRS = \ src/pulse \ src/protocols/kubernetes \ src/protocols/rdp \ + src/protocols/spice \ src/protocols/ssh \ src/protocols/telnet \ src/protocols/vnc @@ -65,6 +66,10 @@ if ENABLE_RDP SUBDIRS += src/protocols/rdp endif +if ENABLE_SPICE +SUBDIRS += src/protocols/spice +endif + if ENABLE_SSH SUBDIRS += src/protocols/ssh endif diff --git a/configure.ac b/configure.ac index 624aae13..0d89a22c 100644 --- a/configure.ac +++ b/configure.ac @@ -634,6 +634,27 @@ then fi +# +# spice-glib +# + +have_spice_glib=disabled +AC_ARG_WITH([spice], + [AS_HELP_STRING([--with-spice], + [support SPICE @<:@default=check@:>@])], + [], + [with_spice=check]) + +if test "x$with_spice" != "xno" +then + have_spice_glib=yes + PKG_CHECK_MODULES([GLIB2], [glib-2.0],, [have_spice_glib=no]) + PKG_CHECK_MODULES([SPICE], [spice-client-glib-2.0],, [have_spice_glib=no]) +fi + +AM_CONDITIONAL([ENABLE_SPICE], [test "x${have_spice_glib}" = "xyes"]) +AC_SUBST(SPICE_LIBS) + # # FreeRDP 2 (libfreerdp2, libfreerdp-client2, and libwinpr2) @@ -1188,6 +1209,7 @@ AC_CONFIG_FILES([Makefile src/protocols/kubernetes/tests/Makefile src/protocols/rdp/Makefile src/protocols/rdp/tests/Makefile + src/protocols/spice/Makefile src/protocols/ssh/Makefile src/protocols/telnet/Makefile src/protocols/vnc/Makefile]) @@ -1199,6 +1221,7 @@ AC_OUTPUT AM_COND_IF([ENABLE_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no]) AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=no]) +AM_COND_IF([ENABLE_SPICE], [build_spice=yes], [build_spice=no]) AM_COND_IF([ENABLE_SSH], [build_ssh=yes], [build_ssh=no]) AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no]) AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no]) @@ -1260,6 +1283,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION Kubernetes .... ${build_kubernetes} RDP ........... ${build_rdp} + SPICE ......... ${build_spice} SSH ........... ${build_ssh} Telnet ........ ${build_telnet} VNC ........... ${build_vnc} diff --git a/src/protocols/spice/Makefile.am b/src/protocols/spice/Makefile.am new file mode 100644 index 00000000..0ffa94b9 --- /dev/null +++ b/src/protocols/spice/Makefile.am @@ -0,0 +1,133 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# NOTE: Parts of this file (Makefile.am) are automatically transcluded verbatim +# into Makefile.in. Though the build system (GNU Autotools) automatically adds +# its own license boilerplate to the generated Makefile.in, that boilerplate +# does not apply to the transcluded portions of Makefile.am which are licensed +# to you by the ASF under the Apache License, Version 2.0, as described above. +# + +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libguac-client-spice.la + +nodist_libguac_client_spice_la_SOURCES = \ + _generated_keymaps.c + +libguac_client_spice_la_SOURCES = \ + argv.c \ + auth.c \ + channels/audio.c \ + channels/clipboard.c \ + channels/cursor.c \ + channels/display.c \ + channels/file.c \ + client.c \ + decompose.c \ + input.c \ + keyboard.c \ + keymap.c \ + log.c \ + settings.c \ + spice.c \ + user.c + +noinst_HEADERS = \ + argv.h \ + auth.h \ + channels/audio.h \ + channels/clipboard.h \ + channels/cursor.h \ + channels/display.h \ + channels/file.h \ + client.h \ + decompose.h \ + input.h \ + keyboard.h \ + keymap.h \ + log.h \ + settings.h \ + spice.h \ + user.h + +libguac_client_spice_la_CFLAGS = \ + -Werror -Wall -pedantic -Iinclude \ + @COMMON_INCLUDE@ \ + @COMMON_SSH_INCLUDE@ \ + @LIBGUAC_INCLUDE@ \ + @GLIB2_CFLAGS@ \ + @SPICE_CFLAGS@ + +libguac_client_spice_la_LDFLAGS = \ + -version-info 0:0:0 \ + @CAIRO_LIBS@ \ + @GLIB2_LIBS@ \ + @SPICE_LIBS@ + +libguac_client_spice_la_LIBADD = \ + @COMMON_LTLIB@ \ + @LIBGUAC_LTLIB@ + +# Optional SFTP support +if ENABLE_COMMON_SSH +libguac_client_spice_la_SOURCES += sftp.c +noinst_HEADERS += sftp.h +libguac_client_spice_la_LIBADD += @COMMON_SSH_LTLIB@ +endif + +# +# Autogenerated keymaps and channel wrapper functions +# + +CLEANFILES = \ + _generated_keymaps.c + +BUILT_SOURCES = \ + _generated_keymaps.c + +spice_keymaps = \ + $(srcdir)/keymaps/base.keymap \ + $(srcdir)/keymaps/failsafe.keymap \ + $(srcdir)/keymaps/de_de_qwertz.keymap \ + $(srcdir)/keymaps/de_ch_qwertz.keymap \ + $(srcdir)/keymaps/en_gb_qwerty.keymap \ + $(srcdir)/keymaps/en_us_qwerty.keymap \ + $(srcdir)/keymaps/es_es_qwerty.keymap \ + $(srcdir)/keymaps/es_latam_qwerty.keymap \ + $(srcdir)/keymaps/fr_be_azerty.keymap \ + $(srcdir)/keymaps/fr_ca_qwerty.keymap \ + $(srcdir)/keymaps/fr_ch_qwertz.keymap \ + $(srcdir)/keymaps/fr_fr_azerty.keymap \ + $(srcdir)/keymaps/hu_hu_qwertz.keymap \ + $(srcdir)/keymaps/it_it_qwerty.keymap \ + $(srcdir)/keymaps/ja_jp_qwerty.keymap \ + $(srcdir)/keymaps/no_no_qwerty.keymap \ + $(srcdir)/keymaps/pl_pl_qwerty.keymap \ + $(srcdir)/keymaps/pt_br_qwerty.keymap \ + $(srcdir)/keymaps/sv_se_qwerty.keymap \ + $(srcdir)/keymaps/da_dk_qwerty.keymap \ + $(srcdir)/keymaps/tr_tr_qwerty.keymap + +_generated_keymaps.c: $(spice_keymaps) $(srcdir)/keymaps/generate.pl + $(AM_V_GEN) $(srcdir)/keymaps/generate.pl $(spice_keymaps) + +EXTRA_DIST = \ + $(spice_keymaps) \ + keymaps/generate.pl \ No newline at end of file diff --git a/src/protocols/spice/argv.c b/src/protocols/spice/argv.c new file mode 100644 index 00000000..ee326a8a --- /dev/null +++ b/src/protocols/spice/argv.c @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "argv.h" +#include "spice.h" + +#include +#include +#include + +#include +#include +#include + +int guac_spice_argv_callback(guac_user* user, const char* mimetype, + const char* name, const char* value, void* data) { + + guac_client* client = user->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_spice_settings* settings = spice_client->settings; + + /* Update username */ + if (strcmp(name, GUAC_SPICE_ARGV_USERNAME) == 0) { + free(settings->username); + settings->username = strdup(value); + } + + /* Update password */ + else if (strcmp(name, GUAC_SPICE_ARGV_PASSWORD) == 0) { + free(settings->password); + settings->password = strdup(value); + } + + return 0; + +} \ No newline at end of file diff --git a/src/protocols/spice/argv.h b/src/protocols/spice/argv.h new file mode 100644 index 00000000..3a0c3f16 --- /dev/null +++ b/src/protocols/spice/argv.h @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_ARGV_H +#define GUAC_SPICE_ARGV_H + +#include "config.h" + +#include +#include + +/** + * Handles a received argument value from a Guacamole "argv" instruction, + * updating the given connection parameter. + */ +guac_argv_callback guac_spice_argv_callback; + +/** + * The name of the parameter Guacamole will use to specify/update the username + * for the SPICE connection. + */ +#define GUAC_SPICE_ARGV_USERNAME "username" + +/** + * The name of the parameter Guacamole will use to specify/update the password + * for the SPICE connection. + */ +#define GUAC_SPICE_ARGV_PASSWORD "password" + +#endif /* GUAC_SPICE_ARGV_H */ + diff --git a/src/protocols/spice/auth.c b/src/protocols/spice/auth.c new file mode 100644 index 00000000..58c851d3 --- /dev/null +++ b/src/protocols/spice/auth.c @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "argv.h" +#include "auth.h" +#include "spice.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +gboolean guac_spice_get_credentials(guac_client* client) { + + guac_spice_client* spice_client = ((guac_spice_client*) client->data); + guac_spice_settings* settings = spice_client->settings; + + char* params[3] = {NULL}; + int i = 0; + + /* Check if username is not provided. */ + if (settings->username == NULL) { + guac_argv_register(GUAC_SPICE_ARGV_USERNAME, guac_spice_argv_callback, NULL, 0); + params[i] = GUAC_SPICE_ARGV_USERNAME; + i++; + } + + /* Check if password is not provided. */ + if (settings->password == NULL) { + guac_argv_register(GUAC_SPICE_ARGV_PASSWORD, guac_spice_argv_callback, NULL, 0); + params[i] = GUAC_SPICE_ARGV_PASSWORD; + i++; + } + + params[i] = NULL; + + /* If we have empty parameters, request them and await response. */ + if (i > 0) { + guac_client_owner_send_required(client, (const char**) params); + guac_argv_await((const char**) params); + return true; + } + + guac_client_log(client, GUAC_LOG_DEBUG, + "Unable to retrieve any credentials from the user."); + return false; + +} diff --git a/src/protocols/spice/auth.h b/src/protocols/spice/auth.h new file mode 100644 index 00000000..3d18ec8f --- /dev/null +++ b/src/protocols/spice/auth.h @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_AUTH_H +#define GUAC_SPICE_AUTH_H + +#include "config.h" + +#include + +#include + +/** + * Handler invoked when an authentication error is received from the SPICE + * server, which retrieves the credentials from the Guacamole Client accessing + * the connection, if those credentials have not been explicitly set in the + * configuration. Returns TRUE if credentials are successfully retrieved, or + * FALSE otherwise. + * + * @param client + * The guac_client that is attempting to connect to the SPICE server and + * that will be asked for the credentials. + * + * @return + * TRUE if the credentials are retrieved from the users; otherwise FALSE. + */ +gboolean guac_spice_get_credentials(guac_client* client); + +#endif /* GUAC_SPICE_AUTH_H */ + diff --git a/src/protocols/spice/channels/audio.c b/src/protocols/spice/channels/audio.c new file mode 100644 index 00000000..425275f2 --- /dev/null +++ b/src/protocols/spice/channels/audio.c @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "audio.h" + +#include + +void guac_spice_client_audio_playback_data_handler( + SpicePlaybackChannel* channel, gpointer data, gint size, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio playback data handler."); + +} + +void guac_spice_client_audio_playback_delay_handler( + SpicePlaybackChannel* channel, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio playback delay handler."); + +} + +void guac_spice_client_audio_playback_start_handler( + SpicePlaybackChannel* channel, gint format, gint channels, gint rate, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio playback start handler."); + +} + +void guac_spice_client_audio_playback_stop_handler( + SpicePlaybackChannel* channel, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio playback stop handler."); + +} + +void guac_spice_client_audio_record_start_handler(SpiceRecordChannel* channel, + gint format, gint channels, gint rate, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record start handler."); + +} + +void guac_spice_client_audio_record_stop_handler(SpiceRecordChannel* channel, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling audio record stop handler."); + +} \ No newline at end of file diff --git a/src/protocols/spice/channels/audio.h b/src/protocols/spice/channels/audio.h new file mode 100644 index 00000000..1097bd9f --- /dev/null +++ b/src/protocols/spice/channels/audio.h @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_AUDIO_H +#define GUAC_SPICE_AUDIO_H + +#include "config.h" + +#include + +#include + +/** + * A callback function invoked with the SPICE client receives audio playback + * data from the SPICE server. + * + * @param channel + * The SpicePlaybackChannel on which the data was sent. + * + * @param data + * The audio data. + * + * @param size + * The number of bytes of data sent. + * + * @param client + * The guac_client associated with this event. + */ +void guac_spice_client_audio_playback_data_handler( + SpicePlaybackChannel* channel, gpointer data, gint size, + guac_client* client); + +/** + * A callback function invoked when the SPICE server requests the audio playback + * delay value from the client. + * + * @param channel + * The SpicePlaybackChannel channel on which this event occurred. + * + * @param client + * The guac_client associated with this event. + */ +void guac_spice_client_audio_playback_delay_handler( + SpicePlaybackChannel* channel, guac_client* client); + +/** + * A callback function invoked by the client when the SPICE server sends a + * signal indicating that it is starting an audio transmission. + * + * @param channel + * The SpicePlaybackChannel on which this event occurred. + * + * @param format + * The SPICE_AUDIO_FMT value of the format of the audio data. + * + * @param channels + * The number of channels of audio data present. + * + * @param rate + * The audio sampling rate at which the data will be sent. + * + * @param client + * The guac_client associated with this event. + */ +void guac_spice_client_audio_playback_start_handler( + SpicePlaybackChannel* channel, gint format, gint channels, gint rate, + guac_client* client); + +/** + * The callback function invoked by the client when the SPICE server sends + * an event indicating that audio playback will cease. + * + * @param channel + * The SpicePlaybackChannel on which the recording is being halted. + * + * @param client + * The guac_client associated with this event. + */ +void guac_spice_client_audio_playback_stop_handler( + SpicePlaybackChannel* channel, guac_client* client); + +/** + * The callback function invoked by the client when the SPICE server requests + * that the client begin recording audio data to send to the server. + * + * @param channel + * The SpiceRecordChannel on which the record event is being requested. + * + * @param format + * The SPICE_AUDIO_FMT value representing the required recording format. + * + * @param channels + * The number of audio channels that should be recorded. + * + * @param rate + * The rate at which the recording should take place. + * + * @param client + * The guac_client associated with this event. + */ +void guac_spice_client_audio_record_start_handler(SpiceRecordChannel* channel, + gint format, gint channels, gint rate, guac_client* client); + +/** + * The callback function invoked by the client when the SPICE server sends + * an event requesting that recording be stopped. + * + * @param channel + * The channel associated with the event. + * + * @param client + * The guac_client associated with the event. + */ +void guac_spice_client_audio_record_stop_handler(SpiceRecordChannel* channel, + guac_client* client); +#endif /* GUAC_SPICE_AUDIO_H */ + diff --git a/src/protocols/spice/channels/clipboard.c b/src/protocols/spice/channels/clipboard.c new file mode 100644 index 00000000..00ecf6f2 --- /dev/null +++ b/src/protocols/spice/channels/clipboard.c @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" +#include "client.h" +#include "clipboard.h" +#include "common/clipboard.h" +#include "common/iconv.h" +#include "spice.h" +#include "spice-constants.h" +#include "user.h" + +#include +#include +#include + +#include + +int guac_spice_set_clipboard_encoding(guac_client* client, + const char* name) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting clipboard encoding."); + + return 0; + +} + +int guac_spice_clipboard_handler(guac_user* user, guac_stream* stream, + char* mimetype) { + + guac_client_log(user->client, GUAC_LOG_DEBUG, "Calling SPICE clipboard handler."); + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + guac_common_clipboard_reset(spice_client->clipboard, mimetype); + + /* Set handlers for clipboard stream */ + stream->blob_handler = guac_spice_clipboard_blob_handler; + stream->end_handler = guac_spice_clipboard_end_handler; + + return 0; +} + +int guac_spice_clipboard_blob_handler(guac_user* user, guac_stream* stream, + void* data, int length) { + + guac_client_log(user->client, GUAC_LOG_DEBUG, "Calling SPICE clipboard BLOB handler."); + + /* Append new data */ + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + guac_common_clipboard_append(spice_client->clipboard, (char*) data, length); + + return 0; +} + +int guac_spice_clipboard_end_handler(guac_user* user, guac_stream* stream) { + + guac_client_log(user->client, GUAC_LOG_DEBUG, "Calling SPICE clipboard end handler."); + + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + const char* input = spice_client->clipboard->buffer; + + /* Send via VNC only if finished connecting */ + if (spice_client->main_channel != NULL) + spice_main_channel_clipboard_selection_notify(spice_client->main_channel, + VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, + VD_AGENT_CLIPBOARD_UTF8_TEXT, + (const unsigned char*) input, + spice_client->clipboard->length); + + return 0; +} + +void guac_spice_clipboard_selection_handler(SpiceMainChannel channel, + guint selection, guint type, gpointer data, guint size, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Notifying client of clipboard data" + " available from the guest."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + switch (type) { + case VD_AGENT_CLIPBOARD_UTF8_TEXT: + guac_common_clipboard_append(spice_client->clipboard, (char *) data, size); + break; + + default: + guac_client_log(client, GUAC_LOG_WARNING, "Guacamole currently does" + " not support clipboard data other than plain text."); + } + +} + +void guac_spice_clipboard_selection_grab_handler(SpiceMainChannel channel, + guint selection, gpointer types, guint ntypes, guint extra, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Notifying client of clipboard grab" + " in the guest."); + guac_client_log(client, GUAC_LOG_DEBUG, "Arg: channel: 0x%08x", channel); + guac_client_log(client, GUAC_LOG_DEBUG, "Arg: selection: 0x%08x", selection); + guac_client_log(client, GUAC_LOG_DEBUG, "Arg: types: 0x%08x", types); + guac_client_log(client, GUAC_LOG_DEBUG, "Arg: ntypes: 0x%08x", ntypes); + guac_client_log(client, GUAC_LOG_DEBUG, "Arg: extra: 0x%08x", extra); + +} + +void guac_spice_clipboard_selection_release_handler(SpiceMainChannel channel, + guint selection, guint extra, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Notifying client of clipboard" + " release in the guest."); + +} + +void guac_spice_clipboard_selection_request_handler(SpiceMainChannel channel, + guint selection, guint types, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Requesting clipboard data from" + " the client."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + const char* input = spice_client->clipboard->buffer; + + spice_main_channel_clipboard_selection_notify(spice_client->main_channel, + VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, + VD_AGENT_CLIPBOARD_UTF8_TEXT, + (const unsigned char*) input, + spice_client->clipboard->length); + +} diff --git a/src/protocols/spice/channels/clipboard.h b/src/protocols/spice/channels/clipboard.h new file mode 100644 index 00000000..6dfdb265 --- /dev/null +++ b/src/protocols/spice/channels/clipboard.h @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_CLIPBOARD_H +#define GUAC_SPICE_CLIPBOARD_H + +#include "config.h" + +#include +#include + +#include + +/** + * Sets the encoding of clipboard data exchanged with the SPICE server to the + * encoding having the given name. If the name is NULL, or is invalid, the + * standard ISO8859-1 encoding will be used. + * + * @param client + * The client to set the clipboard encoding of. + * + * @param name + * The name of the encoding to use for all clipboard data. Valid values + * are: "ISO8859-1", "UTF-8", "UTF-16", "CP1252", or NULL. + * + * @return + * Zero if the chosen encoding is standard for SPICE, or non-zero if the + * SPICE standard is being violated. + */ +int guac_spice_set_clipboard_encoding(guac_client* client, + const char* name); + +/** + * Handler for inbound clipboard data from Guacamole users. + */ +guac_user_clipboard_handler guac_spice_clipboard_handler; + +/** + * Handler for stream data related to clipboard. + */ +guac_user_blob_handler guac_spice_clipboard_blob_handler; + +/** + * Handler for end-of-stream related to clipboard. + */ +guac_user_end_handler guac_spice_clipboard_end_handler; + +/** + * A handler that will be registered with the SPICE client to handle clipboard + * data sent from the SPICE server to the client. + * + * @param channel + * The main SPICE channel on which this event was fired. + * + * @param selection + * The clipboard on which the selection occurred. + * + * @param type + * The type of the data that is on the clipboard. + * + * @param data + * A pointer to the location containing the data that is on the clipboard. + * + * @param size + * The amount of data in bytes. + * + * @param client + * The guac_client associated with this event handler, passed when the + * handler was registered. + */ +void guac_spice_clipboard_selection_handler(SpiceMainChannel channel, + guint selection, guint type, gpointer data, guint size, + guac_client* client); + +/** + * A handler that will be registered with the SPICE client to handle clipboard + * events where the guest (vdagent) within the SPICE server notifies the client + * that data is available on the clipboard. + * + * @param channel + * The main SpiceChannel on which this event is fired. + * + * @param selection + * The SPICE clipboard from which the event is fired. + * + * @param types + * The type of data being sent by the agent. + * + * @param ntypes + * The number of data types represented. + * + * @param client + * The guac_client that was passed in when the callback was registered. + */ +void guac_spice_clipboard_selection_grab_handler(SpiceMainChannel channel, + guint selection, gpointer types, guint ntypes, guint other, guac_client* client); + +/** + * A handler that will be called by the SPICE client when the SPICE server + * is done with the clipboard and releases control of it. + * + * @param chennl + * The main SPICE channel on which this event is fired. + * + * @param selection + * The SPICE server clipboard releasing control. + * + * @param client + * The guac_client that was registered with the callback. + */ +void guac_spice_clipboard_selection_release_handler(SpiceMainChannel channel, + guint selection, guint other, guac_client* client); + +/** + * A handler that will be called by the SPICE client when the SPICE server + * would like to check and receive the contents of the client's clipboard. + * + * @param channel + * The main SPICE channel on which this event is fired. + * + * @param selection + * The SPICE server clipboard that is requesting data. + * + * @param types + * The type of data to be sent to the SPICE server. + * + * @param client + * The guac_client object that was registered with the callback. + */ +void guac_spice_clipboard_selection_request_handler(SpiceMainChannel channel, + guint selection, guint types, guac_client* client); + +#endif /* GUAC_SPICE_CLIPBOARD_H */ + diff --git a/src/protocols/spice/channels/cursor.c b/src/protocols/spice/channels/cursor.c new file mode 100644 index 00000000..ad48c576 --- /dev/null +++ b/src/protocols/spice/channels/cursor.c @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "client.h" +#include "common/cursor.h" +#include "common/display.h" +#include "common/surface.h" +#include "spice.h" +#include "spice-constants.h" + +#include +#include +#include +#include +#include +#include + +/* Define cairo_format_stride_for_width() if missing */ +#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH +#define cairo_format_stride_for_width(format, width) (width*4) +#endif + +#include +#include +#include +#include +#include + +void guac_spice_cursor_hide(SpiceChannel* channel, guac_client* client) { + + guac_client_log(client, GUAC_LOG_TRACE, "Cursor hide signal received."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_common_cursor_set_blank(spice_client->display->cursor); +} + +void guac_spice_cursor_move(SpiceChannel* channel, int x, int y, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_TRACE, "Cursor move signal received."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_common_cursor_update(spice_client->display->cursor, client->__owner, x, y, spice_client->display->cursor->button_mask); +} + +void guac_spice_cursor_reset(SpiceChannel* channel, guac_client* client) { + guac_client_log(client, GUAC_LOG_DEBUG, "Cursor reset signal received, not yet implemented"); +} + +void guac_spice_cursor_set(SpiceChannel* channel, int width, int height, + int x, int y, gpointer* rgba, guac_client* client) { + + guac_client_log(client, GUAC_LOG_TRACE, "Cursor set signal received."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + + /* Update stored cursor information */ + guac_common_cursor_set_argb(spice_client->display->cursor, x, y, + (const unsigned char*) rgba, width, height, stride); + +} diff --git a/src/protocols/spice/channels/cursor.h b/src/protocols/spice/channels/cursor.h new file mode 100644 index 00000000..ed3ddc64 --- /dev/null +++ b/src/protocols/spice/channels/cursor.h @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_CURSOR_H +#define GUAC_SPICE_CURSOR_H + +#include "config.h" + +#include + +/** + * The callback function that is executed when the cursor hide signal is received + * from the SPICE server. + * + * @param channel + * The channel which received the cursor hide event. + * + * @param client + * The guac_client associated with this session. + */ +void guac_spice_cursor_hide(SpiceCursorChannel* channel, guac_client* client); + +/** + * The callback function that is executed when the cursor move signal is + * received from the server. + * + * @param channel + * The channel that received the cursor move event. + * + * @param x + * The x position of the cursor. + * + * @param y + * The y position of the cursor. + * + * @param client + * The guac_client associated with this SPICE session. + */ +void guac_spice_cursor_move(SpiceCursorChannel* channel, int x, int y, + guac_client* client); + +/** + * The callback function that is executed in response to the cursor reset + * signal, resetting the cursor to the default context. + * + * @param channel + * The channel that received the cursor reset signal. + * + * @param client + * The guac_client associated with this SPICE session. + */ +void guac_spice_cursor_reset(SpiceCursorChannel* channel, guac_client* client); + +/** + * The callback function that is executed in response to receiving the cursor + * set signal from the SPICE server, which sets the width, height, and image + * of the cursor, and the x and y coordinates of the cursor hotspot. + * + * @param channel + * The channel that received the cursor set signal. + * + * @param width + * The width of the cursor image. + * + * @param height + * The height of the cursor image. + * + * @param x + * The x coordinate of the cursor hotspot. + * + * @param y + * The y coordinate of the cursor hotspot. + * + * @param rgba + * A pointer to the memory region containing the image data for the cursor, + * or NULL if the default cursor image should be used. + * + * @param client + * The guac_client associated with this SPICE session. + */ +void guac_spice_cursor_set(SpiceCursorChannel* channel, int width, int height, + int x, int y, gpointer* rgba, guac_client* client); + +#endif /* GUAC_SPICE_CURSOR_H */ + diff --git a/src/protocols/spice/channels/display.c b/src/protocols/spice/channels/display.c new file mode 100644 index 00000000..a65e701f --- /dev/null +++ b/src/protocols/spice/channels/display.c @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "client.h" +#include "common/display.h" +#include "common/iconv.h" +#include "common/surface.h" +#include "spice.h" + +#include +#include +#include +#include +#include +#include + +/* Define cairo_format_stride_for_width() if missing */ +#ifndef HAVE_CAIRO_FORMAT_STRIDE_FOR_WIDTH +#define cairo_format_stride_for_width(format, width) (width*4) +#endif + +#include +#include +#include +#include +#include + +void guac_spice_client_display_update(SpiceChannel* channel, int x, + int y, int w, int h, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received request to update Spice display: %d, %d, %d, %d", x, y, w, h); + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + /* Retrieve the primary display buffer */ + SpiceDisplayPrimary primary; + if (spice_display_channel_get_primary(channel, 0, &primary)) { + + /* Create cairo surface from decoded buffer */ + cairo_surface_t* surface = cairo_image_surface_create_for_data( + primary.data, + CAIRO_FORMAT_RGB24, + primary.width, + primary.height, + primary.stride); + + /* A region smaller than the entire display should be updated. */ + if ((x > 0 || y > 0 ) && (w < primary.width || h < primary.height)) { + + cairo_surface_t* updateArea = cairo_surface_create_similar_image(surface, CAIRO_FORMAT_RGB24, w, h); + cairo_t* updateContext = cairo_create(updateArea); + cairo_set_operator(updateContext, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(updateContext, surface, 0 - x, 0 - y); + cairo_rectangle(updateContext, 0, 0, w, h); + cairo_fill(updateContext); + guac_common_surface_draw(spice_client->display->default_surface, x, y, updateArea); + cairo_surface_destroy(updateArea); + + } + + /* The entire display should be updated. */ + else { + guac_common_surface_draw(spice_client->display->default_surface, + 0, 0, surface); + } + + /* Free surfaces */ + cairo_surface_destroy(surface); + } + + /* Flush surface and mark end of frame. */ + guac_common_surface_flush(spice_client->display->default_surface); + guac_client_end_frame(client); + guac_socket_flush(client->socket); +} + +void guac_spice_client_display_gl_draw(SpiceChannel* channel, int x, + int y, int w, int h, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received gl draw request."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + /* Copy specified rectangle within default layer */ + guac_common_surface_copy(spice_client->display->default_surface, + x, y, w, h, + spice_client->display->default_surface, x, y); + + +} + +void guac_spice_client_display_mark(SpiceChannel* channel, gint mark, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received signal to mark display."); + + int channelId; + + g_object_get(channel, "channel-id", &channelId, NULL); + + guac_client_log(client, GUAC_LOG_DEBUG, "Channel %i marked as available.", channelId); + +} + +void guac_spice_client_display_primary_create(SpiceChannel* channel, + gint format, gint width, gint height, gint stride, gint shmid, + gpointer imgdata, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received request to create primary display."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + spice_client->display = guac_common_display_alloc(client, + width, height); + + guac_client_log(client, GUAC_LOG_TRACE, "Creating Cairo image surface."); + cairo_surface_t* surface = cairo_image_surface_create_for_data(imgdata, CAIRO_FORMAT_RGB24, + width, height, stride); + + /* Draw directly to default layer */ + guac_client_log(client, GUAC_LOG_TRACE, "Drawing to the default surface."); + guac_common_surface_draw(spice_client->display->default_surface, + 0, 0, surface); + + guac_client_log(client, GUAC_LOG_TRACE, "Flushing the default surface."); + guac_common_surface_flush(spice_client->display->default_surface); + + guac_client_end_frame(client); + guac_socket_flush(client->socket); + + +} + +void guac_spice_client_display_primary_destroy(SpiceChannel* channel, + guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received request to destroy the primary display."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_common_display_free(spice_client->display); + +} + +void* guac_spice_client_streaming_handler(SpiceChannel* channel, + gboolean streaming_mode, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Received call to streaming handler."); + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + return spice_client->display; + +} + + diff --git a/src/protocols/spice/channels/display.h b/src/protocols/spice/channels/display.h new file mode 100644 index 00000000..3d1d1067 --- /dev/null +++ b/src/protocols/spice/channels/display.h @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_DISPLAY_H +#define GUAC_SPICE_DISPLAY_H + +#include "config.h" + +#include + +/** + * Callback invoked by the SPICE library when it receives a new binary image + * data from the SPICE server. The image itself will be stored in the designated + * sub-rectangle of client->framebuffer. + * + * @param channel + * The SpiceDisplayChannel that received the update event. + * + * @param x + * The X coordinate of the upper-left corner of the destination rectangle + * in which the image should be drawn, in pixels. + * + * @param y + * The Y coordinate of the upper-left corner of the destination rectangle + * in which the image should be drawn, in pixels. + * + * @param w + * The width of the image, in pixels. + * + * @param h + * The height of the image, in pixels. + * + * @param guac_client + * The guac_client associated with the event. + */ +void guac_spice_client_display_update(SpiceDisplayChannel* channel, int x, + int y, int w, int h, guac_client* client); + +/** + * The callback function invoked when the RED_DISPLAY_MARK command is received + * from the SPICE server and the display should be exposed. + * + * @param channel + * The SpiceDisplayChannel on which the event was received. + * + * @param mark + * Non-zero when the display mark has been received. + * + * @param client + * The guac_client associated with this channel and event. + */ +void guac_spice_client_display_mark(SpiceDisplayChannel* channel, gint mark, + guac_client* client); + +/** + * The callback function invoked when primary display buffer data is sent from + * the SPICE server to the client. + * + * @param channel + * The SpiceDisplayChannel on which this event was received. + * + * @param format + * The SPICE format of the received data. + * + * @param width + * The total width of the display. + * + * @param height + * The total height of the display. + * + * @param stride + * The buffer width padding. + * + * @param shmid + * The identifier of the shared memory segment associated with the data, or + * -1 if shared memory is not in use. + * + * @param imgdata + * A pointer to the buffer containing the surface data. + * + * @param client + * The guac_client associated with this channel/event. + */ +void guac_spice_client_display_primary_create(SpiceDisplayChannel* channel, + gint format, gint width, gint height, gint stride, gint shmid, + gpointer imgdata, guac_client* client); + +/** + * The callback function invoked by the client when the primary surface is + * destroyed and should no longer be accessed. + * + * @param channel + * The SpiceDisplayChannel on which the primary surface destroy event was + * received. + * + * @param client + * The guac_client associated with this channel/event. + */ +void guac_spice_client_display_primary_destroy(SpiceDisplayChannel* channel, + guac_client* client); + +/** + * Callback function that is invoked when a channel specifies that a display + * is marked as active and should be exposed. + * + * @param channel + * The channel on which the mark was received. + * + * @param marked + * TRUE if the display should be marked; otherwise false. + * + * @param client + * The guac_client associated with the channel/event. + */ +void guac_spice_client_display_mark(SpiceDisplayChannel* channel, + gboolean marked, guac_client* client); + +/** + * Callback invoked by the SPICE client when it receives a CopyRect message. + * CopyRect specified a rectangle of source data within the display and a + * set of X/Y coordinates to which that rectangle should be copied. + * + * @param channel + * The SpiceDisplayChannel that received the gl_draw event. + * + * @param x + * The X coordinate of the upper-left corner of the source rectangle + * from which the image data should be copied, in pixels. + * + * @param y + * The Y coordinate of the upper-left corner of the source rectangle + * from which the image data should be copied, in pixels. + * + * @param w + * The width of the source and destination rectangles, in pixels. + * + * @param h + * The height of the source and destination rectangles, in pixels. + * + * @param guac_client + * The guac_client associated with this gl_draw event. + */ +void guac_spice_client_display_gl_draw(SpiceDisplayChannel* channel, int x, + int y, int w, int h, guac_client* client); + +/** + * The callback function invoked by the client when it receives a request to + * change streaming mode. + * + * @param channel + * The SpiceDisplayChannel that received the streaming mode change request. + * + * @param streaming_mode + * TRUE if the display channel should be in streaming mode; otherwise FALSE. + * + * @param guac_client + * The guac_client associated with this event. + * + * @return + * A pointer to the display. + */ +void* guac_spice_client_streaming_handler(SpiceDisplayChannel* channel, + gboolean streaming_mode, guac_client* client); + +#endif /* GUAC_SPICE_DISPLAY_H */ + diff --git a/src/protocols/spice/channels/file.c b/src/protocols/spice/channels/file.c new file mode 100644 index 00000000..9439c818 --- /dev/null +++ b/src/protocols/spice/channels/file.c @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "file.h" + +#include + +void guac_spice_client_file_transfer_handler(SpiceMainChannel* main_channel, + SpiceFileTransferTask* task, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "File transfer handler."); + +} + +#include "file.h" \ No newline at end of file diff --git a/src/protocols/spice/channels/file.h b/src/protocols/spice/channels/file.h new file mode 100644 index 00000000..67df0e35 --- /dev/null +++ b/src/protocols/spice/channels/file.h @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_SPICE_FILE_H +#define GUAC_SPICE_FILE_H + +#include "config.h" + +#include + +#include + +/** + * A handler that is called when the SPICE client receives notification of + * a new file transfer task. + * + * @param main_channel + * The main channel associated with the SPICE session. + * + * @param task + * The file transfer task that triggered this function call. + * + * @param client + * The guac_client object associated with this session. + */ +void guac_spice_client_file_transfer_handler(SpiceMainChannel* main_channel, + SpiceFileTransferTask* task, guac_client* client); + +#endif /* GUAC_SPICE_FILE_H */ + diff --git a/src/protocols/spice/client.c b/src/protocols/spice/client.c new file mode 100644 index 00000000..34666dc5 --- /dev/null +++ b/src/protocols/spice/client.c @@ -0,0 +1,375 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "auth.h" +#include "client.h" +#include "channels/audio.h" +#include "channels/clipboard.h" +#include "channels/cursor.h" +#include "channels/display.h" +#include "channels/file.h" +#include "input.h" +#include "keyboard.h" +#include "user.h" +#include "spice.h" +#include "spice-constants.h" + +#ifdef ENABLE_COMMON_SSH +#include "common-ssh/sftp.h" +#include "common-ssh/ssh.h" +#include "common-ssh/user.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include + +/** + * Handle events for the main SPICE channel, taking the appropriate action + * for known events, and logging warnings for unknowns and non-fatal events. + * + * @param channel + * The channel for which to handle events. + * + * @param event + * The event that is being handled. + * + * @param client + * The guacamole_client associated with this event. + */ +static void guac_spice_client_main_channel_handler(SpiceChannel *channel, + SpiceChannelEvent event, guac_client* client) { + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_client_log(client, GUAC_LOG_DEBUG, "Received new main channel event: %u", event); + + /* Handle the various possible SPICE events. */ + switch (event) { + + /* Channel has been closed, so we abort the connection. */ + case SPICE_CHANNEL_CLOSED: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Disconnected from SPICE server."); + break; + + /* Channel has been opened - log it and do nothing else. */ + case SPICE_CHANNEL_OPENED: + guac_client_log(client, GUAC_LOG_DEBUG, "Channel opened."); + break; + + /* Error authenticating, log a warning and prompt user. */ + case SPICE_CHANNEL_ERROR_AUTH: + guac_client_log(client, GUAC_LOG_WARNING, + "Channel authentication failed."); + + /* Trigger a credential prompt. */ + if (guac_spice_get_credentials(client) + && spice_session_connect(spice_client->spice_session)) + guac_client_log(client, GUAC_LOG_DEBUG, "Session connection started."); + + else + guac_client_abort(client, + GUAC_PROTOCOL_STATUS_CLIENT_UNAUTHORIZED, + "Failed to get credentials to connect to server."); + + break; + + /* TLS error, abort the connection with a warning. */ + case SPICE_CHANNEL_ERROR_TLS: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "TLS failure connecting to SPICE server."); + break; + + /* I/O error, abort the connection with a warning. */ + case SPICE_CHANNEL_ERROR_IO: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "IO error communicating with SPICE server."); + break; + + /* SPICE link error, abort the connection with a warning. */ + case SPICE_CHANNEL_ERROR_LINK: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Link error communicating with SPICE server."); + break; + + /* Connect error, abort the connection with a warning. */ + case SPICE_CHANNEL_ERROR_CONNECT: + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Connection error communicating with SPICe server."); + break; + + /* Some other unknown event - log it and move on. */ + default: + guac_client_log(client, GUAC_LOG_WARNING, + "Unknown event received on channel."); + } + +} + +int guac_client_init(guac_client* client) { + + /* Set client args */ + client->args = GUAC_SPICE_CLIENT_ARGS; + + guac_client_log(client, GUAC_LOG_DEBUG, "Initializing SPICE client."); + + /* Alloc client data */ + guac_spice_client* spice_client = calloc(1, sizeof(guac_spice_client)); + client->data = spice_client; + + guac_client_log(client, GUAC_LOG_DEBUG, "Initializing clipboard."); + + /* Init clipboard */ + spice_client->clipboard = + guac_common_clipboard_alloc(GUAC_SPICE_CLIPBOARD_MAX_LENGTH); + + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up user handlers."); + + /* Set handlers */ + client->join_handler = guac_spice_user_join_handler; + client->leave_handler = guac_spice_user_leave_handler; + client->free_handler = guac_spice_client_free_handler; + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up mutex locks."); + + /* Recursive attribute for locks */ + pthread_mutexattr_init(&(spice_client->attributes)); + pthread_mutexattr_settype(&(spice_client->attributes), + PTHREAD_MUTEX_RECURSIVE); + + /* Init required locks */ + pthread_rwlock_init(&(spice_client->lock), NULL); + pthread_mutex_init(&(spice_client->message_lock), &(spice_client->attributes)); + + return 0; +} + +int guac_spice_client_free_handler(guac_client* client) { + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_spice_settings* settings = spice_client->settings; + + /* Clean up SPICE client*/ + SpiceSession* spice_session = spice_client->spice_session; + if (spice_session != NULL) { + + /* Wait for client thread to finish */ + pthread_join(spice_client->client_thread, NULL); + + /* Disconnect the session, destroying data */ + spice_session_disconnect(spice_session); + + /* Free up the glib main loop. */ + g_main_loop_unref(spice_client->spice_mainloop); + + } + +#ifdef ENABLE_COMMON_SSH + /* Free SFTP filesystem, if loaded */ + if (spice_client->sftp_filesystem) + guac_common_ssh_destroy_sftp_filesystem(spice_client->sftp_filesystem); + + /* Free SFTP session */ + if (spice_client->sftp_session) + guac_common_ssh_destroy_session(spice_client->sftp_session); + + /* Free SFTP user */ + if (spice_client->sftp_user) + guac_common_ssh_destroy_user(spice_client->sftp_user); + + guac_common_ssh_uninit(); +#endif + + /* Clean up recording, if in progress */ + if (spice_client->recording != NULL) + guac_recording_free(spice_client->recording); + + /* Free clipboard */ + if (spice_client->clipboard != NULL) + guac_common_clipboard_free(spice_client->clipboard); + + /* Free display */ + if (spice_client->display != NULL) + guac_common_display_free(spice_client->display); + + /* Free SPICE keyboard state */ + guac_spice_keyboard_free(spice_client->keyboard); + spice_client->keyboard = NULL; + + /* Free parsed settings */ + if (settings != NULL) + guac_spice_settings_free(settings); + + pthread_rwlock_destroy(&(spice_client->lock)); + pthread_mutex_destroy(&(spice_client->message_lock)); + + /* Free generic data struct */ + free(client->data); + + return 0; +} + +void guac_spice_client_channel_handler(SpiceSession *spice_session, + SpiceChannel *channel, guac_client* client) { + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_spice_settings* settings = spice_client->settings; + int id; + + /* Get the channel ID. */ + g_object_get(channel, SPICE_PROPERTY_CHANNEL_ID, &id, NULL); + + guac_client_log(client, GUAC_LOG_DEBUG, "New channel created: %i", id); + + /* Check if this is the main channel and register handlers. */ + if (SPICE_IS_MAIN_CHANNEL(channel)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up main channel."); + spice_client->main_channel = SPICE_MAIN_CHANNEL(channel); + + /* Register the main channel event handler and return. */ + g_signal_connect(channel, SPICE_SIGNAL_CHANNEL_EVENT, + G_CALLBACK(guac_spice_client_main_channel_handler), client); + + /* Register clipboard handlers. */ + g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION, + G_CALLBACK(guac_spice_clipboard_selection_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_GRAB, + G_CALLBACK(guac_spice_clipboard_selection_grab_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_RELEASE, + G_CALLBACK(guac_spice_clipboard_selection_release_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_REQUEST, + G_CALLBACK(guac_spice_clipboard_selection_request_handler), client); + + /* Update the main display with our size. */ + if (client->__owner != NULL) + spice_main_channel_update_display(spice_client->main_channel, + GUAC_SPICE_DEFAULT_DISPLAY_ID, + 0, + 0, + client->__owner->info.optimal_width, + client->__owner->info.optimal_height, TRUE); + return; + } + + /* Check if this is the display channel and register display handlers. */ + if (SPICE_IS_DISPLAY_CHANNEL(channel) && id == 0) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up display channel."); + int width, height; + SpiceDisplayPrimary primary; + g_object_get(channel, "width", &width, "height", &height, NULL); + spice_client->spice_display = SPICE_DISPLAY_CHANNEL(channel); + spice_client->display = guac_common_display_alloc(client, width, height); + + /* Register callbacks fr the various display signals. */ + g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_INVALIDATE, + G_CALLBACK(guac_spice_client_display_update), client); + g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_MARK, + G_CALLBACK(guac_spice_client_display_mark), client); + g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_PRIMARY_CREATE, + G_CALLBACK(guac_spice_client_display_primary_create), client); + g_signal_connect(channel, SPICE_SIGNAL_DISPLAY_PRIMARY_DESTROY, + G_CALLBACK(guac_spice_client_display_primary_destroy), client); + g_signal_connect(channel, SPICE_SIGNAL_GL_DRAW, + G_CALLBACK(guac_spice_client_display_gl_draw), client); + g_signal_connect(channel, SPICE_SIGNAL_STREAMING_MODE, + G_CALLBACK(guac_spice_client_streaming_handler), client); + + /* Attempt to get the primary display, and set it up. */ + if (spice_display_channel_get_primary(channel, 0, &primary)) { + guac_spice_client_display_primary_create( + spice_client->spice_display, primary.format, + primary.width, primary.height, primary.stride, + primary.shmid, primary.data, client); + guac_spice_client_display_mark(spice_client->spice_display, + primary.marked, client); + } + + } + + /* Check for audio playback channel and set up the channel. */ + if (SPICE_IS_PLAYBACK_CHANNEL(channel) && settings->audio_enabled) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio playback channel."); + spice_client->playback_channel = SPICE_PLAYBACK_CHANNEL(channel); + g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_DATA, + G_CALLBACK(guac_spice_client_audio_playback_data_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_GET_DELAY, + G_CALLBACK(guac_spice_client_audio_playback_delay_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_START, + G_CALLBACK(guac_spice_client_audio_playback_start_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_PLAYBACK_STOP, + G_CALLBACK(guac_spice_client_audio_playback_stop_handler), client); + } + + /* Check for audio recording channel and set up the channel. */ + if (SPICE_IS_RECORD_CHANNEL(channel) && settings->audio_enabled) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up audio record channel."); + spice_client->record_channel = SPICE_RECORD_CHANNEL(channel); + g_signal_connect(channel, SPICE_SIGNAL_RECORD_START, + G_CALLBACK(guac_spice_client_audio_record_start_handler), client); + g_signal_connect(channel, SPICE_SIGNAL_RECORD_STOP, + G_CALLBACK(guac_spice_client_audio_record_stop_handler), client); + } + + /* Check for cursor channel and set it up. */ + if (SPICE_IS_CURSOR_CHANNEL(channel)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up cursor channel."); + spice_client->cursor_channel = SPICE_CURSOR_CHANNEL(channel); + g_signal_connect(channel, SPICE_SIGNAL_CURSOR_HIDE, + G_CALLBACK(guac_spice_cursor_hide), client); + g_signal_connect(channel, SPICE_SIGNAL_CURSOR_MOVE, + G_CALLBACK(guac_spice_cursor_move), client); + g_signal_connect(channel, SPICE_SIGNAL_CURSOR_RESET, + G_CALLBACK(guac_spice_cursor_reset), client); + g_signal_connect(channel, SPICE_SIGNAL_CURSOR_SET, + G_CALLBACK(guac_spice_cursor_set), client); + } + + /* Check if this is an inputs channel and set it up. */ + if (SPICE_IS_INPUTS_CHANNEL(channel)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up inputs channel."); + spice_client->inputs_channel = SPICE_INPUTS_CHANNEL(channel); + + /* Register callback that sets keyboard modifiers */ + g_signal_connect(channel, SPICE_SIGNAL_INPUTS_MODIFIERS, + G_CALLBACK(guac_spice_keyboard_set_indicators), client); + } + + /* File transfer channel */ + if (SPICE_IS_WEBDAV_CHANNEL(channel)) { + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up webdav channel."); + if (settings->file_transfer + && settings->file_directory != NULL + && strcmp(settings->file_directory, "") != 0) { + } + } + + guac_client_log(client, GUAC_LOG_DEBUG, "Calling spice_channel_connect for channel %d.", id); + if (!spice_channel_connect(channel)) + guac_client_abort(client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, + "Unable to connect the channel."); + +} diff --git a/src/protocols/spice/client.h b/src/protocols/spice/client.h new file mode 100644 index 00000000..a5077051 --- /dev/null +++ b/src/protocols/spice/client.h @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_SPICE_CLIENT_H +#define GUAC_SPICE_CLIENT_H + +#include "config.h" + +#include + +#include + +/** + * The maximum duration of a frame in milliseconds. + */ +#define GUAC_SPICE_FRAME_DURATION 40 + +/** + * The amount of time to allow per message read within a frame, in + * milliseconds. If the server is silent for at least this amount of time, the + * frame will be considered finished. + */ +#define GUAC_SPICE_FRAME_TIMEOUT 0 + +/** + * The amount of time to wait for a new message from the SPICE server when + * beginning a new frame. This value must be kept reasonably small such that + * a slow SPICE server will not prevent external events from being handled (such + * as the stop signal from guac_client_stop()), but large enough that the + * message handling loop does not eat up CPU spinning. + */ +#define GUAC_SPICE_FRAME_START_TIMEOUT 1000000 + +/** + * The number of milliseconds to wait between connection attempts. + */ +#define GUAC_SPICE_CONNECT_INTERVAL 1000 + +/** + * The maximum number of bytes to allow within the clipboard. + */ +#define GUAC_SPICE_CLIPBOARD_MAX_LENGTH 262144 + +/** + * Handler which frees all data associated with the guac_client. + */ +guac_client_free_handler guac_spice_client_free_handler; + +/** + * Handler for new channel events. + */ +void guac_spice_client_channel_handler(SpiceSession *spice_session, + SpiceChannel *channel, guac_client* client); + +#endif /* GUAC_SPICE_CLIENT_H */ + diff --git a/src/protocols/spice/decompose.c b/src/protocols/spice/decompose.c new file mode 100644 index 00000000..5f4cd65b --- /dev/null +++ b/src/protocols/spice/decompose.c @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "keyboard.h" + +/** + * The X11 keysym for the dead key which types a grave (`). + */ +#define DEAD_GRAVE 0xFE50 + +/** + * The X11 keysym for the dead key which types an acute (´). Note that this is + * NOT equivalent to an apostrophe or single quote. + */ +#define DEAD_ACUTE 0xFE51 + +/** + * The X11 keysym for the dead key which types a circumflex/caret (^). + */ +#define DEAD_CIRCUMFLEX 0xFE52 + +/** + * The X11 keysym for the dead key which types a tilde (~). + */ +#define DEAD_TILDE 0xFE53 + +/** + * The X11 keysym for the dead key which types a dieresis/umlaut (¨). + */ +#define DEAD_DIERESIS 0xFE57 + +/** + * The X11 keysym for the dead key which types an abovering (˚). Note that this + * is NOT equivalent to the degree symbol. + */ +#define DEAD_ABOVERING 0xFE58 + +/** + * The decomposed form of a key that can be typed using two keypresses: a dead + * key followed by a base key. For example, on a keyboard which lacks a single + * dedicated key for doing the same, "ó" would be typed using the dead acute + * key followed by the "o" key. The dead key and base key are pressed and + * released in sequence; they are not held down. + */ +typedef struct guac_spice_decomposed_key { + + /** + * The keysym of the dead key which must first be pressed and released to + * begin typing the desired character. The dead key defines the diacritic + * which will be applied to the character typed by the base key. + */ + int dead_keysym; + + /** + * The keysym of the base key which must be pressed and released to finish + * typing the desired character. The base key defines the normal form of + * the character (the form which lacks any diacritic) to which the + * diacritic defined by the previously-pressed dead key will be applied. + */ + int base_keysym; + +} guac_spice_decomposed_key; + +/** + * A lookup table of all known decomposed forms of various keysyms. Keysyms map + * directly to entries within this table. A keysym which has no entry within + * this table does not have a defined decomposed form (or at least does not + * have a decomposed form relevant to SPICE). + */ +guac_spice_decomposed_key guac_spice_decomposed_keys[256] = { + + /* ^ */ [0x005E] = { DEAD_CIRCUMFLEX, ' ' }, + /* ` */ [0x0060] = { DEAD_GRAVE, ' ' }, + /* ~ */ [0x007E] = { DEAD_TILDE, ' ' }, + /* ¨ */ [0x00A8] = { DEAD_DIERESIS, ' ' }, + /* ´ */ [0x00B4] = { DEAD_ACUTE, ' ' }, + /* À */ [0x00C0] = { DEAD_GRAVE, 'A' }, + /* Á */ [0x00C1] = { DEAD_ACUTE, 'A' }, + /*  */ [0x00C2] = { DEAD_CIRCUMFLEX, 'A' }, + /* à */ [0x00C3] = { DEAD_TILDE, 'A' }, + /* Ä */ [0x00C4] = { DEAD_DIERESIS, 'A' }, + /* Å */ [0x00C5] = { DEAD_ABOVERING, 'A' }, + /* È */ [0x00C8] = { DEAD_GRAVE, 'E' }, + /* É */ [0x00C9] = { DEAD_ACUTE, 'E' }, + /* Ê */ [0x00CA] = { DEAD_CIRCUMFLEX, 'E' }, + /* Ë */ [0x00CB] = { DEAD_DIERESIS, 'E' }, + /* Ì */ [0x00CC] = { DEAD_GRAVE, 'I' }, + /* Í */ [0x00CD] = { DEAD_ACUTE, 'I' }, + /* Î */ [0x00CE] = { DEAD_CIRCUMFLEX, 'I' }, + /* Ï */ [0x00CF] = { DEAD_DIERESIS, 'I' }, + /* Ñ */ [0x00D1] = { DEAD_TILDE, 'N' }, + /* Ò */ [0x00D2] = { DEAD_GRAVE, 'O' }, + /* Ó */ [0x00D3] = { DEAD_ACUTE, 'O' }, + /* Ô */ [0x00D4] = { DEAD_CIRCUMFLEX, 'O' }, + /* Õ */ [0x00D5] = { DEAD_TILDE, 'O' }, + /* Ö */ [0x00D6] = { DEAD_DIERESIS, 'O' }, + /* Ù */ [0x00D9] = { DEAD_GRAVE, 'U' }, + /* Ú */ [0x00DA] = { DEAD_ACUTE, 'U' }, + /* Û */ [0x00DB] = { DEAD_CIRCUMFLEX, 'U' }, + /* Ü */ [0x00DC] = { DEAD_DIERESIS, 'U' }, + /* Ý */ [0x00DD] = { DEAD_ACUTE, 'Y' }, + /* à */ [0x00E0] = { DEAD_GRAVE, 'a' }, + /* á */ [0x00E1] = { DEAD_ACUTE, 'a' }, + /* â */ [0x00E2] = { DEAD_CIRCUMFLEX, 'a' }, + /* ã */ [0x00E3] = { DEAD_TILDE, 'a' }, + /* ä */ [0x00E4] = { DEAD_DIERESIS, 'a' }, + /* å */ [0x00E5] = { DEAD_ABOVERING, 'a' }, + /* è */ [0x00E8] = { DEAD_GRAVE, 'e' }, + /* é */ [0x00E9] = { DEAD_ACUTE, 'e' }, + /* ê */ [0x00EA] = { DEAD_CIRCUMFLEX, 'e' }, + /* ë */ [0x00EB] = { DEAD_DIERESIS, 'e' }, + /* ì */ [0x00EC] = { DEAD_GRAVE, 'i' }, + /* í */ [0x00ED] = { DEAD_ACUTE, 'i' }, + /* î */ [0x00EE] = { DEAD_CIRCUMFLEX, 'i' }, + /* ï */ [0x00EF] = { DEAD_DIERESIS, 'i' }, + /* ñ */ [0x00F1] = { DEAD_TILDE, 'n' }, + /* ò */ [0x00F2] = { DEAD_GRAVE, 'o' }, + /* ó */ [0x00F3] = { DEAD_ACUTE, 'o' }, + /* ô */ [0x00F4] = { DEAD_CIRCUMFLEX, 'o' }, + /* õ */ [0x00F5] = { DEAD_TILDE, 'o' }, + /* ö */ [0x00F6] = { DEAD_DIERESIS, 'o' }, + /* ù */ [0x00F9] = { DEAD_GRAVE, 'u' }, + /* ú */ [0x00FA] = { DEAD_ACUTE, 'u' }, + /* û */ [0x00FB] = { DEAD_CIRCUMFLEX, 'u' }, + /* ü */ [0x00FC] = { DEAD_DIERESIS, 'u' }, + /* ý */ [0x00FD] = { DEAD_ACUTE, 'y' }, + /* ÿ */ [0x00FF] = { DEAD_DIERESIS, 'y' } + +}; + +int guac_spice_decompose_keysym(guac_spice_keyboard* keyboard, int keysym) { + + /* Verify keysym is within range of lookup table */ + if (keysym < 0x00 || keysym > 0xFF) + return 1; + + /* Verify keysym is actually defined within lookup table */ + guac_spice_decomposed_key* key = &guac_spice_decomposed_keys[keysym]; + if (!key->dead_keysym) + return 1; + + /* Cannot type using decomposed keys if those keys are not defined within + * the current layout */ + if (!guac_spice_keyboard_is_defined(keyboard, key->dead_keysym) + || !guac_spice_keyboard_is_defined(keyboard, key->base_keysym)) + return 1; + + /* Press dead key */ + guac_spice_keyboard_update_keysym(keyboard, key->dead_keysym, 1, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, key->dead_keysym, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + + /* Press base key */ + guac_spice_keyboard_update_keysym(keyboard, key->base_keysym, 1, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, key->base_keysym, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + + /* Decomposed key successfully typed */ + return 0; + +} + diff --git a/src/protocols/spice/decompose.h b/src/protocols/spice/decompose.h new file mode 100644 index 00000000..cffb9a64 --- /dev/null +++ b/src/protocols/spice/decompose.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_DECOMPOSE_H +#define GUAC_SPICE_DECOMPOSE_H + +#include "config.h" +#include "keyboard.h" + +/** + * Attempts to type the given keysym by decomposing the associated character + * into the dead key and base key pair which would be used to type that + * character on a keyboard which lacks the necessary dedicated key. The key + * events for the dead key and base key are sent only if the keyboard layout of + * the given keyboard defines those keys. + * + * For example, the keysym for "ò" (0x00F2) would decompose into a dead grave + * (`) and the base key "o". May it rest in peace. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param keysym + * The keysym being pressed. + * + * @return + * Zero if the keysym was successfully decomposed and sent to the SPICE + * server as a pair of key events (the dead key and base key), non-zero + * otherwise. + */ +int guac_spice_decompose_keysym(guac_spice_keyboard* keyboard, int keysym); + +#endif + diff --git a/src/protocols/spice/input.c b/src/protocols/spice/input.c new file mode 100644 index 00000000..e8f8ab54 --- /dev/null +++ b/src/protocols/spice/input.c @@ -0,0 +1,154 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "common/cursor.h" +#include "common/display.h" +#include "spice.h" +#include "spice-constants.h" + +#include +#include +#include + +int guac_spice_user_mouse_handler(guac_user* user, int x, int y, int mask) { + + guac_client* client = user->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + /* Store the old button mask. */ + int curr_button_mask = spice_client->display->cursor->button_mask; + + guac_user_log(user, GUAC_LOG_TRACE, "Handling mouse event: %d, %d, 0x%08x", x, y, mask); + + /* Update current mouse location/state */ + guac_common_cursor_update(spice_client->display->cursor, user, x, y, mask); + + /* Report mouse position and button state within recording. */ + if (spice_client->recording != NULL) + guac_recording_report_mouse(spice_client->recording, x, y, mask); + + /* Send SPICE event only if finished connecting */ + if (spice_client->inputs_channel != NULL) { + spice_inputs_channel_position(spice_client->inputs_channel, x, y, GUAC_SPICE_DEFAULT_DISPLAY_ID, mask); + + /* Button state has changed, so we need to evaluate which buttons changed. */ + if (curr_button_mask != mask) { + + /* Left click. */ + if (curr_button_mask ^ GUAC_CLIENT_MOUSE_LEFT && mask & GUAC_CLIENT_MOUSE_LEFT) { + guac_user_log(user, GUAC_LOG_TRACE, "Left click!"); + spice_inputs_channel_button_press(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_LEFT, mask); + } + + /* Left release. */ + if (curr_button_mask & GUAC_CLIENT_MOUSE_LEFT && mask ^ GUAC_CLIENT_MOUSE_LEFT) { + guac_user_log(user, GUAC_LOG_TRACE, "Left release!"); + spice_inputs_channel_button_release(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_LEFT, mask); + } + + /* Middle click. */ + if (curr_button_mask ^ GUAC_CLIENT_MOUSE_MIDDLE && mask & GUAC_CLIENT_MOUSE_MIDDLE) { + guac_user_log(user, GUAC_LOG_TRACE, "Middle click!"); + spice_inputs_channel_button_press(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_MIDDLE, mask); + } + + /* Middle release. */ + if (curr_button_mask & GUAC_CLIENT_MOUSE_MIDDLE && mask ^ GUAC_CLIENT_MOUSE_MIDDLE) { + guac_user_log(user, GUAC_LOG_TRACE, "Middle release!"); + spice_inputs_channel_button_release(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_MIDDLE, mask); + } + + /* Right click. */ + if (curr_button_mask ^ GUAC_CLIENT_MOUSE_RIGHT && mask & GUAC_CLIENT_MOUSE_RIGHT) { + guac_user_log(user, GUAC_LOG_TRACE, "Right click!"); + spice_inputs_channel_button_press(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_RIGHT, mask); + } + + /* Right release. */ + if (curr_button_mask & GUAC_CLIENT_MOUSE_RIGHT && mask ^ GUAC_CLIENT_MOUSE_RIGHT) { + guac_user_log(user, GUAC_LOG_TRACE, "Right release!"); + spice_inputs_channel_button_release(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_RIGHT, mask); + } + + /* Scroll up. */ + if (curr_button_mask ^ GUAC_CLIENT_MOUSE_SCROLL_UP && mask & GUAC_CLIENT_MOUSE_SCROLL_UP) { + guac_user_log(user, GUAC_LOG_TRACE, "Scroll up!"); + spice_inputs_channel_button_press(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_UP, mask); + } + + /* Scroll up stop. */ + if (curr_button_mask & GUAC_CLIENT_MOUSE_SCROLL_UP && mask ^ GUAC_CLIENT_MOUSE_SCROLL_UP) { + guac_user_log(user, GUAC_LOG_TRACE, "Scroll up stop!"); + spice_inputs_channel_button_release(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_UP, mask); + } + + /* Scroll down. */ + if (curr_button_mask ^ GUAC_CLIENT_MOUSE_SCROLL_DOWN && mask & GUAC_CLIENT_MOUSE_SCROLL_DOWN) { + guac_user_log(user, GUAC_LOG_TRACE, "Scroll down!"); + spice_inputs_channel_button_press(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_DOWN, mask); + } + + /* Scroll down stop. */ + if (curr_button_mask & GUAC_CLIENT_MOUSE_SCROLL_DOWN && mask ^ GUAC_CLIENT_MOUSE_SCROLL_DOWN) { + guac_user_log(user, GUAC_LOG_TRACE, "Scroll down stop!"); + spice_inputs_channel_button_release(spice_client->inputs_channel, SPICE_MOUSE_BUTTON_DOWN, mask); + } + + } + + } + + return 0; +} + +int guac_spice_user_key_handler(guac_user* user, int keysym, int pressed) { + + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + int retval = 0; + + pthread_rwlock_rdlock(&(spice_client->lock)); + + guac_user_log(user, GUAC_LOG_TRACE, "Handling keypress: 0x%08x", keysym); + + /* Report key state within recording */ + if (spice_client->recording != NULL) + guac_recording_report_key(spice_client->recording, + keysym, pressed); + + /* Skip if keyboard not yet ready */ + if (spice_client->inputs_channel == NULL || spice_client->keyboard == NULL) + goto complete; + + /* Update keysym state */ + retval = guac_spice_keyboard_update_keysym(spice_client->keyboard, + keysym, pressed, GUAC_SPICE_KEY_SOURCE_CLIENT); + +complete: + pthread_rwlock_unlock(&(spice_client->lock)); + + return retval; +} + +void guac_spice_mouse_mode_update(SpiceChannel* channel, guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Updating mouse mode, not implemented."); + +} \ No newline at end of file diff --git a/src/protocols/spice/input.h b/src/protocols/spice/input.h new file mode 100644 index 00000000..4ae5d689 --- /dev/null +++ b/src/protocols/spice/input.h @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_SPICE_INPUT_H +#define GUAC_SPICE_INPUT_H + +#include "config.h" + +#include + +/** + * Handler for Guacamole user mouse events. + */ +guac_user_mouse_handler guac_spice_user_mouse_handler; + +/** + * Handler for Guacamole user key events. + */ +guac_user_key_handler guac_spice_user_key_handler; + +/** + * A callback that is invoked when the SPICE server updates the mouse mode. + * + * @param channel + * The channel on which the update occurred. + * + * @param client + * The guac_client instance associated with this session. + */ +void guac_spice_mouse_mode_update(SpiceChannel* channel, guac_client* client); + +#endif /* GUAC_SPICE_INPUT_H */ + diff --git a/src/protocols/spice/keyboard.c b/src/protocols/spice/keyboard.c new file mode 100644 index 00000000..f5afa773 --- /dev/null +++ b/src/protocols/spice/keyboard.c @@ -0,0 +1,652 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "decompose.h" +#include "keyboard.h" +#include "keymap.h" +#include "spice.h" +#include "spice-constants.h" + +#include + +#include + +/** + * Translates the given keysym into the corresponding lock flag, as would be + * required by the SPICE synchronize event. If the given keysym does not + * represent a lock key, zero is returned. + * + * @param keysym + * The keysym to translate into a SPICE lock flag. + * + * @return + * The SPICE lock flag which corresponds to the given keysym, or zero if the + * given keysym does not represent a lock key. + */ +static int guac_spice_keyboard_lock_flag(int keysym) { + + /* Translate keysym into corresponding lock flag */ + switch (keysym) { + + /* Scroll lock */ + case GUAC_SPICE_KEYSYM_SCROLL_LOCK: + return SPICE_INPUTS_SCROLL_LOCK; + + /* Num lock */ + case GUAC_SPICE_KEYSYM_NUM_LOCK: + return SPICE_INPUTS_NUM_LOCK; + + /* Caps lock */ + case GUAC_SPICE_KEYSYM_CAPS_LOCK: + return SPICE_INPUTS_CAPS_LOCK; + + } + + /* Not a lock key */ + return 0; + +} + +/** + * Immediately sends an SPICE key event having the given scancode and flags. + * + * @param spice_client + * The SPICE client instance associated with the SPICE session along which the + * key event should be sent. + * + * @param scancode + * The scancode of the key to press or release via the SPICE key event. + * + * @param flags + * Any SPICE-specific flags required for the provided scancode to have the + * intended meaning, such as KBD_FLAGS_EXTENDED. The possible flags and + * their meanings are dictated by SPICE. KBD_FLAGS_DOWN and KBD_FLAGS_UP + * need not be specified here - they will automatically be added depending + * on the value specified for the pressed parameter. + * + * @param pressed + * Non-zero if the key is being pressed, zero if the key is being released. + */ +static void guac_spice_send_key_event(guac_spice_client* spice_client, + int scancode, int flags, int pressed) { + + /* Send actual key press or release */ + pthread_mutex_lock(&(spice_client->message_lock)); + if (pressed) + spice_inputs_channel_key_press(spice_client->inputs_channel, scancode); + else + spice_inputs_channel_key_release(spice_client->inputs_channel, scancode); + pthread_mutex_unlock(&(spice_client->message_lock)); + +} + +/** + * Immediately sends an SPICE synchonize event having the given flags. An SPICE + * synchronize event sets the state of remote lock keys absolutely, where a + * lock key will be active only if its corresponding flag is set in the event. + * + * @param spice_client + * The SPICE client instance associated with the SPICE session along which the + * synchronize event should be sent. + * + * @param modifiers + * Bitwise OR of the flags representing the lock keys which should be set, + * if any, as dictated by the SPICE protocol. If no flags are set, then no + * lock keys will be active. + */ +static void guac_spice_send_synchronize_event(guac_spice_client* spice_client, + unsigned int modifiers) { + + /* Skip if inputs channel is not connected */ + if (spice_client->inputs_channel == NULL) + return; + + /* Synchronize lock key states */ + pthread_mutex_lock(&(spice_client->message_lock)); + spice_inputs_channel_set_key_locks(spice_client->inputs_channel, modifiers); + pthread_mutex_unlock(&(spice_client->message_lock)); + +} + +/** + * Given a keyboard instance and X11 keysym, returns a pointer to the + * keys_by_keysym entry that represents the key having that keysym within the + * keyboard, regardless of whether the key is currently defined. If no such key + * can exist (the keysym cannot be mapped or is out of range), NULL is + * returned. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param keysym + * The keysym of the key to lookup within the given keyboard. + * + * @return + * A pointer to the keys_by_keysym entry which represents or can represent + * the key having the given keysym, or NULL if no such keysym can be + * defined within a guac_spice_keyboard structure. + */ +static guac_spice_key** guac_spice_keyboard_map_key(guac_spice_keyboard* keyboard, + int keysym) { + + int index; + + /* Map keysyms between 0x0000 and 0xFFFF directly */ + if (keysym >= 0x0000 && keysym <= 0xFFFF) + index = keysym; + + /* Map all Unicode keysyms from U+0000 to U+FFFF */ + else if (keysym >= 0x1000000 && keysym <= 0x100FFFF) + index = 0x10000 + (keysym & 0xFFFF); + + /* All other keysyms are unmapped */ + else + return NULL; + + /* Corresponding key mapping (defined or not) has been located */ + return &(keyboard->keys_by_keysym[index]); + +} + +/** + * Returns the number of bits that are set within the given integer (the number + * of 1s in the binary expansion of the given integer). + * + * @param value + * The integer to read. + * + * @return + * The number of bits that are set within the given integer. + */ +static int guac_spice_count_bits(unsigned int value) { + + int bits = 0; + + while (value) { + bits += value & 1; + value >>= 1; + } + + return bits; + +} + +/** + * Returns an estimated cost for sending the necessary SPICE events to type the + * key described by the given guac_spice_keysym_desc, given the current lock and + * modifier state of the keyboard. A higher cost value indicates that a greater + * number of events are expected to be required. + * + * Lower-cost approaches should be preferred when multiple alternatives exist + * for typing a particular key, as the lower cost implies fewer additional key + * events required to produce the expected behavior. For example, if Caps Lock + * is enabled, typing an uppercase "A" by pressing the "A" key has a lower cost + * than disabling Caps Lock and pressing Shift+A. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param def + * The guac_spice_keysym_desc that describes the key being pressed, as well + * as any requirements that must be satisfied for the key to be interpreted + * as expected. + * + * @return + * An arbitrary integer value which indicates the overall estimated + * complexity of typing the given key. + */ +static int guac_spice_keyboard_get_cost(guac_spice_keyboard* keyboard, + const guac_spice_keysym_desc* def) { + + unsigned int modifiers = guac_spice_keyboard_get_modifier_flags(keyboard); + + /* Each change to any key requires one event, by definition */ + int cost = 1; + + /* Each change to a lock requires roughly two key events */ + unsigned int update_locks = (def->set_modifiers & ~keyboard->modifiers) | (def->clear_modifiers & keyboard->modifiers); + cost += guac_spice_count_bits(update_locks) * 2; + + /* Each change to a modifier requires one key event */ + unsigned int update_modifiers = (def->clear_modifiers & modifiers) | (def->set_modifiers & ~modifiers); + cost += guac_spice_count_bits(update_modifiers); + + return cost; + +} + +/** + * Returns a pointer to the guac_spice_key structure representing the + * definition(s) and state of the key having the given keysym. If no such key + * is defined within the keyboard layout of the SPICE server, NULL is returned. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param keysym + * The keysym of the key to lookup within the given keyboard. + * + * @return + * A pointer to the guac_spice_key structure representing the definition(s) + * and state of the key having the given keysym, or NULL if no such key is + * defined within the keyboard layout of the SPICE server. + */ +static guac_spice_key* guac_spice_keyboard_get_key(guac_spice_keyboard* keyboard, + int keysym) { + + /* Verify that the key is actually defined */ + guac_spice_key** key_by_keysym = guac_spice_keyboard_map_key(keyboard, keysym); + if (key_by_keysym == NULL) + return NULL; + + return *key_by_keysym; + +} + +/** + * Given a key which may have multiple possible definitions, returns the + * definition that currently has the lowest cost, taking into account the + * current keyboard lock and modifier states. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param key + * The key whose lowest-cost possible definition should be retrieved. + * + * @return + * A pointer to the guac_spice_keysym_desc which defines the current + * lowest-cost method of typing the given key. + */ +static const guac_spice_keysym_desc* guac_spice_keyboard_get_definition(guac_spice_keyboard* keyboard, + guac_spice_key* key) { + + /* Consistently map the same entry so long as the key is held */ + if (key->pressed != NULL) + return key->pressed; + + /* Calculate cost of first definition of key (there must always be at least + * one definition) */ + const guac_spice_keysym_desc* best_def = key->definitions[0]; + int best_cost = guac_spice_keyboard_get_cost(keyboard, best_def); + + /* If further definitions exist, choose the definition with the lowest + * overall cost */ + for (int i = 1; i < key->num_definitions; i++) { + + const guac_spice_keysym_desc* def = key->definitions[i]; + int cost = guac_spice_keyboard_get_cost(keyboard, def); + + if (cost < best_cost) { + best_def = def; + best_cost = cost; + } + + } + + return best_def; + +} + +/** + * Adds the keysym/scancode mapping described by the given guac_spice_keysym_desc + * to the internal mapping of the keyboard. If insufficient space remains for + * additional keysyms, or the given keysym has already reached the maximum + * number of possible definitions, the mapping is ignored and the failure is + * logged. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param mapping + * The keysym/scancode mapping that should be added to the given keyboard. + */ +static void guac_spice_keyboard_add_mapping(guac_spice_keyboard* keyboard, + const guac_spice_keysym_desc* mapping) { + + /* Locate corresponding keysym-to-key translation entry within keyboard + * structure */ + guac_spice_key** key_by_keysym = guac_spice_keyboard_map_key(keyboard, mapping->keysym); + if (key_by_keysym == NULL) { + guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Ignoring unmappable keysym 0x%X", mapping->keysym); + return; + } + + /* If not yet pointing to a key, point keysym-to-key translation entry at + * next available storage */ + if (*key_by_keysym == NULL) { + + if (keyboard->num_keys == GUAC_SPICE_KEYBOARD_MAX_KEYSYMS) { + guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Key definition " + "for keysym 0x%X dropped: Keymap exceeds maximum " + "supported number of keysyms", + mapping->keysym); + return; + } + + *key_by_keysym = &keyboard->keys[keyboard->num_keys++]; + + } + + guac_spice_key* key = *key_by_keysym; + + /* Add new definition only if sufficient space remains */ + if (key->num_definitions == GUAC_SPICE_KEY_MAX_DEFINITIONS) { + guac_client_log(keyboard->client, GUAC_LOG_DEBUG, "Key definition " + "for keysym 0x%X dropped: Maximum number of possible " + "definitions has been reached for this keysym", + mapping->keysym); + return; + } + + /* Store new possible definition of key */ + key->definitions[key->num_definitions++] = mapping; + +} + +/** + * Loads all keysym/scancode mappings declared within the given keymap and its + * parent keymap, if any. These mappings are stored within the given + * guac_spice_keyboard structure for future use in translating keysyms to the + * scancodes required by SPICE key events. + * + * @param keyboard + * The guac_spice_keyboard which should be initialized with the + * keysym/scancode mapping defined in the given keymap. + * + * @param keymap + * The keymap to use to populate the given client's keysym/scancode + * mapping. + */ +static void guac_spice_keyboard_load_keymap(guac_spice_keyboard* keyboard, + const guac_spice_keymap* keymap) { + + /* If parent exists, load parent first */ + if (keymap->parent != NULL) + guac_spice_keyboard_load_keymap(keyboard, keymap->parent); + + /* Log load */ + guac_client_log(keyboard->client, GUAC_LOG_INFO, + "Loading keymap \"%s\"", keymap->name); + + /* Copy mapping into keymap */ + const guac_spice_keysym_desc* mapping = keymap->mapping; + while (mapping->keysym != 0) { + guac_spice_keyboard_add_mapping(keyboard, mapping++); + } + +} + +guac_spice_keyboard* guac_spice_keyboard_alloc(guac_client* client, + const guac_spice_keymap* keymap) { + + guac_spice_keyboard* keyboard = calloc(1, sizeof(guac_spice_keyboard)); + keyboard->client = client; + + /* Load keymap into keyboard */ + guac_spice_keyboard_load_keymap(keyboard, keymap); + + return keyboard; + +} + +void guac_spice_keyboard_free(guac_spice_keyboard* keyboard) { + free(keyboard); +} + +int guac_spice_keyboard_is_defined(guac_spice_keyboard* keyboard, int keysym) { + + /* Return whether the mapping actually exists */ + return guac_spice_keyboard_get_key(keyboard, keysym) != NULL; + +} + +int guac_spice_keyboard_is_pressed(guac_spice_keyboard* keyboard, int keysym) { + + guac_spice_key* key = guac_spice_keyboard_get_key(keyboard, keysym); + return key != NULL && key->pressed != NULL; + +} + +unsigned int guac_spice_keyboard_get_modifier_flags(guac_spice_keyboard* keyboard) { + + unsigned int modifier_flags = 0; + + /* Shift */ + if (guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_LSHIFT) + || guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_RSHIFT)) + modifier_flags |= GUAC_SPICE_KEYMAP_MODIFIER_SHIFT; + + /* Dedicated AltGr key */ + if (guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_RALT) + || guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_ALTGR)) + modifier_flags |= GUAC_SPICE_KEYMAP_MODIFIER_ALTGR; + + /* AltGr via Ctrl+Alt */ + if (guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_LALT) + && (guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_RCTRL) + || guac_spice_keyboard_is_pressed(keyboard, GUAC_SPICE_KEYSYM_LCTRL))) + modifier_flags |= GUAC_SPICE_KEYMAP_MODIFIER_ALTGR; + + return modifier_flags; + +} + +/** + * Presses/releases the requested key by sending one or more SPICE key events, as + * defined within the keymap defining that key. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param key + * The guac_spice_keysym_desc of the key being pressed or released, as + * retrieved from the relevant keymap. + * + * @param pressed + * Zero if the key is being released, non-zero otherwise. + * + * @return + * Zero if the key was successfully pressed/released, non-zero if the key + * cannot be sent using SPICE key events. + */ +static const guac_spice_keysym_desc* guac_spice_keyboard_send_defined_key(guac_spice_keyboard* keyboard, + guac_spice_key* key, int pressed) { + + guac_client* client = keyboard->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + const guac_spice_keysym_desc* keysym_desc = guac_spice_keyboard_get_definition(keyboard, key); + if (keysym_desc->scancode == 0) + return NULL; + + /* Update state of required locks and modifiers only when key is just + * now being pressed */ + if (pressed) { + guac_spice_keyboard_update_locks(keyboard, + keysym_desc->set_locks, + keysym_desc->clear_locks); + + guac_spice_keyboard_update_modifiers(keyboard, + keysym_desc->set_modifiers, + keysym_desc->clear_modifiers); + } + + /* Fire actual key event for target key */ + guac_client_log(client, GUAC_LOG_TRACE, "Firing scancode event: %08x", keysym_desc->scancode); + guac_spice_send_key_event(spice_client, keysym_desc->scancode, + keysym_desc->flags, pressed); + + return keysym_desc; + +} + +void guac_spice_keyboard_update_locks(guac_spice_keyboard* keyboard, + unsigned int set_modifiers, unsigned int clear_modifiers) { + + guac_client* client = keyboard->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + /* Calculate updated lock flags */ + unsigned int modifiers = (keyboard->modifiers | set_modifiers) & ~clear_modifiers; + + /* Synchronize remote side only if lock flags have changed */ + if (modifiers != keyboard->modifiers) { + guac_spice_send_synchronize_event(spice_client, modifiers); + keyboard->modifiers = modifiers; + } + +} + +void guac_spice_keyboard_update_modifiers(guac_spice_keyboard* keyboard, + unsigned int set_flags, unsigned int clear_flags) { + + unsigned int modifier_flags = guac_spice_keyboard_get_modifier_flags(keyboard); + + /* Only clear modifiers that are set */ + clear_flags &= modifier_flags; + + /* Only set modifiers that are currently cleared */ + set_flags &= ~modifier_flags; + + /* Press/release Shift as needed */ + if (set_flags & GUAC_SPICE_KEYMAP_MODIFIER_SHIFT) { + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_LSHIFT, 1, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + } + else if (clear_flags & GUAC_SPICE_KEYMAP_MODIFIER_SHIFT) { + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_LSHIFT, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_RSHIFT, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + } + + /* Press/release AltGr as needed */ + if (set_flags & GUAC_SPICE_KEYMAP_MODIFIER_ALTGR) { + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_ALTGR, 1, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + } + else if (clear_flags & GUAC_SPICE_KEYMAP_MODIFIER_ALTGR) { + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_ALTGR, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_LALT, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_RALT, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_LCTRL, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + guac_spice_keyboard_update_keysym(keyboard, GUAC_SPICE_KEYSYM_RCTRL, 0, GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + } + +} + +int guac_spice_keyboard_update_keysym(guac_spice_keyboard* keyboard, + int keysym, int pressed, guac_spice_key_source source) { + + /* Synchronize lock keys states, if this has not yet been done */ + if (!keyboard->synchronized) { + + guac_client* client = keyboard->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + /* Synchronize remote lock key states with local state */ + guac_spice_send_synchronize_event(spice_client, keyboard->modifiers); + keyboard->synchronized = 1; + + } + + guac_spice_key* key = guac_spice_keyboard_get_key(keyboard, keysym); + + /* Update tracking of client-side keyboard state but only for keys which + * are tracked server-side, as well (to ensure that the key count remains + * correct, even if a user sends extra unbalanced or excessive press and + * release events) */ + if (source == GUAC_SPICE_KEY_SOURCE_CLIENT && key != NULL) { + if (pressed && !key->user_pressed) { + keyboard->user_pressed_keys++; + key->user_pressed = 1; + } + else if (!pressed && key->user_pressed) { + keyboard->user_pressed_keys--; + key->user_pressed = 0; + } + } + + /* Send events and update server-side lock state only if server-side key + * state is changing (or if server-side state of this key is untracked) */ + if (key == NULL || (pressed && key->pressed == NULL) || (!pressed && key->pressed != NULL)) { + + /* Toggle locks on keydown */ + if (pressed) + keyboard->modifiers ^= guac_spice_keyboard_lock_flag(keysym); + + /* If key is known, update state and attempt to send using normal SPICE key + * events */ + const guac_spice_keysym_desc* definition = NULL; + if (key != NULL) { + definition = guac_spice_keyboard_send_defined_key(keyboard, key, pressed); + key->pressed = pressed ? definition : NULL; + } + + /* Fall back to dead keys or Unicode events if otherwise undefined inside + * current keymap (note that we only handle "pressed" here, as neither + * Unicode events nor dead keys can have a pressed/released state) */ + if (definition == NULL && pressed) { + guac_client_log(keyboard->client, GUAC_LOG_WARNING, + "Undefined key will not be sent: %d", keysym); + } + + } + + /* Reset SPICE server keyboard state (releasing any automatically + * pressed keys) once all keys have been released on the client + * side */ + if (source == GUAC_SPICE_KEY_SOURCE_CLIENT && keyboard->user_pressed_keys == 0) + guac_spice_keyboard_reset(keyboard); + + return 0; + +} + +void guac_spice_keyboard_reset(guac_spice_keyboard* keyboard) { + + /* Release all pressed keys */ + for (int i = 0; i < keyboard->num_keys; i++) { + guac_spice_key* key = &keyboard->keys[i]; + if (key->pressed != NULL) + guac_spice_keyboard_update_keysym(keyboard, key->pressed->keysym, 0, + GUAC_SPICE_KEY_SOURCE_SYNTHETIC); + } + +} + +void guac_spice_keyboard_set_indicators(SpiceChannel* channel, guac_client* client) { + + guac_spice_client* spice_client = (guac_spice_client*) client->data; + + pthread_rwlock_rdlock(&(spice_client->lock)); + + /* Skip if keyboard not yet ready */ + guac_spice_keyboard* keyboard = spice_client->keyboard; + if (keyboard == NULL) + goto complete; + + unsigned int modifiers; + g_object_get(channel, SPICE_PROPERTY_KEY_MODIFIERS, &modifiers, NULL); + + /* Update with received locks */ + guac_client_log(client, GUAC_LOG_DEBUG, "Received updated keyboard lock flags from SPICE server: 0x%X", modifiers); + keyboard->modifiers = modifiers; + +complete: + pthread_rwlock_unlock(&(spice_client->lock)); + +} diff --git a/src/protocols/spice/keyboard.h b/src/protocols/spice/keyboard.h new file mode 100644 index 00000000..bdeca7ba --- /dev/null +++ b/src/protocols/spice/keyboard.h @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_KEYBOARD_H +#define GUAC_SPICE_KEYBOARD_H + +#include "keymap.h" + +#include +#include + +/** + * The maximum number of distinct keysyms that any particular keyboard may support. + */ +#define GUAC_SPICE_KEYBOARD_MAX_KEYSYMS 1024 + +/** + * The maximum number of unique modifier variations that any particular keysym + * may define. For example, on a US English keyboard, an uppercase "A" may be + * typed by pressing Shift+A with Caps Lock unset, or by pressing A with Caps + * Lock set (two variations). + */ +#define GUAC_SPICE_KEY_MAX_DEFINITIONS 4 + +/** + * All possible sources of SPICE key events tracked by guac_spice_keyboard. + */ +typedef enum guac_spice_key_source { + + /** + * The key event was received directly from the Guacamole client via a + * "key" instruction. + */ + GUAC_SPICE_KEY_SOURCE_CLIENT = 0, + + /** + * The key event is being synthesized internally within the SPICE support. + */ + GUAC_SPICE_KEY_SOURCE_SYNTHETIC = 1 + +} guac_spice_key_source; + +/** + * A representation of a single key within the overall local keyboard, + * including the definition of that key within the SPICE server's keymap and + * whether the key is currently pressed locally. + */ +typedef struct guac_spice_key { + + /** + * All definitions of this key within the SPICE server's keymap (keyboard + * layout). Each definition describes which scancode corresponds to this + * key from the perspective of the SPICE server, as well as which other + * scancodes must be pressed/released for this key to have the desired + * meaning. + */ + const guac_spice_keysym_desc* definitions[GUAC_SPICE_KEY_MAX_DEFINITIONS]; + + /** + * The number of definitions within the definitions array. If this key does + * not exist within the SPICE server's keymap, this will be 0. + */ + int num_definitions; + + /** + * The definition of this key that is currently pressed. If this key is not + * currently pressed, this will be NULL. + */ + const guac_spice_keysym_desc* pressed; + + /** + * Whether this key is currently pressed by the user, and is included among + * the total tracked by user_pressed_keys within guac_spice_keyboard. + */ + int user_pressed; + +} guac_spice_key; + +/** + * The current keyboard state of an SPICE session. + */ +typedef struct guac_spice_keyboard { + + /** + * The guac_client associated with the SPICE session whose keyboard state is + * being managed by this guac_spice_keyboard. + */ + guac_client* client; + + /** + * The local state of all known lock keys, as a bitwise OR of all SPICE lock + * key flags. Legal flags are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, + * KBD_SYNC_CAPS_LOCK, and KBD_SYNC_KANA_LOCK. + */ + int modifiers; + + /** + * Whether the states of remote lock keys (Caps lock, Num lock, etc.) have + * been synchronized with local lock key states. + */ + int synchronized; + + /** + * The number of keys stored within the keys array. + */ + unsigned int num_keys; + + /** + * The local state of all keys, as well as the necessary information to + * translate received keysyms into scancodes or sequences of scancodes for + * SPICE. The state of each key is updated based on received Guacamole key + * events, while the information describing the behavior and scancode + * mapping of each key is populated based on an associated keymap. + * + * Keys within this array are in arbitrary order. + */ + guac_spice_key keys[GUAC_SPICE_KEYBOARD_MAX_KEYSYMS]; + + /** + * Lookup table into the overall keys array, locating the guac_spice_key + * associated with any particular keysym. If a keysym has no corresponding + * guac_spice_key within the keys array, its entry within this lookuptable + * will be NULL. + * + * The index of the key for a given keysym is determined based on a + * simple transformation of the keysym itself. Keysyms between 0x0000 and + * 0xFFFF inclusive are mapped to 0x00000 through 0x0FFFF, while keysyms + * between 0x1000000 and 0x100FFFF inclusive (keysyms which are derived + * from Unicode) are mapped to 0x10000 through 0x1FFFF. + */ + guac_spice_key* keys_by_keysym[0x20000]; + + /** + * The total number of keys that the user of the connection is currently + * holding down. This value indicates only the client-side keyboard state. + * It DOES NOT indicate the number of keys currently pressed within the SPICE + * server. + */ + int user_pressed_keys; + +} guac_spice_keyboard; + +/** + * Allocates a new guac_spice_keyboard which manages the keyboard state of the + * SPICE session associated with the given guac_client. Keyboard events will be + * dynamically translated from keysym to SPICE scancode according to the + * provided keymap. The returned guac_spice_keyboard must eventually be freed + * with guac_spice_keyboard_free(). + * + * @param client + * The guac_client associated with the SPICE session whose keyboard state is + * to be managed by the newly-allocated guac_spice_keyboard. + * + * @param keymap + * The keymap which should be used to translate keyboard events. + * + * @return + * A newly-allocated guac_spice_keyboard which manages the keyboard state + * for the SPICE session associated given guac_client. + */ +guac_spice_keyboard* guac_spice_keyboard_alloc(guac_client* client, + const guac_spice_keymap* keymap); + +/** + * Frees all memory allocated for the given guac_spice_keyboard. The + * guac_spice_keyboard must have been previously allocated via + * guac_spice_keyboard_alloc(). + * + * @param keyboard + * The guac_spice_keyboard instance which should be freed. + */ +void guac_spice_keyboard_free(guac_spice_keyboard* keyboard); + +/** + * Returns whether the given keysym is defined for the keyboard layout + * associated with the given keyboard. + * + * @param keyboard + * The guac_spice_keyboard instance to check. + * + * @param keysym + * The keysym of the key being checked against the keyboard layout of the + * given keyboard. + * + * @return + * Non-zero if the key is explicitly defined within the keyboard layout of + * the given keyboard, zero otherwise. + */ +int guac_spice_keyboard_is_defined(guac_spice_keyboard* keyboard, int keysym); + +/** + * Returns whether the key having the given keysym is currently pressed. + * + * @param keyboard + * The guac_spice_keyboard instance to check. + * + * @param keysym + * The keysym of the key being checked. + * + * @return + * Non-zero if the key is currently pressed, zero otherwise. + */ +int guac_spice_keyboard_is_pressed(guac_spice_keyboard* keyboard, int keysym); + +/** + * Returns the local state of all known modifier keys, as a bitwise OR of the + * modifier flags used by the keymaps. Alternative methods of producing the + * effect of certain modifiers, such as holding Ctrl+Alt for AltGr when a + * dedicated AltGr key is unavailable, are taken into account. + * + * @see GUAC_SPICE_KEYMAP_MODIFIER_SHIFT + * @see GUAC_SPICE_KEYMAP_MODIFIER_ALTGR + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @return + * The local state of all known modifier keys. + */ +unsigned int guac_spice_keyboard_get_modifier_flags(guac_spice_keyboard* keyboard); + +/** + * Updates the local state of the lock keys (such as Caps lock or Num lock), + * synchronizing the remote state of those keys if it is expected to differ. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param set_modifiers + * The lock key flags which should be set. Legal flags are + * KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + * + * @param clear_modifiers + * The lock key flags which should be cleared. Legal flags are + * KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ +void guac_spice_keyboard_update_locks(guac_spice_keyboard* keyboard, + unsigned int set_modifiers, unsigned int clear_modifiers); + +/** + * Updates the local state of the modifier keys (such as Shift or AltGr), + * synchronizing the remote state of those keys if it is expected to differ. + * Valid modifier flags are defined by keymap.h. + * + * @see GUAC_SPICE_KEYMAP_MODIFIER_SHIFT + * @see GUAC_SPICE_KEYMAP_MODIFIER_ALTGR + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param set_modifiers + * The modifier key flags which should be set. + * + * @param clear_modifiers + * The modifier key flags which should be cleared. + */ +void guac_spice_keyboard_update_modifiers(guac_spice_keyboard* keyboard, + unsigned int set_modifiers, unsigned int clear_modifiers); + +/** + * Updates the local state of the given keysym, sending the key events required + * to replicate that state remotely (on the SPICE server). The key events sent + * will depend on the current keymap. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + * + * @param keysym + * The keysym being pressed or released. + * + * @param pressed + * Zero if the keysym is being released, non-zero otherwise. + * + * @param source + * The source of the key event represented by this call to + * guac_spice_keyboard_update_keysym(). + * + * @return + * Zero if the keys were successfully sent, non-zero otherwise. + */ +int guac_spice_keyboard_update_keysym(guac_spice_keyboard* keyboard, + int keysym, int pressed, guac_spice_key_source source); + +/** + * Releases all currently pressed keys, sending key release events to the SPICE + * server as necessary. Lock states (Caps Lock, etc.) are not affected. + * + * @param keyboard + * The guac_spice_keyboard associated with the current SPICE session. + */ +void guac_spice_keyboard_reset(guac_spice_keyboard* keyboard); + +/** + * Callback which is invoked when the SPICE server reports changes to keyboard + * lock status using a Server Set Keyboard Indicators PDU. + * + * @param channel + * The spiceContext associated with the current SPICE session. + * + * @param client + * The guac_client object associated with the callback. + */ +void guac_spice_keyboard_set_indicators(SpiceChannel* channel, guac_client* client); + +#endif \ No newline at end of file diff --git a/src/protocols/spice/keymap.c b/src/protocols/spice/keymap.c new file mode 100644 index 00000000..51b75a78 --- /dev/null +++ b/src/protocols/spice/keymap.c @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "keymap.h" + +#include + +const guac_spice_keymap* guac_spice_keymap_find(const char* name) { + + /* For each keymap */ + const guac_spice_keymap** current = GUAC_SPICE_KEYMAPS; + while (*current != NULL) { + + /* If name matches, done */ + if (strcmp((*current)->name, name) == 0) + return *current; + + current++; + } + + /* Failure */ + return NULL; + +} + diff --git a/src/protocols/spice/keymap.h b/src/protocols/spice/keymap.h new file mode 100644 index 00000000..58dc4111 --- /dev/null +++ b/src/protocols/spice/keymap.h @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_KEYMAP_H +#define GUAC_SPICE_KEYMAP_H + +/** + * The X11 keysym for Num Lock. + */ +#define GUAC_SPICE_KEYSYM_NUM_LOCK 0xFF7F + +/** + * The X11 keysym for Scroll Lock. + */ +#define GUAC_SPICE_KEYSYM_SCROLL_LOCK 0xFF14 + +/** + * The X11 keysym for Caps Lock. + */ +#define GUAC_SPICE_KEYSYM_CAPS_LOCK 0xFFE5 + +/** + * The X11 keysym for Kana Lock. + */ +#define GUAC_SPICE_KEYSYM_KANA_LOCK 0xFF2D + +/** + * The X11 keysym for Left Shift. + */ +#define GUAC_SPICE_KEYSYM_LSHIFT 0xFFE1 + +/** + * The X11 keysym for Right Shift. + */ +#define GUAC_SPICE_KEYSYM_RSHIFT 0xFFE2 + +/** + * The X11 keysym for Left Ctrl. + */ +#define GUAC_SPICE_KEYSYM_LCTRL 0xFFE3 + +/** + * The X11 keysym for Right Ctrl. + */ +#define GUAC_SPICE_KEYSYM_RCTRL 0xFFE4 + +/** + * The X11 keysym for Left Alt. + */ +#define GUAC_SPICE_KEYSYM_LALT 0xFFE9 + +/** + * The X11 keysym for Right Alt. + */ +#define GUAC_SPICE_KEYSYM_RALT 0xFFEA + +/** + * The X11 keysym for AltGr. + */ +#define GUAC_SPICE_KEYSYM_ALTGR 0xFE03 + +/** + * Bitwise flag value representing the Shift modifier. + */ +#define GUAC_SPICE_KEYMAP_MODIFIER_SHIFT 1 + +/** + * Bitwise flag value representing the AltGr modifier. + */ +#define GUAC_SPICE_KEYMAP_MODIFIER_ALTGR 2 + +/** + * Represents a keysym-to-scancode mapping for SPICE, with extra information + * about the state of prerequisite keysyms. + */ +typedef struct guac_spice_keysym_desc { + + /** + * The keysym being mapped. + */ + int keysym; + + /** + * The scancode this keysym maps to. + */ + int scancode; + + /** + * Required SPICE-specific flags that must be sent along with the scancode. + */ + int flags; + + /** + * Bitwise-OR of the flags of any modifiers that must be active for the + * associated scancode to be interpreted as this keysym. + * + * If the associated keysym is pressed, and any of these modifiers are not + * currently active, Guacamole's SPICE support must send additional events + * to activate these modifiers prior to sending the scancode for this + * keysym. + * + * @see GUAC_SPICE_KEYMAP_MODIFIER_SHIFT + * @see GUAC_SPICE_KEYMAP_MODIFIER_ALTGR + */ + const unsigned int set_modifiers; + + /** + * Bitwise-OR of the flags of any modifiers that must NOT be active for the + * associated scancode to be interpreted as this keysym. + * + * If the associated keysym is pressed, and any of these modifiers are + * currently active, Guacamole's SPICE support must send additional events + * to deactivate these modifiers prior to sending the scancode for this + * keysym. + * + * @see GUAC_SPICE_KEYMAP_MODIFIER_SHIFT + * @see GUAC_SPICE_KEYMAP_MODIFIER_ALTGR + */ + const unsigned int clear_modifiers; + + /** + * Bitwise OR of the flags of all lock keys (ie: Caps lock, Num lock, etc.) + * which must be active for this keysym to be properly typed. Legal flags + * are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ + const unsigned int set_locks; + + /** + * Bitwise OR of the flags of all lock keys (ie: Caps lock, Num lock, etc.) + * which must be inactive for this keysym to be properly typed. Legal flags + * are KBD_SYNC_SCROLL_LOCK, KBD_SYNC_NUM_LOCK, KBD_SYNC_CAPS_LOCK, and + * KBD_SYNC_KANA_LOCK. + */ + const unsigned int clear_locks; + +} guac_spice_keysym_desc; + +/** + * Hierarchical keysym mapping + */ +typedef struct guac_spice_keymap guac_spice_keymap; +struct guac_spice_keymap { + + /** + * The parent mapping this map will inherit its initial mapping from. + * Any other mapping information will add to or override the mapping + * inherited from the parent. + */ + const guac_spice_keymap* parent; + + /** + * Descriptive name of this keymap + */ + const char* name; + + /** + * Null-terminated array of scancode mappings. + */ + const guac_spice_keysym_desc* mapping; + +}; + +/** + * The name of the default keymap, which MUST exist. + */ +#define GUAC_SPICE_DEFAULT_KEYMAP "en-us-qwerty" + +/** + * NULL-terminated array of all keymaps. + */ +extern const guac_spice_keymap* GUAC_SPICE_KEYMAPS[]; + +/** + * Return the keymap having the given name, if any, or NULL otherwise. + * + * @param name + * The name of the keymap to find. + * + * @return + * The keymap having the given name, or NULL if no such keymap exists. + */ +const guac_spice_keymap* guac_spice_keymap_find(const char* name); + +#endif + diff --git a/src/protocols/spice/keymaps/base.keymap b/src/protocols/spice/keymaps/base.keymap new file mode 100644 index 00000000..5b93fab6 --- /dev/null +++ b/src/protocols/spice/keymaps/base.keymap @@ -0,0 +1,93 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +name "base" + +# Typeable characters +map 0x39 ~ " " # Space +map 0x0F ~ 0xff09 # Tab + +# Control characters +map 0x0E ~ 0xff08 # BackSpace +map 0x1C ~ 0xff0d # Return +map 0x01 ~ 0xff1b # Escape +map +ext 0x52 ~ 0xff63 # Insert +map +ext 0x53 ~ 0xffff # Delete +map +ext 0x47 ~ 0xff50 # Home +map +ext 0x4F ~ 0xff57 # End +map +ext 0x4B ~ 0xff51 # Left +map +ext 0x48 ~ 0xff52 # Up +map +ext 0x4D ~ 0xff53 # Right +map +ext 0x50 ~ 0xff54 # Down +map +ext 0x49 ~ 0xff55 # Page_Up +map +ext 0x51 ~ 0xff56 # Page_Down +map +ext 0x37 ~ 0xff61 # Print Screen + +# Locks +map 0x45 ~ 0xff7f # Num_Lock +map 0x46 ~ 0xff14 # Scroll_Lock +map 0x3A ~ 0xffe5 # Caps_Lock + +# Keypad numerals +map -shift +num 0x52 ~ 0xffb0 # KP_0 +map -shift +num 0x4F ~ 0xffb1 # KP_1 +map -shift +num 0x50 ~ 0xffb2 # KP_2 +map -shift +num 0x51 ~ 0xffb3 # KP_3 +map -shift +num 0x4B ~ 0xffb4 # KP_4 +map -shift +num 0x4C ~ 0xffb5 # KP_5 +map -shift +num 0x4D ~ 0xffb6 # KP_6 +map -shift +num 0x47 ~ 0xffb7 # KP_7 +map -shift +num 0x48 ~ 0xffb8 # KP_8 +map -shift +num 0x49 ~ 0xffb9 # KP_9 + +# Keypad operators +map 0x37 ~ 0xffaa # KP_multiply +map 0x4e ~ 0xffab # KP_add +map 0x4a ~ 0xffad # KP_subtract +map 0x53 ~ 0xffae # KP_decimal +map +ext 0x35 ~ 0xffaf # KP_divide + +# F keys +map 0x3B ~ 0xffbe # F1 +map 0x3C ~ 0xffbf # F2 +map 0x3D ~ 0xffc0 # F3 +map 0x3E ~ 0xffc1 # F4 +map 0x3F ~ 0xffc2 # F5 +map 0x40 ~ 0xffc3 # F6 +map 0x41 ~ 0xffc4 # F7 +map 0x42 ~ 0xffc5 # F8 +map 0x43 ~ 0xffc6 # F9 +map 0x44 ~ 0xffc7 # F10 +map 0x57 ~ 0xffc8 # F11 +map 0x58 ~ 0xffc9 # F12 + +# Modifiers +map 0x2A ~ 0xffe1 # Shift_L +map 0x36 ~ 0xffe2 # Shift_R +map 0x1D ~ 0xffe3 # Control_L +map +ext 0x1D ~ 0xffe4 # Control_R +map 0x38 ~ 0xffe9 # Alt_L +map +ext 0x38 ~ 0xffea # Alt_R +map +ext 0x38 ~ 0xfe03 # AltGr +map +ext 0x5B ~ 0xffe7 # Meta_L +map +ext 0x5C ~ 0xffe8 # Meta_R +map +ext 0x5B ~ 0xffeb # Super_L +map +ext 0x5C ~ 0xffec # Super_R +map +ext 0x5D ~ 0xff67 # Menu + diff --git a/src/protocols/spice/keymaps/da_dk_qwerty.keymap b/src/protocols/spice/keymaps/da_dk_qwerty.keymap new file mode 100644 index 00000000..07611e88 --- /dev/null +++ b/src/protocols/spice/keymaps/da_dk_qwerty.keymap @@ -0,0 +1,73 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "da-dk-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890+" +map -caps -altgr -shift 0x10..0x1A ~ "qwertyuiopå" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklæø'" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890+" +map +caps -altgr -shift 0x10..0x1A ~ "QWERTYUIOPÅ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÆØ'" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x03 ~ "@" +map +altgr -shift 0x04 ~ "£" +map +altgr -shift 0x05 ~ "$" +map +altgr -shift 0x08 ~ "{" +map +altgr -shift 0x09 ~ "[" +map +altgr -shift 0x0A ~ "]" +map +altgr -shift 0x0B ~ "}" +map +altgr -shift 0x56 ~ "\" + +map +altgr -shift 0x12 ~ "€" + +map +altgr -shift 0x0D ~ "|" +map +altgr -shift 0x32 ~ "µ" + +# +# Dead keys +# + +map -altgr -shift 0x0D ~ 0xFE51 # Dead acute +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut +map -altgr +shift 0x1B ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde diff --git a/src/protocols/spice/keymaps/de_ch_qwertz.keymap b/src/protocols/spice/keymaps/de_ch_qwertz.keymap new file mode 100644 index 00000000..a4ba4a1c --- /dev/null +++ b/src/protocols/spice/keymaps/de_ch_qwertz.keymap @@ -0,0 +1,67 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "de-ch-qwertz" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890'" +map -caps -altgr -shift 0x10..0x1A ~ "qwertzuiopü" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklöä$" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "YXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890'" +map +caps -altgr -shift 0x10..0x1A ~ "QWERTZUIOPÜ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÖÄ$" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "yxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02..0x04 ~ "¦@#" +map +altgr -shift 0x07..0x09 ~ "¬|¢" +map +altgr -shift 0x1A..0x1B ~ "[]" +map +altgr -shift 0x28 ~ "{" +map +altgr -shift 0x2B ~ "}" +map +altgr -shift 0x56 ~ "\" +map +altgr -shift 0x12 ~ "€" + +# +# Dead keys +# + +map +altgr -shift 0x0C ~ 0xFE51 # Dead acute +map -altgr -shift 0x0D ~ 0xFE52 # Dead circumflex +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map +altgr -shift 0x0D ~ 0xFE53 # Dead tilde +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut diff --git a/src/protocols/spice/keymaps/de_de_qwertz.keymap b/src/protocols/spice/keymaps/de_de_qwertz.keymap new file mode 100644 index 00000000..bf7a0aaf --- /dev/null +++ b/src/protocols/spice/keymaps/de_de_qwertz.keymap @@ -0,0 +1,74 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "de-de-qwertz" + +# +# Basic keys +# + +map -caps -altgr -shift 0x02..0x0C ~ "1234567890ß" +map -caps -altgr -shift 0x10..0x1B ~ "qwertzuiopü+" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklöä#" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "YXCVBNM;:_" + +map +caps -altgr -shift 0x02..0x0C ~ "!"§$%&/()=?" +map +caps -altgr -shift 0x10..0x1B ~ "QWERTZUIOPÜ*" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÖÄ'" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "yxcvbnm,._" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x03 ~ "²" +map +altgr -shift 0x04 ~ "³" +map +altgr -shift 0x08 ~ "{" +map +altgr -shift 0x09 ~ "[" +map +altgr -shift 0x0A ~ "]" +map +altgr -shift 0x0B ~ "}" +map +altgr -shift 0x0C ~ "\" + +map +altgr -shift 0x10 ~ "@" +map +altgr -shift 0x12 ~ "€" +map +altgr -shift 0x1B ~ "~" + +map +altgr -shift 0x56 ~ "|" +map +altgr -shift 0x32 ~ "µ" + +# +# Dead keys +# + +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map -altgr -shift 0x0D ~ 0xFE51 # Dead acute +map -altgr -shift 0x29 ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x0C ~ 0xFE53 # Dead tilde + diff --git a/src/protocols/spice/keymaps/en_gb_qwerty.keymap b/src/protocols/spice/keymaps/en_gb_qwerty.keymap new file mode 100644 index 00000000..c14485b2 --- /dev/null +++ b/src/protocols/spice/keymaps/en_gb_qwerty.keymap @@ -0,0 +1,79 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "en-gb-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map -caps -altgr -shift 0x10..0x1B ~ "qwertyuiop[]" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjkl;'#" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "\zxcvbnm,./" + +map -caps -altgr +shift 0x29 0x02..0x0D ~ "¬!"£$%^&*()_+" +map -caps -altgr +shift 0x10..0x1B ~ "QWERTYUIOP{}" +map -caps -altgr +shift 0x1E..0x28 0x2B ~ "ASDFGHJKL:@~" +map -caps -altgr +shift 0x56 0x2C..0x35 ~ "|ZXCVBNM<>?" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map +caps -altgr -shift 0x10..0x1B ~ "QWERTYUIOP[]" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKL;'#" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "\ZXCVBNM,./" + +map +caps -altgr +shift 0x29 0x02..0x0D ~ "¬!"£$%^&*()_+" +map +caps -altgr +shift 0x10..0x1B ~ "qwertyuiop{}" +map +caps -altgr +shift 0x1E..0x28 0x2B ~ "asdfghjkl:@~" +map +caps -altgr +shift 0x56 0x2C..0x35 ~ "|zxcvbnm<>?" + +# +# Keys requiring AltGr (some of which are affected by Caps Lock) +# + +map +altgr -shift 0x29 ~ "¦" +map +altgr -shift 0x05 ~ "€" + +map -caps +altgr -shift 0x12 ~ "é" +map -caps +altgr +shift 0x12 ~ "É" +map -caps +altgr -shift 0x16 ~ "ú" +map -caps +altgr +shift 0x16 ~ "Ú" +map -caps +altgr -shift 0x17 ~ "í" +map -caps +altgr +shift 0x17 ~ "Í" +map -caps +altgr -shift 0x18 ~ "ó" +map -caps +altgr +shift 0x18 ~ "Ó" +map -caps +altgr -shift 0x1E ~ "á" +map -caps +altgr +shift 0x1E ~ "Á" +map -caps +altgr -shift 0x2E ~ "ç" +map -caps +altgr +shift 0x2E ~ "Ç" + +map +caps +altgr +shift 0x12 ~ "é" +map +caps +altgr -shift 0x12 ~ "É" +map +caps +altgr +shift 0x16 ~ "ú" +map +caps +altgr -shift 0x16 ~ "Ú" +map +caps +altgr +shift 0x17 ~ "í" +map +caps +altgr -shift 0x17 ~ "Í" +map +caps +altgr +shift 0x18 ~ "ó" +map +caps +altgr -shift 0x18 ~ "Ó" +map +caps +altgr +shift 0x1E ~ "á" +map +caps +altgr -shift 0x1E ~ "Á" +map +caps +altgr +shift 0x2E ~ "ç" +map +caps +altgr -shift 0x2E ~ "Ç" + diff --git a/src/protocols/spice/keymaps/en_us_qwerty.keymap b/src/protocols/spice/keymaps/en_us_qwerty.keymap new file mode 100644 index 00000000..8af29e31 --- /dev/null +++ b/src/protocols/spice/keymaps/en_us_qwerty.keymap @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "en-us-qwerty" + +map -caps -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map -caps -shift 0x10..0x1B 0x2B ~ "qwertyuiop[]\" +map -caps -shift 0x1E..0x28 ~ "asdfghjkl;'" +map -caps -shift 0x2C..0x35 ~ "zxcvbnm,./" + +map -caps +shift 0x29 0x02..0x0D ~ "~!@#$%^&*()_+" +map -caps +shift 0x10..0x1B 0x2B ~ "QWERTYUIOP{}|" +map -caps +shift 0x1E..0x28 ~ "ASDFGHJKL:"" +map -caps +shift 0x2C..0x35 ~ "ZXCVBNM<>?" + +map +caps -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map +caps -shift 0x10..0x1B 0x2B ~ "QWERTYUIOP[]\" +map +caps -shift 0x1E..0x28 ~ "ASDFGHJKL;'" +map +caps -shift 0x2C..0x35 ~ "ZXCVBNM,./" + +map +caps +shift 0x29 0x02..0x0D ~ "~!@#$%^&*()_+" +map +caps +shift 0x10..0x1B 0x2B ~ "qwertyuiop{}|" +map +caps +shift 0x1E..0x28 ~ "asdfghjkl:"" +map +caps +shift 0x2C..0x35 ~ "zxcvbnm<>?" + diff --git a/src/protocols/spice/keymaps/es_es_qwerty.keymap b/src/protocols/spice/keymaps/es_es_qwerty.keymap new file mode 100644 index 00000000..fa00104f --- /dev/null +++ b/src/protocols/spice/keymaps/es_es_qwerty.keymap @@ -0,0 +1,63 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "es-es-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "º1234567890'¡" +map -caps -altgr -shift 0x10..0x19 0x1B ~ "qwertyuiop+" +map -caps -altgr -shift 0x1E..0x27 0x2B ~ "asdfghjklñç" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "º1234567890'¡" +map +caps -altgr -shift 0x10..0x19 0x1B ~ "QWERTYUIOP+" +map +caps -altgr -shift 0x1E..0x27 0x2B ~ "ASDFGHJKLÑÇ" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x29 0x02..0x04 0x07 ~ "\|@#¬" +map +altgr -shift 0x12 0x1A 0x1B ~ "€[]" +map +altgr -shift 0x28 0x2B ~ "{}" + +# +# Dead keys +# + +map -altgr -shift 0x1A ~ 0xFE50 # Dead grave +map -altgr -shift 0x28 ~ 0xFE51 # Dead acute +map -altgr +shift 0x1A ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x05 ~ 0xFE53 # Dead tilde +map -altgr +shift 0x28 ~ 0xFE57 # Dead diaeresis (umlaut) diff --git a/src/protocols/spice/keymaps/es_latam_qwerty.keymap b/src/protocols/spice/keymaps/es_latam_qwerty.keymap new file mode 100644 index 00000000..260f94d5 --- /dev/null +++ b/src/protocols/spice/keymaps/es_latam_qwerty.keymap @@ -0,0 +1,63 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "es-latam-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "|1234567890'¿" +map -caps -altgr -shift 0x10..0x19 0x1B ~ "qwertyuiop+" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklñ{}" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "|1234567890'¿" +map +caps -altgr -shift 0x10..0x19 0x1B ~ "QWERTYUIOP+" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÑ{}" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x29 0x0C ~ "¬\" +map +altgr -shift 0x10 ~ "€" +map +altgr -shift 0x28 ~ "^" + +# +# Dead keys +# + +map +altgr -shift 0x2B ~ 0xFE50 # Dead grave +map -altgr -shift 0x1A ~ 0xFE51 # Dead acute +map -altgr +shift 0x1A ~ 0xFE57 # Dead diaeresis (umlaut) +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde + diff --git a/src/protocols/spice/keymaps/failsafe.keymap b/src/protocols/spice/keymaps/failsafe.keymap new file mode 100644 index 00000000..76625ca7 --- /dev/null +++ b/src/protocols/spice/keymaps/failsafe.keymap @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "failsafe" + diff --git a/src/protocols/spice/keymaps/fr_be_azerty.keymap b/src/protocols/spice/keymaps/fr_be_azerty.keymap new file mode 100644 index 00000000..0a5b3848 --- /dev/null +++ b/src/protocols/spice/keymaps/fr_be_azerty.keymap @@ -0,0 +1,76 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "fr-be-azerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "²&é"'(§è!çà)-" +map -caps -altgr -shift 0x10..0x19 0x1B ~ "azertyuiop$" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "qsdfghjklmùµ" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "WXCVBN?./+" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "²1234567890°_" +map +caps -altgr -shift 0x10..0x19 0x1B ~ "AZERTYUIOP£" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "QSDFGHJKLM%£" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "wxcvbn,;:=" + +# +# Keys requiring AltGr (unaffected by Caps Lock, but Shift must not be pressed) +# + +map +altgr -shift 0x02..0x04 ~ "|@#" +map +altgr -shift 0x0A..0x0B ~ "{}" +map +altgr -shift 0x1A..0x1B ~ "[]" + +map +altgr -shift 0x12 ~ "€" +map +altgr -shift 0x56 ~ "\" +map +altgr -shift 0x07 ~ "^" + +# +# Dead keys requiring AltGr (unaffected by Caps Lock or Shift) +# + +map +altgr 0x35 ~ 0xFE53 # Dead tilde +map +altgr 0x28 ~ 0xFE51 # Dead acute +map +altgr 0x2B ~ 0xFE50 # Dead grave + +# +# Dead keys (affected by Caps Lock and Shift) +# + +map -caps -altgr -shift 0x1A ~ 0xFE52 # Dead circumflex +map -caps -altgr +shift 0x1A ~ 0xFE57 # Dead umlaut + +map +caps -altgr -shift 0x1A ~ 0xFE57 # Dead umlaut +map +caps -altgr +shift 0x1A ~ 0xFE52 # Dead circumflex + diff --git a/src/protocols/spice/keymaps/fr_ca_qwerty.keymap b/src/protocols/spice/keymaps/fr_ca_qwerty.keymap new file mode 100644 index 00000000..6f4a912b --- /dev/null +++ b/src/protocols/spice/keymaps/fr_ca_qwerty.keymap @@ -0,0 +1,54 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "fr-ca-qwerty" + +# +# Basic keys +# + +map -altgr -shift 0x29 0x02..0x0D ~ "#1234567890-=" +map -altgr -shift 0x10..0x1B 0x2B ~ "qwertyuiop^¸<" +map -altgr -shift 0x1E..0x28 ~ "asdfghjkl;`" +map -altgr -shift 0x2C..0x35 ~ "zxcvbnm,.é" + +map -altgr +shift 0x29 0x02..0x0D ~ "|!"/$%?&*()_+" +map -altgr +shift 0x10..0x1B 0x2B ~ "QWERTYUIOP^¨>" +map -altgr +shift 0x1E..0x28 ~ "ASDFGHJKL:`" +map -altgr +shift 0x2C..0x35 ~ "ZXCVBNM'.É" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x29 0x02..0x0D ~ "\±@£¢¤¬¦²³¼½¾" +map +altgr -shift 0x18..0x1B 0x2B ~ "§¶[]}" +map +altgr -shift 0x27..0x28 ~ "~{" +map +altgr -shift 0x32..0x33 0x35 ~ "µ¯´" + +# +# Combined accents +# + +map -altgr -shift 0x1A ~ 0x0302 # COMBINING CIRCUMFLEX ACCENT +map -altgr -shift 0x1B ~ 0x0327 # COMBINING CEDILLA +map -altgr +shift 0x1B ~ 0x0308 # COMBINING DIAERESIS +map -altgr -shift 0x28 ~ 0x0300 # COMBINING GRAVE ACCENT +map +altgr -shift 0x35 ~ 0x0301 # COMBINING ACUTE ACCENT diff --git a/src/protocols/spice/keymaps/fr_ch_qwertz.keymap b/src/protocols/spice/keymaps/fr_ch_qwertz.keymap new file mode 100644 index 00000000..bd413871 --- /dev/null +++ b/src/protocols/spice/keymaps/fr_ch_qwertz.keymap @@ -0,0 +1,68 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "fr-ch-qwertz" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890'" +map -caps -altgr -shift 0x10..0x1A ~ "qwertzuiopè" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjkléà$" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "YXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890'" +map +caps -altgr -shift 0x10..0x1A ~ "QWERTZUIOPè" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLéà$" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "yxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02..0x04 ~ "¦@#" +map +altgr -shift 0x07..0x09 ~ "¬|¢" +map +altgr -shift 0x1A 0x1B ~ "[]" +map +altgr -shift 0x28 0x2B ~ "{}" +map +altgr -shift 0x56 ~ "\" + +map +altgr -shift 0x12 ~ "€" + +# +# Dead keys +# + +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut +map +altgr -shift 0x0C ~ 0xFE51 # Dead acute +map -altgr -shift 0x0D ~ 0xFE52 # Dead circumflex +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map +altgr -shift 0x0D ~ 0xFE53 # Dead tilde + diff --git a/src/protocols/spice/keymaps/fr_fr_azerty.keymap b/src/protocols/spice/keymaps/fr_fr_azerty.keymap new file mode 100644 index 00000000..09101a7f --- /dev/null +++ b/src/protocols/spice/keymaps/fr_fr_azerty.keymap @@ -0,0 +1,65 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "fr-fr-azerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "²&é"'(-è_çà)=" +map -caps -altgr -shift 0x10..0x19 0x1B ~ "azertyuiop$" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "qsdfghjklmù*" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "WXCVBN?./§" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "²1234567890°+" +map +caps -altgr -shift 0x10..0x19 0x1B ~ "AZERTYUIOP£" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "QSDFGHJKLM%µ" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "wxcvbn,;:!" + +# +# Keys requiring AltGr (unaffected by Caps Lock, but Shift must not be pressed) +# + +map +altgr -shift 0x03..0x0D ~ "~#{[|`\^@]}" + +map +altgr -shift 0x12 ~ "€" +map +altgr -shift 0x1B ~ "¤" + +# +# Dead keys (affected by Caps Lock and Shift) +# + +map -caps -altgr -shift 0x1A ~ 0xFE52 # Dead circumflex +map -caps -altgr +shift 0x1A ~ 0xFE57 # Dead umlaut + +map +caps -altgr -shift 0x1A ~ 0xFE57 # Dead umlaut +map +caps -altgr +shift 0x1A ~ 0xFE52 # Dead circumflex + diff --git a/src/protocols/spice/keymaps/generate.pl b/src/protocols/spice/keymaps/generate.pl new file mode 100755 index 00000000..b6f1fe56 --- /dev/null +++ b/src/protocols/spice/keymaps/generate.pl @@ -0,0 +1,253 @@ +#!/usr/bin/env perl +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# generate.pl +# +# Parse .keymap files, producing corresponding .c files that can be included +# into the Spice plugin source. +# + +# We need at least Perl 5.8 for Unicode's sake +use 5.008; + +sub keymap_symbol { + my $name = shift; + $name =~ s/-/_/g; + return 'guac_spice_keymap_' . $name; +} + +# +# _generated_keymaps.c +# + +my @keymaps = (); + +open OUTPUT, ">", "_generated_keymaps.c"; +print OUTPUT + '#include "config.h"' . "\n" + . '#include "keymap.h"' . "\n" + . "\n" + . '#include ' . "\n" + . '#include ' . "\n" + . "\n"; + +for my $filename (@ARGV) { + + my $content = ""; + my $parent = ""; + my $layout_name = ""; + + # Parse file + open INPUT, '<', "$filename"; + binmode INPUT, ":encoding(utf8)"; + while () { + + chomp; + + # Comments + if (m/^\s*#/) {} + + # Name + elsif ((my $name) = m/^\s*name\s+"(.*)"\s*(?:#.*)?$/) { + $layout_name = $name; + } + + # Parent map + elsif ((my $name) = m/^\s*parent\s+"(.*)"\s*(?:#.*)?$/) { + $parent = keymap_symbol($name); + } + + # Map + elsif ((my $range, my $onto) = + m/^\s*map\s+([^~]*)\s+~\s+(".*"|0x[0-9A-Fa-f]+)\s*(?:#.*)?$/) { + + my @keysyms = (); + my @scancodes = (); + + my $ext_flags = 0; + my $set_shift = 0; + my $set_altgr = 0; + my $set_caps = 0; + my $set_num = 0; + + my $clear_shift = 0; + my $clear_altgr = 0; + my $clear_caps = 0; + my $clear_num = 0; + + # Parse ranges and options + foreach $_ (split(/\s+/, $range)) { + + # Set option/modifier + if ((my $opt) = m/^\+([a-z]+)$/) { + if ($opt eq "shift") { $set_shift = 1; } + elsif ($opt eq "altgr") { $set_altgr = 1; } + elsif ($opt eq "caps") { $set_caps = 1; } + elsif ($opt eq "num") { $set_num = 1; } + elsif ($opt eq "ext") { $ext_flags = 1; } + else { + die "$filename: $.: ERROR: " + . "Invalid set option\n"; + } + } + + # Clear option/modifier + elsif ((my $opt) = m/^-([a-z]+)$/) { + if ($opt eq "shift") { $clear_shift = 1; } + elsif ($opt eq "altgr") { $clear_altgr = 1; } + elsif ($opt eq "caps") { $clear_caps = 1; } + elsif ($opt eq "num") { $clear_num = 1; } + else { + die "$filename: $.: ERROR: " + . "Invalid clear option\n"; + } + } + + # Single scancode + elsif ((my $scancode) = m/^(0x[0-9A-Fa-f]+)$/) { + $scancodes[++$#scancodes] = hex($scancode); + } + + # Range of scancodes + elsif ((my $start, my $end) = + m/^(0x[0-9A-Fa-f]+)\.\.(0x[0-9A-Fa-f]+)$/) { + for (my $i=hex($start); $i<=hex($end); $i++) { + $scancodes[++$#scancodes] = $i; + } + } + + # Invalid token + else { + die "$filename: $.: ERROR: " + . "Invalid token\n"; + } + + } + + # Parse onto + if ($onto =~ m/^0x/) { + $keysyms[0] = hex($onto); + } + else { + foreach my $char (split('', + substr($onto, 1, length($onto)-2))) { + my $codepoint = ord($char); + if ($codepoint >= 0x0100) { + $keysyms[++$#keysyms] = 0x01000000 | $codepoint; + } + else { + $keysyms[++$#keysyms] = $codepoint; + } + } + } + + # Check mapping + if ($#keysyms != $#scancodes) { + die "$filename: $.: ERROR: " + . "Keysym and scancode range lengths differ\n"; + } + + # Write keysym/scancode pairs + for (my $i=0; $i<=$#keysyms; $i++) { + + $content .= " {" + . " .keysym = " . $keysyms[$i] . "," + . " .scancode = " . $scancodes[$i]; + + # Modifiers that must be active + $content .= ", .set_modifiers = 0"; + $content .= " | GUAC_SPICE_KEYMAP_MODIFIER_SHIFT" if $set_shift; + $content .= " | GUAC_SPICE_KEYMAP_MODIFIER_ALTGR" if $set_altgr; + + # Modifiers that must be inactive + $content .= ", .clear_modifiers = 0"; + $content .= " | GUAC_SPICE_KEYMAP_MODIFIER_SHIFT" if $clear_shift; + $content .= " | GUAC_SPICE_KEYMAP_MODIFIER_ALTGR" if $clear_altgr; + + # Locks that must be set + $content .= ", .set_locks = 0"; + $content .= " | SPICE_INPUTS_NUM_LOCK" if $set_num; + $content .= " | SPICE_INPUTS_CAPS_LOCK" if $set_caps; + + # Locks that must NOT be set + $content .= ", .clear_locks = 0"; + $content .= " | SPICE_INPUTS_NUM_LOCK" if $clear_num; + $content .= " | SPICE_INPUTS_CAPS_LOCK" if $clear_caps; + + $content .= " },\n"; + + } + + } + + # Invalid token + elsif (m/\S/) { + die "$filename: $.: ERROR: " + . "Invalid token\n"; + } + + } + close INPUT; + + # Header + my $sym = keymap_symbol($layout_name); + print OUTPUT + "\n" + . '/* Autogenerated from ' . $filename . ' */' . "\n" + . 'static guac_spice_keysym_desc __' . $sym . '[] = {' . "\n" + . $content + . ' {0}' . "\n" + . '};' . "\n"; + + # Desc header + print OUTPUT "\n" + . 'static const guac_spice_keymap ' . $sym . ' = { ' . "\n"; + + # Layout name + print OUTPUT " .name = \"$layout_name\",\n"; + + # Parent layout (if any) + if ($parent) { + print OUTPUT " .parent = &$parent,\n"; + } + + # Desc footer + print OUTPUT + ' .mapping = __' . $sym . "\n" + . '};' . "\n"; + + $keymaps[++$#keymaps] = $sym; + print STDERR "Added: $layout_name\n"; + +} + +print OUTPUT "\n" + . 'const guac_spice_keymap* GUAC_SPICE_KEYMAPS[] = {' . "\n"; + +foreach my $keymap (@keymaps) { + print OUTPUT " &$keymap,\n"; +} +print OUTPUT + ' NULL' . "\n" + . '};' . "\n"; + +close OUTPUT; + diff --git a/src/protocols/spice/keymaps/hu_hu_qwertz.keymap b/src/protocols/spice/keymaps/hu_hu_qwertz.keymap new file mode 100644 index 00000000..b559cc42 --- /dev/null +++ b/src/protocols/spice/keymaps/hu_hu_qwertz.keymap @@ -0,0 +1,108 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "hu-hu-qwertz" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "0123456789öüó" +map -caps -altgr -shift 0x10..0x1B ~ "qwertzuıopőú" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjkléáű" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "íyxcvbnm,.-" + +map -caps -altgr +shift 0x29 0x02..0x0D ~ "§'"+!%/=()ÖÜÓ" +map -caps -altgr +shift 0x10..0x1B ~ "QWERTZUIOPŐÚ" +map -caps -altgr +shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÉÁŰ" +map -caps -altgr +shift 0x56 0x2C..0x35 ~ "ÍYXCVBNM?:_" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "0123456789ÖÜÓ" +map +caps -altgr -shift 0x10..0x1B ~ "QWERTZUIOPŐÚ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÉÁŰ" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "ÍYXCVBNM,.-" + +map +caps -altgr +shift 0x29 0x02..0x0D ~ "§'"+!%/=()öüó" +map +caps -altgr +shift 0x10..0x1B ~ "qwertzuiopőú" +map +caps -altgr +shift 0x1E..0x28 0x2B ~ "asdfghjkléáű" +map +caps -altgr +shift 0x56 0x2C..0x35 ~ "íyxcvbnm?:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02 ~ "~" +map +altgr -shift 0x08 ~ "`" + +map +altgr -shift 0x10 ~ "\" +map +altgr -shift 0x11 ~ "|" +map +altgr -shift 0x12 ~ "Ä" +map +altgr -shift 0x16 ~ "€" +map +altgr -shift 0x17 ~ "Í" +map +altgr -shift 0x1A ~ "÷" +map +altgr -shift 0x1B ~ "×" + +map +altgr -shift 0x1E ~ "ä" +map +altgr -shift 0x1F ~ "đ" +map +altgr -shift 0x20 ~ "Đ" +map +altgr -shift 0x21 ~ "[" +map +altgr -shift 0x22 ~ "]" +map +altgr -shift 0x24 ~ "í" +map +altgr -shift 0x25 ~ "ł" +map +altgr -shift 0x26 ~ "Ł" +map +altgr -shift 0x27 ~ "$" +map +altgr -shift 0x28 ~ "ß" +map +altgr -shift 0x2B ~ "¤" + +map +altgr -shift 0x56 ~ "<" +map +altgr -shift 0x2C ~ ">" +map +altgr -shift 0x2D ~ "#" +map +altgr -shift 0x2E ~ "&" +map +altgr -shift 0x2F ~ "@" +map +altgr -shift 0x30 ~ "{" +map +altgr -shift 0x31 ~ "}" +map +altgr -shift 0x32 ~ "<" +map +altgr -shift 0x33 ~ ";" +map +altgr -shift 0x34 ~ ">" +map +altgr -shift 0x35 ~ "*" + + +# +# Keys requiring AltGr & Shift +# + + +# +# Dead keys +# + +map +altgr -shift 0x03 ~ 0xFE5A # Dead caron +map +altgr -shift 0x04 ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x05 ~ 0xFE55 # Dead breve +map +altgr -shift 0x06 ~ 0xFE58 # Dead abovering +map +altgr -shift 0x07 ~ 0xFE5C # Dead ogonek +map +altgr -shift 0x09 ~ 0xFE56 # Dead abovedot +map +altgr -shift 0x0A ~ 0xFE51 # Dead acute +map +altgr -shift 0x0B ~ 0xFE59 # Dead doubleacute +map +altgr -shift 0x0C ~ 0xFE57 # Dead diaeresis +map +altgr -shift 0x0D ~ 0xFE5B # Dead cedilla + + +# END diff --git a/src/protocols/spice/keymaps/it_it_qwerty.keymap b/src/protocols/spice/keymaps/it_it_qwerty.keymap new file mode 100644 index 00000000..74da0bf9 --- /dev/null +++ b/src/protocols/spice/keymaps/it_it_qwerty.keymap @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "it-it-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "\1234567890'ì" +map -caps -altgr -shift 0x10..0x1B ~ "qwertyuiopè+" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklòàù" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "\1234567890'ì" +map +caps -altgr -shift 0x10..0x1B ~ "QWERTYUIOPè+" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLòàù" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x12 ~ "€" +map +altgr -shift 0x1A ~ "[" +map +altgr -shift 0x1B ~ "]" +map +altgr -shift 0x27 ~ "@" +map +altgr -shift 0x28 ~ "#" + +map +altgr +shift 0x1A ~ "{" +map +altgr +shift 0x1B ~ "}" + diff --git a/src/protocols/spice/keymaps/ja_jp_qwerty.keymap b/src/protocols/spice/keymaps/ja_jp_qwerty.keymap new file mode 100644 index 00000000..dc879fb7 --- /dev/null +++ b/src/protocols/spice/keymaps/ja_jp_qwerty.keymap @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "ja-jp-qwerty" + +map -shift 0x02..0x0D 0x7D ~ "1234567890-^\" +map -shift 0x10..0x1B ~ "qwertyuiop@[" +map -shift 0x1E..0x28 0x2B ~ "asdfghjkl;:]" +map -shift 0x2C..0x35 0x73 ~ "zxcvbnm,./\" + +map +shift 0x02..0x0A 0x0C 0x0D 0x7D ~ "!"#$%&'()=~|" +map +shift 0x10..0x1B ~ "QWERTYUIOP`{" +map +shift 0x1E..0x28 0x2B ~ "ASDFGHJKL+*}" +map +shift 0x2C..0x35 0x73 ~ "ZXCVBNM<>?_" + +map -shift 0x29 ~ 0xFF28 +map -shift 0x29 ~ 0xFF2A +map +shift 0x29 ~ 0xFF29 diff --git a/src/protocols/spice/keymaps/no_no_qwerty.keymap b/src/protocols/spice/keymaps/no_no_qwerty.keymap new file mode 100644 index 00000000..0661a58d --- /dev/null +++ b/src/protocols/spice/keymaps/no_no_qwerty.keymap @@ -0,0 +1,75 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "no-no-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0C ~ "|1234567890+" +map -caps -altgr -shift 0x10..0x1A ~ "qwertyuiopå" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjkløæ'" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0C ~ "|1234567890+" +map +caps -altgr -shift 0x10..0x1A ~ "QWERTYUIOPÅ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLØÆ'" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +map -altgr -shift 0x0D ~ "\" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x03 ~ "@" +map +altgr -shift 0x04 ~ "£" +map +altgr -shift 0x05 ~ "$" +map +altgr -shift 0x08 ~ "{" +map +altgr -shift 0x09 ~ "[" +map +altgr -shift 0x0A ~ "]" +map +altgr -shift 0x0B ~ "}" +map +altgr -shift 0x56 ~ "\" + +map +altgr -shift 0x12 ~ "€" + +map +altgr -shift 0x0D ~ "|" +map +altgr -shift 0x32 ~ "µ" + +# +# Dead keys +# + +map +altgr -shift 0x0D ~ 0xFE51 # Dead acute +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut +map -altgr +shift 0x1B ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde diff --git a/src/protocols/spice/keymaps/pl_pl_qwerty.keymap b/src/protocols/spice/keymaps/pl_pl_qwerty.keymap new file mode 100644 index 00000000..1072971e --- /dev/null +++ b/src/protocols/spice/keymaps/pl_pl_qwerty.keymap @@ -0,0 +1,63 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "pl-pl-qwerty" + +map -caps -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map -caps -shift 0x10..0x1B 0x2B ~ "qwertyuiop[]\" +map -caps -shift 0x1E..0x28 ~ "asdfghjkl;'" +map -caps -shift 0x2C..0x35 ~ "zxcvbnm,./" + +map -caps +shift 0x29 0x02..0x0D ~ "~!@#$%^&*()_+" +map -caps +shift 0x10..0x1B 0x2B ~ "QWERTYUIOP{}|" +map -caps +shift 0x1E..0x28 ~ "ASDFGHJKL:"" +map -caps +shift 0x2C..0x35 ~ "ZXCVBNM<>?" + +map +caps -shift 0x29 0x02..0x0D ~ "`1234567890-=" +map +caps -shift 0x10..0x1B 0x2B ~ "QWERTYUIOP[]\" +map +caps -shift 0x1E..0x28 ~ "ASDFGHJKL;'" +map +caps -shift 0x2C..0x35 ~ "ZXCVBNM,./" + +map +caps +shift 0x29 0x02..0x0D ~ "~!@#$%^&*()_+" +map +caps +shift 0x10..0x1B 0x2B ~ "qwertyuiop{}|" +map +caps +shift 0x1E..0x28 ~ "asdfghjkl:"" +map +caps +shift 0x2C..0x35 ~ "zxcvbnm<>?" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x16 ~ "€" + +map -caps -shift +altgr 0x12 0x18 ~ "ęó" +map -caps -shift +altgr 0x1E 0x1F 0x26 ~ "ąśł" +map -caps -shift +altgr 0x2C 0x2D 0x2E 0x31 ~ "żźćń" + +map -caps +shift +altgr 0x12 0x18 ~ "ĘÓ" +map -caps +shift +altgr 0x1E 0x1F 0x26 ~ "ĄŚŁ" +map -caps +shift +altgr 0x2C 0x2D 0x2E 0x31 ~ "ŻŹĆŃ" + +map +caps -shift +altgr 0x12 0x18 ~ "ĘÓ" +map +caps -shift +altgr 0x1E 0x1F 0x26 ~ "ĄŚŁ" +map +caps -shift +altgr 0x2C 0x2D 0x2E 0x31 ~ "ŻŹĆŃ" + +map +caps +shift +altgr 0x12 0x18 ~ "ęó" +map +caps +shift +altgr 0x1E 0x1F 0x26 ~ "ąśł" +map +caps +shift +altgr 0x2C 0x2D 0x2E 0x31 ~ "żźćń" diff --git a/src/protocols/spice/keymaps/pt_br_qwerty.keymap b/src/protocols/spice/keymaps/pt_br_qwerty.keymap new file mode 100644 index 00000000..b1d0a2ef --- /dev/null +++ b/src/protocols/spice/keymaps/pt_br_qwerty.keymap @@ -0,0 +1,66 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "pt-br-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ "'1234567890-=" +map -caps -altgr -shift 0x10..0x19 0x1B ~ "qwertyuiop[" +map -caps -altgr -shift 0x1E..0x27 0x2B ~ "asdfghjklç]" +map -caps -altgr -shift 0x56 0x2C..0x35 0x73 ~ "\zxcvbnm,.;/" + +map -caps -altgr +shift 0x29 0x02..0x06 0x08..0x0D ~ ""!@#$%&*()_+" +map -caps -altgr +shift 0x10..0x19 0x1B ~ "QWERTYUIOP{" +map -caps -altgr +shift 0x1E..0x27 0x2B ~ "ASDFGHJKLÇ}" +map -caps -altgr +shift 0x56 0x2C..0x35 0x73 ~ "|ZXCVBNM<>:?" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ "'1234567890-=" +map +caps -altgr -shift 0x10..0x19 0x1B ~ "QWERTYUIOP[" +map +caps -altgr -shift 0x1E..0x27 0x2B ~ "ASDFGHJKLÇ]" +map +caps -altgr -shift 0x56 0x2C..0x35 0x73 ~ "\ZXCVBNM,.;/" + +map +caps -altgr +shift 0x29 0x02..0x06 0x08..0x0D ~ ""!@#$%&*()_+" +map +caps -altgr +shift 0x10..0x19 0x1B ~ "qwertyuiop{" +map +caps -altgr +shift 0x1E..0x27 0x2B ~ "asdfghjklç}" +map +caps -altgr +shift 0x56 0x2C..0x35 0x73 ~ "|zxcvbnm<>:?" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x02..0x07 0x0D ~ "¹²³£¢¬§" +map +altgr -shift 0x10..0x11 ~ "/?" +map +altgr -shift 0x12 ~ "°" +map +altgr -shift 0x1B ~ "ª" +map +altgr -shift 0x2B ~ "º" +map +altgr -shift 0x2E ~ "₢" + +# +# Dead keys +# + +map -altgr +shift 0x07 ~ 0xFE57 # Dead diaeresis (umlaut) +map -altgr +shift 0x1A ~ 0xFE50 # Dead grave +map -altgr -shift 0x1A ~ 0xFE51 # Dead acute +map -altgr +shift 0x28 ~ 0xFE52 # Dead circumflex +map -altgr -shift 0x28 ~ 0xFE53 # Dead tilde \ No newline at end of file diff --git a/src/protocols/spice/keymaps/sv_se_qwerty.keymap b/src/protocols/spice/keymaps/sv_se_qwerty.keymap new file mode 100644 index 00000000..9edfccb3 --- /dev/null +++ b/src/protocols/spice/keymaps/sv_se_qwerty.keymap @@ -0,0 +1,74 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "sv-se-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890+" +map -caps -altgr -shift 0x10..0x1A ~ "qwertyuiopå" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklöä'" +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNM;:_" + +map +caps -altgr -shift 0x29 0x02..0x0C ~ "§1234567890+" +map +caps -altgr -shift 0x10..0x1A ~ "QWERTYUIOPÅ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLÖÄ'" +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnm;:_" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x03 ~ "@" +map +altgr -shift 0x04 ~ "£" +map +altgr -shift 0x05 ~ "$" +map +altgr -shift 0x08 ~ "{" +map +altgr -shift 0x09 ~ "[" +map +altgr -shift 0x0A ~ "]" +map +altgr -shift 0x0B ~ "}" +map +altgr -shift 0x0C ~ "\" + +map +altgr -shift 0x12 ~ "€" + +map +altgr -shift 0x56 ~ "|" +map +altgr -shift 0x32 ~ "µ" + +# +# Dead keys +# + +map -altgr -shift 0x0D ~ 0xFE51 # Dead acute +map -altgr +shift 0x0D ~ 0xFE50 # Dead grave +map -altgr -shift 0x1B ~ 0xFE57 # Dead umlaut +map -altgr +shift 0x1B ~ 0xFE52 # Dead circumflex +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde + diff --git a/src/protocols/spice/keymaps/tr_tr_qwerty.keymap b/src/protocols/spice/keymaps/tr_tr_qwerty.keymap new file mode 100644 index 00000000..5df30005 --- /dev/null +++ b/src/protocols/spice/keymaps/tr_tr_qwerty.keymap @@ -0,0 +1,92 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +parent "base" +name "tr-tr-qwerty" + +# +# Basic keys +# + +map -caps -altgr -shift 0x29 0x02..0x0D ~ ""1234567890*-" +map -caps -altgr -shift 0x10..0x1B ~ "qwertyuıopğü" +map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklşi," +map -caps -altgr -shift 0x56 0x2C..0x35 ~ "ZXCVBNMÖÇ:" + +map +caps -altgr -shift 0x29 0x02..0x0D ~ ""1234567890*-" +map +caps -altgr -shift 0x10..0x1B ~ "QWERTYUIOPĞÜ" +map +caps -altgr -shift 0x1E..0x28 0x2B ~ "ASDFGHJKLŞİ," +map +caps -altgr -shift 0x56 0x2C..0x35 ~ "zxcvbnmöç:" + +# +# Keys requiring AltGr +# + +map +altgr -shift 0x29 0x02..0x06 ~ "<>£#$½" +map +altgr -shift 0x08..0x0D ~ "{[]}\|" + +map +altgr -shift 0x10 ~ "@" +map +altgr -shift 0x12 ~ "€" +map +altgr -shift 0x14 ~ "₺" +map +altgr -shift 0x1F ~ "ß" + +map +altgr -shift 0x56 ~ "|" + + +# +# Keys requiring AltGr and vary by Shift and Caps Lock +# + +map -caps +altgr -shift 0x17 ~ "i" +map -caps +altgr -shift 0x1E ~ "æ" + +map -caps +altgr +shift 0x17 ~ "İ" +map -caps +altgr +shift 0x1E ~ "Æ" + +map +caps +altgr -shift 0x17 ~ "İ" +map +caps +altgr -shift 0x1E ~ "Æ" + +map +caps +altgr +shift 0x1E ~ "æ" +map +caps +altgr +shift 0x17 ~ "i" + +# +# Dead keys +# + +map -altgr +shift 0x04 ~ 0xFE52 # Dead circumflex + +map +altgr -shift 0x1A ~ 0xFE57 # Dead diaeresis (umlaut) +map +altgr -shift 0x1B ~ 0xFE53 # Dead tilde + +map +altgr -shift 0x27 ~ 0xFE51 # Dead acute +map +altgr -shift 0x2B ~ 0xFE50 # Dead grave + +# END diff --git a/src/protocols/spice/log.c b/src/protocols/spice/log.c new file mode 100644 index 00000000..0daf2ab0 --- /dev/null +++ b/src/protocols/spice/log.c @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "client.h" +#include "common/iconv.h" +#include "common/surface.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +void guac_spice_client_log_info(const char* format, ...) { + + char message[2048]; + + /* Copy log message into buffer */ + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + /* Log to syslog */ + syslog(LOG_INFO, "%s", message); + +} + +void guac_spice_client_log_error(const char* format, ...) { + + char message[2048]; + + /* Copy log message into buffer */ + va_list args; + va_start(args, format); + vsnprintf(message, sizeof(message), format, args); + va_end(args); + + /* Log to syslog */ + syslog(LOG_ERR, "%s", message); + +} + diff --git a/src/protocols/spice/log.h b/src/protocols/spice/log.h new file mode 100644 index 00000000..408800c5 --- /dev/null +++ b/src/protocols/spice/log.h @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_LOG_H +#define GUAC_SPICE_LOG_H + +#include "config.h" + +#include "client.h" +#include "common/iconv.h" +#include "common/surface.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/** + * Callback invoked by SPICE when an informational message needs to be + * logged. + * + * @param format + * A printf-style format string to log. + * + * @param ... + * The values to use when filling the conversion specifiers within the + * format string. + */ +void guac_spice_client_log_info(const char* format, ...); + +/** + * Callback invoked by SPICE when an error message needs to be logged. + * + * @param format + * A printf-style format string to log. + * + * @param ... + * The values to use when filling the conversion specifiers within the + * format string. + */ +void guac_spice_client_log_error(const char* format, ...); + +#endif /* GUAC_SPICE_LOG_H */ + diff --git a/src/protocols/spice/settings.c b/src/protocols/spice/settings.c new file mode 100644 index 00000000..e15d596e --- /dev/null +++ b/src/protocols/spice/settings.c @@ -0,0 +1,628 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "argv.h" +#include "client.h" +#include "common/defaults.h" +#include "settings.h" +#include "spice-defaults.h" +#include "spice-constants.h" + +#include + +#include +#include +#include +#include +#include + +/* Client plugin arguments */ +const char* GUAC_SPICE_CLIENT_ARGS[] = { + "hostname", + "port", + "tls", + "tls-verify", + "ca", + "ca-file", + "pubkey", + "proxy", + "read-only", + "encodings", + GUAC_SPICE_ARGV_USERNAME, + GUAC_SPICE_ARGV_PASSWORD, + "swap-red-blue", + "color-depth", + "cursor", + "autoretry", + "clipboard-encoding", + + "enable-audio", + "file-transfer", + "file-directory", + "file-transfer-ro", + "server-layout", + +#ifdef ENABLE_COMMON_SSH + "enable-sftp", + "sftp-hostname", + "sftp-host-key", + "sftp-port", + "sftp-username", + "sftp-password", + "sftp-private-key", + "sftp-passphrase", + "sftp-directory", + "sftp-root-directory", + "sftp-server-alive-interval", + "sftp-disable-download", + "sftp-disable-upload", +#endif + + "recording-path", + "recording-name", + "recording-exclude-output", + "recording-exclude-mouse", + "recording-include-keys", + "create-recording-path", + "disable-copy", + "disable-paste", + + NULL +}; + +enum SPICE_ARGS_IDX { + + /** + * The hostname of the SPICE server (or repeater) to connect to. + */ + IDX_HOSTNAME, + + /** + * The port of the SPICE server (or repeater) to connect to. + */ + IDX_PORT, + + /** + * Whether or not the connection to the SPICE server should be made via + * TLS. + */ + IDX_TLS, + + /** + * The verification mode that should be used to validate TLS connections + * to the SPICE server. + */ + IDX_TLS_VERIFY, + + /** + * One or more Base64-encoded certificates that will be used for TLS + * verification. + */ + IDX_CA, + + /** + * A path to a file containing one or more certificates that will be used + * when validating TLS connections. + */ + IDX_CA_FILE, + + /** + * The public key of the host for TLS verification. + */ + IDX_PUBKEY, + + /** + * The proxy server to connect through when connecting to the SPICE server. + */ + IDX_PROXY, + + /** + * "true" if this connection should be read-only (user input should be + * dropped), "false" or blank otherwise. + */ + IDX_READ_ONLY, + + /** + * Space-separated list of encodings to use within the SPICE session. If not + * specified, this will be: + * + * "zrle ultra copyrect hextile zlib corre rre raw". + */ + IDX_ENCODINGS, + + /** + * The username to send to the SPICE server if authentication is requested. + */ + IDX_USERNAME, + + /** + * The password to send to the SPICE server if authentication is requested. + */ + IDX_PASSWORD, + + /** + * "true" if the red and blue components of each color should be swapped, + * "false" or blank otherwise. This is mainly used for SPICE servers that do + * not properly handle colors. + */ + IDX_SWAP_RED_BLUE, + + /** + * The color depth to request, in bits. + */ + IDX_COLOR_DEPTH, + + /** + * "remote" if the cursor should be rendered on the server instead of the + * client. All other values will default to local rendering. + */ + IDX_CURSOR, + + /** + * The number of connection attempts to make before giving up. By default, + * this will be 0. + */ + IDX_AUTORETRY, + + /** + * The encoding to use for clipboard data sent to the SPICE server if we are + * going to be deviating from the standard (which mandates ISO 8829-1). + * Valid values are "ISO8829-1" (the only legal value with respect to the + * SPICE standard), "UTF-8", "UTF-16", and "CP2252". + */ + IDX_CLIPBOARD_ENCODING, + + /** + * "true" if audio should be enabled, "false" or blank otherwise. + */ + IDX_ENABLE_AUDIO, + + /** + * "true" if file transfer should be enabled, "false" or blank otherwise. + */ + IDX_FILE_TRANSFER, + + /** + * The absolute path to the directory that should be shared from the system + * running guacd to the spice server. + */ + IDX_FILE_DIRECTORY, + + /** + * Whether or not the shared directory should be read-only to the SPICE + * server. + */ + IDX_FILE_TRANSFER_RO, + + /** + * The name of the keymap chosen as the layout of the server. Legal names + * are defined within the *.keymap files in the "keymaps" directory of the + * source for Guacamole's SPICE support. + */ + IDX_SERVER_LAYOUT, + +#ifdef ENABLE_COMMON_SSH + /** + * "true" if SFTP should be enabled for the SPICE connection, "false" or + * blank otherwise. + */ + IDX_ENABLE_SFTP, + + /** + * The hostname of the SSH server to connect to for SFTP. If blank, the + * hostname of the SPICE server will be used. + */ + IDX_SFTP_HOSTNAME, + + /** + * The public SSH host key to identify the SFTP server. + */ + IDX_SFTP_HOST_KEY, + + /** + * The port of the SSH server to connect to for SFTP. If blank, the default + * SSH port of "22" will be used. + */ + IDX_SFTP_PORT, + + /** + * The username to provide when authenticating with the SSH server for + * SFTP. + */ + IDX_SFTP_USERNAME, + + /** + * The password to provide when authenticating with the SSH server for + * SFTP (if not using a private key). + */ + IDX_SFTP_PASSWORD, + + /** + * The base64-encoded private key to use when authenticating with the SSH + * server for SFTP (if not using a password). + */ + IDX_SFTP_PRIVATE_KEY, + + /** + * The passphrase to use to decrypt the provided base64-encoded private + * key. + */ + IDX_SFTP_PASSPHRASE, + + /** + * The default location for file uploads within the SSH server. This will + * apply only to uploads which do not use the filesystem guac_object (where + * the destination directory is otherwise ambiguous). + */ + IDX_SFTP_DIRECTORY, + + /** + * The path of the directory within the SSH server to expose as a + * filesystem guac_object. If omitted, "/" will be used by default. + */ + IDX_SFTP_ROOT_DIRECTORY, + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically incremented to 2 by libssh2 to avoid busy loop corner + * cases. + */ + IDX_SFTP_SERVER_ALIVE_INTERVAL, + + /** + * If set to "true", file downloads over SFTP will be blocked. If set to + * "false" or not set, file downloads will be allowed. + */ + IDX_SFTP_DISABLE_DOWNLOAD, + + /** + * If set to "true", file uploads over SFTP will be blocked. If set to + * "false" or not set, file uploads will be allowed. + */ + IDX_SFTP_DISABLE_UPLOAD, +#endif + + /** + * The full absolute path to the directory in which screen recordings + * should be written. + */ + IDX_RECORDING_PATH, + + /** + * The name that should be given to screen recordings which are written in + * the given path. + */ + IDX_RECORDING_NAME, + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + IDX_RECORDING_EXCLUDE_OUTPUT, + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + IDX_RECORDING_EXCLUDE_MOUSE, + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + IDX_RECORDING_INCLUDE_KEYS, + + /** + * Whether the specified screen recording path should automatically be + * created if it does not yet exist. + */ + IDX_CREATE_RECORDING_PATH, + + /** + * Whether outbound clipboard access should be blocked. If set to "true", + * it will not be possible to copy data from the remote desktop to the + * client using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_COPY, + + /** + * Whether inbound clipboard access should be blocked. If set to "true", it + * will not be possible to paste data from the client to the remote desktop + * using the clipboard. By default, clipboard access is not blocked. + */ + IDX_DISABLE_PASTE, + + SPICE_ARGS_COUNT +}; + +guac_spice_settings* guac_spice_parse_args(guac_user* user, + int argc, const char** argv) { + + /* Validate arg count */ + if (argc != SPICE_ARGS_COUNT) { + guac_user_log(user, GUAC_LOG_WARNING, "Incorrect number of connection " + "parameters provided: expected %i, got %i.", + SPICE_ARGS_COUNT, argc); + return NULL; + } + + guac_spice_settings* settings = calloc(1, sizeof(guac_spice_settings)); + + settings->hostname = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_HOSTNAME, SPICE_DEFAULT_HOST); + + settings->port = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_PORT, SPICE_DEFAULT_PORT); + + settings->tls = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_TLS, false); + + char* verify_mode = guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_TLS_VERIFY, NULL); + + if (verify_mode != NULL) { + if (strcmp(verify_mode, GUAC_SPICE_PARAMETER_TLS_VERIFY_PUBKEY) == 0) + settings->tls_verify = SPICE_SESSION_VERIFY_PUBKEY; + else if (strcmp(verify_mode, GUAC_SPICE_PARAMETER_TLS_VERIFY_SUBJECT) == 0) + settings->tls_verify = SPICE_SESSION_VERIFY_SUBJECT; + } + + else { + settings->tls_verify = SPICE_SESSION_VERIFY_HOSTNAME; + } + + free(verify_mode); + + settings->ca = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_CA, NULL); + + settings->ca_file = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_CA_FILE, NULL); + + settings->pubkey = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_PUBKEY, NULL); + + settings->proxy = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_PROXY, NULL); + + settings->username = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_USERNAME, NULL); + + settings->password = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_PASSWORD, NULL); + + /* Read-only mode */ + settings->read_only = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_READ_ONLY, false); + + /* Parse color depth */ + settings->color_depth = + guac_user_parse_args_int(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_COLOR_DEPTH, 0); + + /* Set encodings if specified */ + settings->encodings = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_ENCODINGS, + SPICE_DEFAULT_ENCODINGS); + + /* Parse autoretry */ + settings->retries = + guac_user_parse_args_int(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_AUTORETRY, 0); + + /* Audio enable/disable */ + settings->audio_enabled = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_ENABLE_AUDIO, false); + + /* File transfer enable/disable */ + settings->file_transfer = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_FILE_TRANSFER, false); + + /* The directory on the guacd server to share */ + settings->file_directory = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_FILE_DIRECTORY, NULL); + + /* Whether or not the share should be read-only. */ + settings->file_transfer_ro = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_FILE_TRANSFER_RO, false); + + /* Pick keymap based on argument */ + settings->server_layout = NULL; + if (argv[IDX_SERVER_LAYOUT][0] != '\0') + settings->server_layout = + guac_spice_keymap_find(argv[IDX_SERVER_LAYOUT]); + + /* If no keymap requested, use default */ + if (settings->server_layout == NULL) + settings->server_layout = guac_spice_keymap_find(GUAC_SPICE_DEFAULT_KEYMAP); + + /* Set clipboard encoding if specified */ + settings->clipboard_encoding = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_CLIPBOARD_ENCODING, NULL); + +#ifdef ENABLE_COMMON_SSH + /* SFTP enable/disable */ + settings->enable_sftp = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_ENABLE_SFTP, false); + + /* Hostname for SFTP connection */ + settings->sftp_hostname = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_HOSTNAME, settings->hostname); + + /* The public SSH host key. */ + settings->sftp_host_key = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_HOST_KEY, NULL); + + /* Port for SFTP connection */ + settings->sftp_port = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_PORT, SPICE_DEFAULT_SFTP_PORT); + + /* Username for SSH/SFTP authentication */ + settings->sftp_username = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_USERNAME, ""); + + /* Password for SFTP (if not using private key) */ + settings->sftp_password = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_PASSWORD, ""); + + /* Private key for SFTP (if not using password) */ + settings->sftp_private_key = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_PRIVATE_KEY, NULL); + + /* Passphrase for decrypting the SFTP private key (if applicable */ + settings->sftp_passphrase = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_PASSPHRASE, ""); + + /* Default upload directory */ + settings->sftp_directory = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_DIRECTORY, NULL); + + /* SFTP root directory */ + settings->sftp_root_directory = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_ROOT_DIRECTORY, SPICE_DEFAULT_SFTP_ROOT); + + /* Default keepalive value */ + settings->sftp_server_alive_interval = + guac_user_parse_args_int(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_SERVER_ALIVE_INTERVAL, 0); + + settings->sftp_disable_download = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_DOWNLOAD, false); + + settings->sftp_disable_upload = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_SFTP_DISABLE_UPLOAD, false); +#endif + + /* Read recording path */ + settings->recording_path = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_RECORDING_PATH, NULL); + + /* Read recording name */ + settings->recording_name = + guac_user_parse_args_string(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_RECORDING_NAME, GUAC_SPICE_DEFAULT_RECORDING_NAME); + + /* Parse output exclusion flag */ + settings->recording_exclude_output = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_OUTPUT, false); + + /* Parse mouse exclusion flag */ + settings->recording_exclude_mouse = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_RECORDING_EXCLUDE_MOUSE, false); + + /* Parse key event inclusion flag */ + settings->recording_include_keys = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_RECORDING_INCLUDE_KEYS, false); + + /* Parse path creation flag */ + settings->create_recording_path = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_CREATE_RECORDING_PATH, false); + + /* Parse clipboard copy disable flag */ + settings->disable_copy = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_DISABLE_COPY, false); + + /* Parse clipboard paste disable flag */ + settings->disable_paste = + guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv, + IDX_DISABLE_PASTE, false); + + return settings; + +} + +void guac_spice_settings_free(guac_spice_settings* settings) { + + /* Free settings strings */ + free(settings->clipboard_encoding); + free(settings->encodings); + free(settings->hostname); + free(settings->password); + free(settings->recording_name); + free(settings->recording_path); + free(settings->username); + +#ifdef ENABLE_SPICE_REPEATER + /* Free SPICE repeater settings */ + free(settings->dest_host); +#endif + +#ifdef ENABLE_COMMON_SSH + /* Free SFTP settings */ + free(settings->sftp_directory); + free(settings->sftp_root_directory); + free(settings->sftp_host_key); + free(settings->sftp_hostname); + free(settings->sftp_passphrase); + free(settings->sftp_password); + free(settings->sftp_port); + free(settings->sftp_private_key); + free(settings->sftp_username); +#endif + + /* Free settings structure */ + free(settings); + +} + diff --git a/src/protocols/spice/settings.h b/src/protocols/spice/settings.h new file mode 100644 index 00000000..85465202 --- /dev/null +++ b/src/protocols/spice/settings.h @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +#ifndef GUAC_SPICE_SETTINGS_H +#define GUAC_SPICE_SETTINGS_H + +#include "config.h" +#include "keymap.h" + +#include + +#include + +/** + * The filename to use for the screen recording, if not specified. + */ +#define GUAC_SPICE_DEFAULT_RECORDING_NAME "recording" + +/** + * SPICE-specific client data. + */ +typedef struct guac_spice_settings { + + /** + * The hostname of the SPICE server (or repeater) to connect to. + */ + char* hostname; + + /** + * The port of the SPICE server (or repeater) to connect to. + */ + char* port; + + /** + * Whether or not TLS should be used to connect to the SPICE server. + */ + bool tls; + + /** + * The type of TLS validation that should be done for encrypted connections + * to SPICE servers. + */ + SpiceSessionVerify tls_verify; + + /** + * One or more Base64-encoded certificates to use to validate TLS + * connections to the SPICE server. + */ + char* ca; + + /** + * A path to a file containing one more certificates that will be used to + * validate TLS connections. + */ + char* ca_file; + + /** + * The public key of the SPICE server for TLS verification. + */ + char* pubkey; + + /** + * SPICE supports connecting to remote servers via a proxy server. You can + * specify the proxy server to use in this property. + */ + char* proxy; + + /** + * The username given in the arguments. + */ + char* username; + + /** + * The password given in the arguments. + */ + char* password; + + /** + * Space-separated list of encodings to use within the SPICE session. + */ + char* encodings; + + /** + * The color depth to request, in bits. + */ + int color_depth; + + /** + * Whether this connection is read-only, and user input should be dropped. + */ + bool read_only; + + /** + * Whether audio is enabled. + */ + bool audio_enabled; + + /** + * If file transfer capability should be enabled. + */ + bool file_transfer; + + /** + * The directory on the server where guacd is running that should be + * shared. + */ + char* file_directory; + + /** + * If file transfer capability should be limited to read-only. + */ + bool file_transfer_ro; + + /** + * The keymap chosen as the layout of the server. + */ + const guac_spice_keymap* server_layout; + + /** + * The number of connection attempts to make before giving up. + */ + int retries; + + /** + * The encoding to use for clipboard data sent to the SPICE server, or NULL + * to use the encoding required by the SPICE standard. + */ + char* clipboard_encoding; + + /** + * Whether outbound clipboard access should be blocked. If set, it will not + * be possible to copy data from the remote desktop to the client using the + * clipboard. + */ + bool disable_copy; + + /** + * Whether inbound clipboard access should be blocked. If set, it will not + * be possible to paste data from the client to the remote desktop using + * the clipboard. + */ + bool disable_paste; + +#ifdef ENABLE_COMMON_SSH + /** + * Whether SFTP should be enabled for the SPICE connection. + */ + bool enable_sftp; + + /** + * The hostname of the SSH server to connect to for SFTP. + */ + char* sftp_hostname; + + /** + * The public SSH host key. + */ + char* sftp_host_key; + + /** + * The port of the SSH server to connect to for SFTP. + */ + char* sftp_port; + + /** + * The username to provide when authenticating with the SSH server for + * SFTP. + */ + char* sftp_username; + + /** + * The password to provide when authenticating with the SSH server for + * SFTP (if not using a private key). + */ + char* sftp_password; + + /** + * The base64-encoded private key to use when authenticating with the SSH + * server for SFTP (if not using a password). + */ + char* sftp_private_key; + + /** + * The passphrase to use to decrypt the provided base64-encoded private + * key. + */ + char* sftp_passphrase; + + /** + * The default location for file uploads within the SSH server. This will + * apply only to uploads which do not use the filesystem guac_object (where + * the destination directory is otherwise ambiguous). + */ + char* sftp_directory; + + /** + * The path of the directory within the SSH server to expose as a + * filesystem guac_object. + */ + char* sftp_root_directory; + + /** + * The interval at which SSH keepalive messages are sent to the server for + * SFTP connections. The default is 0 (disabling keepalives), and a value + * of 1 is automatically increased to 2 by libssh2 to avoid busy loop corner + * cases. + */ + int sftp_server_alive_interval; + + /** + * Whether file downloads over SFTP should be blocked. If set to "true", + * the local client will not be able to download files from the SFTP server. + * If set to "false" or not set, file downloads will be allowed. + */ + bool sftp_disable_download; + + /** + * Whether file uploads over SFTP should be blocked. If set to "true", the + * local client will not be able to upload files to the SFTP server. If set + * to "false" or not set, file uploads will be allowed. + */ + bool sftp_disable_upload; +#endif + + /** + * The path in which the screen recording should be saved, if enabled. If + * no screen recording should be saved, this will be NULL. + */ + char* recording_path; + + /** + * The filename to use for the screen recording, if enabled. + */ + char* recording_name; + + /** + * Whether the screen recording path should be automatically created if it + * does not already exist. + */ + bool create_recording_path; + + /** + * Whether output which is broadcast to each connected client (graphics, + * streams, etc.) should NOT be included in the session recording. Output + * is included by default, as it is necessary for any recording which must + * later be viewable as video. + */ + bool recording_exclude_output; + + /** + * Whether changes to mouse state, such as position and buttons pressed or + * released, should NOT be included in the session recording. Mouse state + * is included by default, as it is necessary for the mouse cursor to be + * rendered in any resulting video. + */ + bool recording_exclude_mouse; + + /** + * Whether keys pressed and released should be included in the session + * recording. Key events are NOT included by default within the recording, + * as doing so has privacy and security implications. Including key events + * may be necessary in certain auditing contexts, but should only be done + * with caution. Key events can easily contain sensitive information, such + * as passwords, credit card numbers, etc. + */ + bool recording_include_keys; + +} guac_spice_settings; + +/** + * Parses all given args, storing them in a newly-allocated settings object. If + * the args fail to parse, NULL is returned. + * + * @param user + * The user who submitted the given arguments while joining the + * connection. + * + * @param argc + * The number of arguments within the argv array. + * + * @param argv + * The values of all arguments provided by the user. + * + * @return + * A newly-allocated settings object which must be freed with + * guac_spice_settings_free() when no longer needed. If the arguments fail + * to parse, NULL is returned. + */ +guac_spice_settings* guac_spice_parse_args(guac_user* user, + int argc, const char** argv); + +/** + * Frees the given guac_spice_settings object, having been previously allocated + * via guac_spice_parse_args(). + * + * @param settings + * The settings object to free. + */ +void guac_spice_settings_free(guac_spice_settings* settings); + +/** + * NULL-terminated array of accepted client args. + */ +extern const char* GUAC_SPICE_CLIENT_ARGS[]; + +#endif /* SPICE_SETTINGS_H */ diff --git a/src/protocols/spice/sftp.c b/src/protocols/spice/sftp.c new file mode 100644 index 00000000..bdf68a41 --- /dev/null +++ b/src/protocols/spice/sftp.c @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "common-ssh/sftp.h" +#include "sftp.h" +#include "spice.h" + +#include +#include +#include + +int guac_spice_sftp_file_handler(guac_user* user, guac_stream* stream, + char* mimetype, char* filename) { + + guac_client* client = user->client; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_common_ssh_sftp_filesystem* filesystem = spice_client->sftp_filesystem; + + /* Handle file upload */ + return guac_common_ssh_sftp_handle_file_stream(filesystem, user, stream, + mimetype, filename); + +} + diff --git a/src/protocols/spice/sftp.h b/src/protocols/spice/sftp.h new file mode 100644 index 00000000..d09145e6 --- /dev/null +++ b/src/protocols/spice/sftp.h @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_SFTP_H +#define GUAC_SPICE_SFTP_H + +#include "config.h" + +#include + +/** + * Handles an incoming stream from a Guacamole "file" instruction, saving the + * contents of that stream to the file having the given name. + */ +guac_user_file_handler guac_spice_sftp_file_handler; + +#endif /* SPICE_SFTP_H */ + diff --git a/src/protocols/spice/spice-constants.h b/src/protocols/spice/spice-constants.h new file mode 100644 index 00000000..92d02839 --- /dev/null +++ b/src/protocols/spice/spice-constants.h @@ -0,0 +1,360 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef SPICE_CONSTANTS_H +#define SPICE_CONSTANTS_H + +/** + * The key used to store and retrieve Guacamole-related data from within the + * SPICE client structure. + */ +#define GUAC_SPICE_CLIENT_KEY "GUAC_SPICE" + +/** + * The default identifier of the pimary/main display. + */ +#define GUAC_SPICE_DEFAULT_DISPLAY_ID 0 + +/** + * The TLS verification value from Guacamole Client that indicates that hostname + * verification should be done. + */ +#define GUAC_SPICE_PARAMETER_TLS_VERIFY_HOSTNAME "hostname" + +/** + * The TLS verification value from Guacamole Client that indicates that public + * key verification should be performed. + */ +#define GUAC_SPICE_PARAMETER_TLS_VERIFY_PUBKEY "pubkey" + +/** + * The TLS verification value from Guacamole Client that indicates that subject + * verification should be performed. + */ +#define GUAC_SPICE_PARAMETER_TLS_VERIFY_SUBJECT "subject" + +/** + * The property within a SPICE client channel that indicates if the SPICE + * agent is connected. + */ +#define SPICE_PROPERTY_AGENT_CONNECTED "agent-connected" + +/** + * The SPICE client property that defines CA certificates used to validate + * the TLS connection to the SPICE server. + */ +#define SPICE_PROPERTY_CA "ca" + +/** + * The SPICE client property that defines a path on the server running guacd + * to the file containing the certificate authority certificates to use to + * validate the TLS connection to the SPICE server. + */ +#define SPICE_PROPERTY_CA_FILE "ca-file" + +/** + * The property that the SPICE client uses to set the image cache size. If + * undefined a default of 0 will be used. + */ +#define SPICE_PROPERTY_CACHE_SIZE "cache-size" + +/** + * The SPICE client channel property that stores the identifier of the channel. + */ +#define SPICE_PROPERTY_CHANNEL_ID "channel-id" + +/** + * SPICE library property that determines whether or not the sockets are provided + * by the client. + */ +#define SPICE_PROPERTY_CLIENT_SOCKETS "client-sockets" + +/** + * The property that tells the SPICE client the color depth to use when + * allocating new displays. + */ +#define SPICE_PROPERTY_COLOR_DEPTH "color-depth" + +/** + * The property that tells the SPICE client to enable audio playback and + * recording. The SPICE client default is TRUE. + */ +#define SPICE_PROPERTY_ENABLE_AUDIO "enable-audio" + +/** + * Property that enables or disables USB redirection. + */ +#define SPICE_PROPERTY_ENABLE_USBREDIR "enable-usbredir" + +/** + * The property that contains the hostname, IP address, or URL of the SPICE + * server that the client should attempt to connect to. + */ +#define SPICE_PROPERTY_HOST "host" + +/** + * A read-only property exposed by the SPICE client library indicating the + * current state of key modifiers - such as lock keys - on the server. + */ +#define SPICE_PROPERTY_KEY_MODIFIERS "key-modifiers" + +/** + * The property that indicates the minimum latency for audio playback. + */ +#define SPICE_PROPERTY_MIN_LATENCY "min-latency" + +/** + * The property used to toggle the playback and/or record + * mute status on the SPICE server. + */ +#define SPICE_PROPERTY_MUTE "mute" + +/** + * The property used to get or set the number of audio playback and/or recording + * channels that will be available between the SPICE server and client. + */ +#define SPICE_PROPERTY_NUM_CHANNELS "nchannels" + +/** + * The property used to tell the SPICE client the password to send on to the + * SPICE server for authentication. + */ +#define SPICE_PROPERTY_PASSWORD "password" + +/** + * The property used to set the unencrypted communication port for communicating + * with the SPICE server. + */ +#define SPICE_PROPERTY_PORT "port" + +/** + * The property that the SPICE client uses to set the proxy server that is used + * to connect to the SPICE server. + */ +#define SPICE_PROPERTY_PROXY "proxy" + +/** + * The property used by the SPICE client to tell the server that the session + * should be read-only. + */ +#define SPICE_PROPERTY_READ_ONLY "read-only" + +/** + * The property that the SPICE client uses to determine a local (to guacd) + * directory that will be shared with the SPICE server. + */ +#define SPICE_PROPERTY_SHARED_DIR "shared-dir" + +/** + * The property that tells the SPICE client that the shared directory should be + * read-only to the SPICE server and should not allow writes. + */ +#define SPICE_PROPERTY_SHARED_DIR_RO "share-dir-ro" + +/** + * The property within the SPICE client that is used to set the port used for + * secure, TLS-based communication with the SPICE server. + */ +#define SPICE_PROPERTY_TLS_PORT "tls-port" + +/** + * The property that is used to set the username that the SPICE client will use + * to authenticate with the server. + */ +#define SPICE_PROPERTY_USERNAME "username" + +/** + * The property that tells the SPICE client whether or not to verify the + * certificate presented by the SPICE server in TLS communications. + */ +#define SPICE_PROPERTY_VERIFY "verify" + +/** + * The property used to get or set the playback and/or recording volume of audio + * on the SPICE server to the remote client. + */ +#define SPICE_PROPERTY_VOLUME "volume" + +/** + * The signal sent by the SPICE client when a new channel is created. + */ +#define SPICE_SIGNAL_CHANNEL_NEW "channel-new" + +/** + * The signal sent by the SPICE client when a channel is destroyed. + */ +#define SPICE_SIGNAL_CHANNEL_DESTROY "channel-destroy" + +/** + * The signal sent by the SPICE client when an event occurs on a channel. + */ +#define SPICE_SIGNAL_CHANNEL_EVENT "channel-event" + +/** + * A signal that indicates that the cursor should be hidden from the display + * area. + */ +#define SPICE_SIGNAL_CURSOR_HIDE "cursor-hide" + +/** + * A signal that indicates a change in position of the cursor in the display + * area. + */ +#define SPICE_SIGNAL_CURSOR_MOVE "cursor-move" + +/** + * A signal that indicates the cursor should be reset to its default context. + */ +#define SPICE_SIGNAL_CURSOR_RESET "cursor-reset" + +/** + * A signal sent to modify cursor aspect and position within the display area. + */ +#define SPICE_SIGNAL_CURSOR_SET "cursor-set" + +/** + * The signal sent by the SPICE client when the client is disconnected from + * the server. + */ +#define SPICE_SIGNAL_DISCONNECTED "disconnected" + +/** + * The signal sent to indicate that a region of the display should be updated. + */ +#define SPICE_SIGNAL_DISPLAY_INVALIDATE "display-invalidate" + +/** + * The signal that indicates that a display is ready to be exposed to the client. + */ +#define SPICE_SIGNAL_DISPLAY_MARK "display-mark" + +/** + * The signal indicating when the primary display data/buffer is ready. + */ +#define SPICE_SIGNAL_DISPLAY_PRIMARY_CREATE "display-primary-create" + +/** + * The signal indicating when the primary display surface should be freed and + * not made available anymore. + */ +#define SPICE_SIGNAL_DISPLAY_PRIMARY_DESTROY "display-primary-destroy" + +/** + * The signal indicating that a rectangular region of he display is updated + * and should be redrawn. + */ +#define SPICE_SIGNAL_GL_DRAW "gl-draw" + +/** + * The signal sent to indicate that the keyboard modifiers - such as lock keys - + * have changed and should be updated. + */ +#define SPICE_SIGNAL_INPUTS_MODIFIERS "inputs-modifiers" + +/** + * The signal sent by the SPICE client when the connected status or capabilities + * of a channel change. + */ +#define SPICE_SIGNAL_MAIN_AGENT_UPDATE "main-agent-update" + +/** + * Signal fired by the SPICE client when clipboard selection data is available. + */ +#define SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION "main-clipboard-selection" + +/** + * A signal fired by the SPICE client when clipboard selection data is available + * from the guest, and of what type. + */ +#define SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_GRAB "main-clipboard-selection-grab" + +/** + * A signal fired by the SPICE client when clipboard selection data is no longer + * available from the guest. + */ +#define SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_RELEASE "main-clipboard-selection-release" + +/** + * A signal used to request clipboard data from the client. + */ +#define SPICE_SIGNAL_MAIN_CLIPBOARD_SELECTION_REQUEST "main-clipboard-selection-request" + +/** + * A signal used to indicate that the mouse mode has changed. + */ +#define SPICE_SIGNAL_MAIN_MOUSE_UPDATE "main-mouse-update" + +/** + * A signal sent by the SPICE client when the server has indicated that live + * migration has started. + */ +#define SPICE_SIGNAL_MIGRATION_STARTED "migration-started" + +/** + * The signal sent by the SPICE client when a MM time discontinuity is + * detected. + */ +#define SPICE_SIGNAL_MM_TIME_RESET "mm-time-reset" + +/** + * The signal fired by the SPICE client when a new file transfer task has been + * initiated. + */ +#define SPICE_SIGNAL_NEW_FILE_TRANSFER "new-file-transfer" + +/** + * The signal fired when data is available to be played on the client, which + * contains a pointer to the data to be played. + */ +#define SPICE_SIGNAL_PLAYBACK_DATA "playback-data" + +/** + * A signal sent when the server is requesting the current audio playback delay. + */ +#define SPICE_SIGNAL_PLAYBACK_GET_DELAY "playback-get-delay" + +/** + * A signal sent when the server is notifying the client that audio playback + * should begin, which also contains characteristics of that audio data. + */ +#define SPICE_SIGNAL_PLAYBACK_START "plaback-start" + +/** + * A signal sent when audio playback should cease. + */ +#define SPICE_SIGNAL_PLAYBACK_STOP "playback-stop" + +/** + * A signal indicating that the SPICE server would like to capture audio data + * from the client, along with the required format of that data. + */ +#define SPICE_SIGNAL_RECORD_START "record-start" + +/** + * A signal indicating that audio capture should cease. + */ +#define SPICE_SIGNAL_RECORD_STOP "record-stop" + +/** + * The signal indicating that the SPICE server has gone to streaming mode. + */ +#define SPICE_SIGNAL_STREAMING_MODE "streaming-mode" + +#endif /* SPICE_CONSTANTS_H */ + diff --git a/src/protocols/spice/spice-defaults.h b/src/protocols/spice/spice-defaults.h new file mode 100644 index 00000000..aa60c423 --- /dev/null +++ b/src/protocols/spice/spice-defaults.h @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef SPICE_DEFAULTS_H +#define SPICE_DEFAULTS_H + +/** + * The default hostname to connect to if none is specified. + */ +#define SPICE_DEFAULT_HOST "localhost" + +/** + * The default SPICE port number to connect to if none is specified. + */ +#define SPICE_DEFAULT_PORT "5900" + +/** + * The default encodings to use for the SPICE clipboard. + */ +#define SPICE_DEFAULT_ENCODINGS "zrle ultra copyrect hextile zlib corre rre raw" + +/** + * The default SFTP port to connect to if SFTP is enabled. + */ +#define SPICE_DEFAULT_SFTP_PORT "22" + +/** + * The default root directory to limit SFTP access to. + */ +#define SPICE_DEFAULT_SFTP_ROOT "/" + +#endif /* SPICE_DEFAULTS_H */ + diff --git a/src/protocols/spice/spice.c b/src/protocols/spice/spice.c new file mode 100644 index 00000000..f7e69e5a --- /dev/null +++ b/src/protocols/spice/spice.c @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "auth.h" +#include "client.h" +#include "common/clipboard.h" +#include "common/cursor.h" +#include "common/display.h" +#include "channels/audio.h" +#include "channels/clipboard.h" +#include "channels/cursor.h" +#include "channels/display.h" +#include "channels/file.h" +#include "log.h" +#include "settings.h" +#include "spice.h" +#include "spice-constants.h" + +#ifdef ENABLE_COMMON_SSH +#include "common-ssh/sftp.h" +#include "common-ssh/ssh.h" +#include "sftp.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +SpiceSession* guac_spice_get_session(guac_client* client) { + + guac_client_log(client, GUAC_LOG_DEBUG, "Initializing new SPICE session."); + + /* Set up the SPICE session and Guacamole client. */ + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_spice_settings* spice_settings = spice_client->settings; + + /* Create a new SPICE client. */ + SpiceSession* spice_session = spice_session_new(); + + guac_client_log(client, GUAC_LOG_DEBUG, "Registering new channel callback."); + + /* Register a callback for handling new channel events. */ + g_signal_connect(spice_session, SPICE_SIGNAL_CHANNEL_NEW, + G_CALLBACK(guac_spice_client_channel_handler), client); + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up connection properties."); + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up host/port."); + + /* Set hostname and port */ + g_object_set(spice_session, SPICE_PROPERTY_HOST, spice_settings->hostname, NULL); + guac_client_log(client, GUAC_LOG_DEBUG, "Connecting to host %s", + spice_settings->hostname); + if (spice_settings->tls) { + guac_client_log(client, GUAC_LOG_DEBUG, "Using TLS mode on port %s", + spice_settings->port); + g_object_set(spice_session, + SPICE_PROPERTY_TLS_PORT, spice_settings->port, + SPICE_PROPERTY_VERIFY, spice_settings->tls_verify, + NULL); + if (spice_settings->ca != NULL) + g_object_set(spice_session, SPICE_PROPERTY_CA, spice_settings->ca, NULL); + if (spice_settings->ca_file != NULL) + g_object_set(spice_session, SPICE_PROPERTY_CA_FILE, spice_settings->ca_file, NULL); + } + else { + guac_client_log(client, GUAC_LOG_DEBUG, "Using plaintext mode on port %s", + spice_settings->port); + g_object_set(spice_session, + SPICE_PROPERTY_PORT, spice_settings->port, NULL); + } + + guac_client_log(client, GUAC_LOG_DEBUG, "Setting up keyboard layout: %s", + spice_settings->server_layout); + + /* Load keymap into client */ + spice_client->keyboard = guac_spice_keyboard_alloc(client, + spice_settings->server_layout); + + guac_client_log(client, GUAC_LOG_DEBUG, "Finished setting properties."); + + /* Return the configured session. */ + return spice_session; + +} + +void* guac_spice_client_thread(void* data) { + + guac_client* client = (guac_client*) data; + guac_spice_client* spice_client = (guac_spice_client*) client->data; + guac_spice_settings* settings = spice_client->settings; + spice_client->spice_mainloop = g_main_loop_new(NULL, false); + + /* Attempt connection */ + guac_client_log(client, GUAC_LOG_DEBUG, "Attempting initial connection to SPICE server."); + spice_client->spice_session = guac_spice_get_session(client); + int retries_remaining = settings->retries; + + /* If unsuccessful, retry as many times as specified */ + while (spice_client->spice_session == NULL && retries_remaining > 0) { + + guac_client_log(client, GUAC_LOG_INFO, + "Connect failed. Waiting %ims before retrying...", + GUAC_SPICE_CONNECT_INTERVAL); + + /* Wait for given interval then retry */ + guac_timestamp_msleep(GUAC_SPICE_CONNECT_INTERVAL); + spice_client->spice_session = guac_spice_get_session(client); + retries_remaining--; + + } + + /* If the final connect attempt fails, return error */ + if (spice_client->spice_session == NULL) { + guac_client_abort(client, GUAC_PROTOCOL_STATUS_UPSTREAM_NOT_FOUND, + "Unable to connect to SPICE server."); + return NULL; + } + + guac_client_log(client, GUAC_LOG_DEBUG, "Configuration completed, flushing socket."); + + guac_socket_flush(client->socket); + + // guac_timestamp last_frame_end = guac_timestamp_current(); + + guac_client_log(client, GUAC_LOG_DEBUG, "Connection configuration finished, calling spice_session_connect."); + + if(!spice_session_connect(spice_client->spice_session)) + return NULL; + + guac_client_log(client, GUAC_LOG_DEBUG, "Session connected, entering main loop."); + + /* Handle messages from SPICE server while client is running */ + while (client->state == GUAC_CLIENT_RUNNING) { + + /* Run the main loop. */ + g_main_loop_run(spice_client->spice_mainloop); + guac_client_log(client, GUAC_LOG_DEBUG, "Finished main loop."); + + /* Wait for an error on the main channel. */ + if (spice_client->main_channel != NULL + && spice_channel_get_error(SPICE_CHANNEL(spice_client->main_channel)) != NULL) + break; + } + + guac_client_log(client, GUAC_LOG_DEBUG, "Exited main loop, cleaning up."); + + /* Kill client and finish connection */ + if (spice_client->spice_session != NULL) { + guac_client_log(client, GUAC_LOG_DEBUG, "Cleaning up SPICE session."); + spice_session_disconnect(spice_client->spice_session); + g_object_unref(spice_client->spice_session); + spice_client->spice_session = NULL; + } + guac_client_stop(client); + guac_client_log(client, GUAC_LOG_INFO, "Internal SPICE client disconnected"); + return NULL; + +} diff --git a/src/protocols/spice/spice.h b/src/protocols/spice/spice.h new file mode 100644 index 00000000..7a36ac6f --- /dev/null +++ b/src/protocols/spice/spice.h @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_H +#define GUAC_SPICE_H + +#include "config.h" + +#include "common/clipboard.h" +#include "common/display.h" +#include "common/iconv.h" +#include "common/surface.h" +#include "keyboard.h" +#include "settings.h" + +#include +#include +#include +#include +#include + +#ifdef ENABLE_COMMON_SSH +#include "common-ssh/sftp.h" +#include "common-ssh/ssh.h" +#include "common-ssh/user.h" +#endif + +#include + +/** + * SPICE-specific client data. + */ +typedef struct guac_spice_client { + + /** + * The SPICE client thread. + */ + pthread_t client_thread; + + /** + * The underlying SPICE session. + */ + SpiceSession* spice_session; + + /** + * The main SPICE channel. + */ + SpiceMainChannel* main_channel; + + /** + * The SPICE audio playback channel. + */ + SpicePlaybackChannel* playback_channel; + + /** + * The SPICE audio recording/input channel. + */ + SpiceRecordChannel* record_channel; + + /** + * The SPICE channel that handles the cursor display and events. + */ + SpiceCursorChannel* cursor_channel; + + /** + * The SPICE channel that handles mouse and keyboard inputs. + */ + SpiceInputsChannel* inputs_channel; + + /** + * Client settings, parsed from args. + */ + guac_spice_settings* settings; + + /** + * The current display state. + */ + guac_common_display* display; + + /** + * The SPICE display channel. + */ + SpiceDisplayChannel* spice_display; + + /** + * The current state of the keyboard with respect to the RDP session. + */ + guac_spice_keyboard* keyboard; + + /** + * The glib main loop + */ + GMainLoop* spice_mainloop; + + /** + * Internal clipboard. + */ + guac_common_clipboard* clipboard; + +#ifdef ENABLE_COMMON_SSH + /** + * The user and credentials used to authenticate for SFTP. + */ + guac_common_ssh_user* sftp_user; + + /** + * The SSH session used for SFTP. + */ + guac_common_ssh_session* sftp_session; + + /** + * An SFTP-based filesystem. + */ + guac_common_ssh_sftp_filesystem* sftp_filesystem; +#endif + + /** + * The in-progress session recording, or NULL if no recording is in + * progress. + */ + guac_recording* recording; + + /** + * Clipboard encoding-specific reader. + */ + guac_iconv_read* clipboard_reader; + + /** + * Clipboard encoding-specific writer. + */ + guac_iconv_write* clipboard_writer; + + /** + * Common attributes for locks. + */ + pthread_mutexattr_t attributes; + + /** + * Lock which is used to synchronizes access to SPICE data structures + * between user input and client threads. It prevents input handlers + * from running when SPICE data structures are allocated or freed + * by the client thread. + */ + pthread_rwlock_t lock; + + /** + * Lock which synchronizes the sending of each SPICE message, ensuring + * attempts to send SPICE messages never overlap. + */ + pthread_mutex_t message_lock; + +} guac_spice_client; + +/** + * Allocates a new rfbClient instance given the parameters stored within the + * client, returning NULL on failure. + * + * @param client + * The guac_client associated with the settings of the desired SPICE + * connection. + * + * @return + * A new rfbClient instance allocated and connected according to the + * parameters stored within the given client, or NULL if connecting to the + * SPICE server fails. + */ +SpiceSession* guac_spice_get_session(guac_client* client); + +/** + * SPICE client thread. This thread initiates the SPICE connection and + * ultimately runs throughout the duration of the client, existing as a single + * instance, shared by all users. + * + * @param data + * The guac_client instance associated with the requested SPICE connection. + * + * @return + * Always NULL. + */ +void* guac_spice_client_thread(void* data); + +#endif /* GUAC_SPICE_H */ + diff --git a/src/protocols/spice/user.c b/src/protocols/spice/user.c new file mode 100644 index 00000000..d54044bd --- /dev/null +++ b/src/protocols/spice/user.c @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include "config.h" + +#include "channels/clipboard.h" +#include "input.h" +#include "common/display.h" +#include "common/dot_cursor.h" +#include "common/pointer_cursor.h" +#include "user.h" +#include "sftp.h" +#include "spice.h" + +#include +#include +#include +#include +#include + +#include + +int guac_spice_user_join_handler(guac_user* user, int argc, char** argv) { + + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + + /* Parse provided arguments */ + guac_spice_settings* settings = guac_spice_parse_args(user, + argc, (const char**) argv); + + /* Fail if settings cannot be parsed */ + if (settings == NULL) { + guac_user_log(user, GUAC_LOG_INFO, + "Badly formatted client arguments."); + return 1; + } + + /* Store settings at user level */ + user->data = settings; + + /* Connect via SPICE if owner */ + if (user->owner) { + + /* Store owner's settings at client level */ + spice_client->settings = settings; + + /* Start client thread */ + if (pthread_create(&spice_client->client_thread, NULL, guac_spice_client_thread, user->client)) { + guac_user_log(user, GUAC_LOG_ERROR, "Unable to start SPICE client thread."); + return 1; + } + + } + + /* If not owner, synchronize with current state */ + else { + + /* Synchronize with current display */ + guac_common_display_dup(spice_client->display, user, user->socket); + guac_socket_flush(user->socket); + + } + + /* Only handle events if not read-only */ + if (!settings->read_only) { + + /* General mouse/keyboard events */ + user->mouse_handler = guac_spice_user_mouse_handler; + user->key_handler = guac_spice_user_key_handler; + + /* Inbound (client to server) clipboard transfer */ + if (!settings->disable_paste) + user->clipboard_handler = guac_spice_clipboard_handler; + + /* Updates to connection parameters if we own the connection */ + if (user->owner) + user->argv_handler = guac_argv_handler; + +#ifdef ENABLE_COMMON_SSH + /* Set generic (non-filesystem) file upload handler */ + if (settings->enable_sftp && !settings->sftp_disable_upload) + user->file_handler = guac_spice_sftp_file_handler; +#endif + + } + + return 0; + +} + +int guac_spice_user_leave_handler(guac_user* user) { + + guac_spice_client* spice_client = (guac_spice_client*) user->client->data; + + if (spice_client->display) { + /* Update shared cursor state */ + guac_common_cursor_remove_user(spice_client->display->cursor, user); + } + + /* Free settings if not owner (owner settings will be freed with client) */ + if (!user->owner) { + guac_spice_settings* settings = (guac_spice_settings*) user->data; + guac_spice_settings_free(settings); + } + + return 0; +} + diff --git a/src/protocols/spice/user.h b/src/protocols/spice/user.h new file mode 100644 index 00000000..2b97496d --- /dev/null +++ b/src/protocols/spice/user.h @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#ifndef GUAC_SPICE_USER_H +#define GUAC_SPICE_USER_H + +#include "config.h" + +#include + +/** + * Handler for joining users. + */ +guac_user_join_handler guac_spice_user_join_handler; + +/** + * Handler for leaving users. + */ +guac_user_leave_handler guac_spice_user_leave_handler; + +#endif /* SPICE_USER_H */ +