GUACAMOLE-261: Implement Spice protocol support.

This commit is contained in:
Virtually Nick 2021-01-01 10:05:20 -05:00
parent b20afa275a
commit c6263a25fd
67 changed files with 9876 additions and 0 deletions

View File

@ -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

View File

@ -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}

View File

@ -0,0 +1,139 @@
#
# 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 \
channels/file-download.c \
channels/file-ls.c \
channels/file-upload.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 \
channels/file-download.h \
channels/file-ls.h \
channels/file-upload.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

View File

@ -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 <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@ -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 <guacamole/argv.h>
#include <guacamole/user.h>
/**
* 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 */

View File

@ -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 <guacamole/argv.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <glib-unix.h>
#include <pthread.h>
#include <string.h>
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;
}

View File

@ -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 <guacamole/client.h>
#include <glib-unix.h>
/**
* 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 */

View File

@ -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.
*/
#include "config.h"
#include "audio.h"
#include "spice.h"
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <errno.h>
#include <time.h>
void guac_spice_client_audio_playback_data_handler(
SpicePlaybackChannel* channel, gpointer data, gint size,
guac_client* client) {
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_audio_stream_write_pcm(spice_client->audio_playback, data, size);
}
void guac_spice_client_audio_playback_delay_handler(
SpicePlaybackChannel* channel, guac_client* client) {
guac_client_log(client, GUAC_LOG_WARNING,
"Delay handler for audio playback is not currently implemented.");
}
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, "Starting audio playback.");
guac_client_log(client, GUAC_LOG_DEBUG, "Format: %d", format);
guac_client_log(client, GUAC_LOG_DEBUG, "Channels: %d", channels);
guac_client_log(client, GUAC_LOG_DEBUG, "Rate: %d", rate);
/* Spice only supports a single audio format. */
if (format != SPICE_AUDIO_FMT_S16) {
guac_client_log(client, GUAC_LOG_WARNING, "Unknown Spice audio format: %d", format);
return;
}
/* Allocate the stream. */
guac_spice_client* spice_client = (guac_spice_client*) client->data;
spice_client->audio_playback = guac_audio_stream_alloc(client, NULL, rate, channels, 16);
}
void guac_spice_client_audio_playback_stop_handler(
SpicePlaybackChannel* channel, guac_client* client) {
guac_client_log(client, GUAC_LOG_DEBUG, "Stoppig audio playback..");
/* Free the audio stream. */
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_audio_stream_free(spice_client->audio_playback);
}
/**
* Parses the given raw audio mimetype, producing the corresponding rate,
* number of channels, and bytes per sample.
*
* @param mimetype
* The raw audio mimetype to parse.
*
* @param rate
* A pointer to an int where the sample rate for the PCM format described
* by the given mimetype should be stored.
*
* @param channels
* A pointer to an int where the number of channels used by the PCM format
* described by the given mimetype should be stored.
*
* @param bps
* A pointer to an int where the number of bytes used the PCM format for
* each sample (independent of number of channels) described by the given
* mimetype should be stored.
*
* @return
* Zero if the given mimetype is a raw audio mimetype and has been parsed
* successfully, non-zero otherwise.
*/
static int guac_spice_audio_parse_mimetype(const char* mimetype, int* rate,
int* channels, int* bps) {
int parsed_rate = -1;
int parsed_channels = 1;
int parsed_bps;
/* PCM audio with two bytes per sample */
if (strncmp(mimetype, "audio/L16;", 10) == 0) {
mimetype += 9; /* Advance to semicolon ONLY */
parsed_bps = 2;
}
/* Unsupported mimetype */
else
return 1;
/* Parse each parameter name/value pair within the mimetype */
do {
/* Advance to first character of parameter (current is either a
* semicolon or a comma) */
mimetype++;
/* Parse number of channels */
if (strncmp(mimetype, "channels=", 9) == 0) {
mimetype += 9;
parsed_channels = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Parse number of rate */
else if (strncmp(mimetype, "rate=", 5) == 0) {
mimetype += 5;
parsed_rate = strtol(mimetype, (char**) &mimetype, 10);
/* Fail if value invalid / out of range */
if (errno == EINVAL || errno == ERANGE)
return 1;
}
/* Advance to next parameter */
mimetype = strchr(mimetype, ',');
} while (mimetype != NULL);
/* Mimetype is invalid if rate was not specified */
if (parsed_rate == -1)
return 1;
/* Parse success */
*rate = parsed_rate;
*channels = parsed_channels;
*bps = parsed_bps;
return 0;
}
/**
* A callback function that is invoked to send audio data from the given
* stream to the Spice server.
*
* @param user
* The user who owns the connection and the stream. This is unused by
* this function.
*
* @param stream
* The stream where the audio data originated. This is unused by this
* function.
*
* @param data
* The audio data to send.
*
* @param length
* The number of bytes of audio data to send.
*
* @return
* Zero on success, non-zero on error.
*/
static int guac_spice_audio_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
/* Write blob to audio stream */
spice_record_channel_send_data(spice_client->record_channel, data, length, (unsigned long) time(NULL));
return 0;
}
/**
* A callback function that is called when the audio stream ends sending data
* to the Spice server.
*
* @param user
* The user who owns the connection and the stream.
*
* @param stream
* The stream that was sending the audio data.
*
* @return
* Zero on success, non-zero on failure.
*/
static int guac_spice_audio_end_handler(guac_user* user, guac_stream* stream) {
/* Ignore - the RECORD_CHANNEL channel will simply not receive anything */
return 0;
}
int guac_spice_client_audio_record_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_user_log(user, GUAC_LOG_DEBUG, "Calling audio input handler.");
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
spice_client->audio_input = stream;
int rate;
int channels;
int bps;
/* Parse mimetype, abort on parse error */
if (guac_spice_audio_parse_mimetype(mimetype, &rate, &channels, &bps)) {
guac_user_log(user, GUAC_LOG_WARNING, "Denying user audio stream with "
"unsupported mimetype: \"%s\"", mimetype);
guac_protocol_send_ack(user->socket, stream, "Unsupported audio "
"mimetype", GUAC_PROTOCOL_STATUS_CLIENT_BAD_TYPE);
return 0;
}
/* Initialize stream handlers */
stream->blob_handler = guac_spice_audio_blob_handler;
stream->end_handler = guac_spice_audio_end_handler;
return 0;
}
/**
* Sends an "ack" instruction over the socket associated with the Guacamole
* stream over which audio data is being received. The "ack" instruction will
* only be sent if the Guacamole audio stream has been established (through
* receipt of an "audio" instruction), is still open (has not received an "end"
* instruction nor been associated with an "ack" having an error code), and is
* associated with an active Spice RECORD_CHANNEL channel.
*
* @param user
* The guac_user associated with the audio input stream.
*
* @param stream
* The guac_stream associated with the audio input for the client.
*
* @param message
* An arbitrary human-readable message to send along with the "ack".
*
* @param status
* The Guacamole protocol status code to send with the "ack". This should
* be GUAC_PROTOCOL_STATUS_SUCCESS if the audio stream has been set up
* successfully or GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED if the audio stream
* has been closed (but may usable again if reopened).
*/
static void guac_spice_audio_stream_ack(guac_user* user, guac_stream* stream,
const char* message, guac_protocol_status status) {
/* Do not send if the connection owner or stream is null. */
if (user == NULL || stream == NULL)
return;
/* Send ack instruction */
guac_protocol_send_ack(user->socket, stream, message, status);
guac_socket_flush(user->socket);
}
/**
* A callback that is invoked for the connection owner when audio recording
* starts, which will notify the client the owner is connected from to start
* sending audio data.
*
* @param owner
* The owner of the connection.
*
* @param data
* A pointer to the guac_client associated with this connection.
*
* @return
* Always NULL;
*/
static void* spice_client_record_start_callback(guac_user* owner, void* data) {
guac_spice_client* spice_client = (guac_spice_client*) data;
guac_spice_audio_stream_ack(owner, spice_client->audio_input, "OK",
GUAC_PROTOCOL_STATUS_SUCCESS);
return NULL;
}
/**
* A callback that is invoked for the connection owner when audio recording
* is stopped, telling the client to stop sending audio data.
*
* @param owner
* The user who owns this connection.
*
* @param data
* A pointer to the guac_client associated with this connection.
*
* @return
* Always NULL;
*/
static void* spice_client_record_stop_callback(guac_user* owner, void* data) {
guac_spice_client* spice_client = (guac_spice_client*) data;
/* The stream is now closed */
guac_spice_audio_stream_ack(owner, spice_client->audio_input, "CLOSED",
GUAC_PROTOCOL_STATUS_RESOURCE_CLOSED);
return NULL;
}
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.");
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_client_for_owner(client, spice_client_record_start_callback, spice_client);
}
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.");
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_client_for_owner(client, spice_client_record_stop_callback, spice_client);
}

View File

@ -0,0 +1,138 @@
/*
* 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 <guacamole/client.h>
#include <spice-client-glib-2.0/spice-client.h>
/**
* 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);
/**
* Handler for inbound audio data (audio input).
*/
guac_user_audio_handler guac_spice_client_audio_record_handler;
/**
* The callback function invoked by the Spice 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 Spice 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 */

View File

@ -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 "client.h"
#include "clipboard.h"
#include "common/clipboard.h"
#include "common/iconv.h"
#include "spice.h"
#include "spice-constants.h"
#include "user.h"
#include <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <spice-1/spice/vd_agent.h>
int guac_spice_clipboard_handler(guac_user* user, guac_stream* stream,
char* mimetype) {
guac_spice_client* spice_client = (guac_spice_client*) user->client->data;
/* Some versions of VDAgent do not support sending clipboard data. */
if (!spice_main_channel_agent_test_capability(spice_client->main_channel, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND)) {
guac_client_log(user->client, GUAC_LOG_WARNING, "Spice guest agent does"
" not support sending clipboard data on demand.");
return 0;
}
/* Clear the current clipboard and send the grab command to the agent. */
guac_common_clipboard_reset(spice_client->clipboard, mimetype);
guint32 clipboard_types[] = { VD_AGENT_CLIPBOARD_UTF8_TEXT };
spice_main_channel_clipboard_selection_grab(spice_client->main_channel,
VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD, clipboard_types, 1);
/* 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) {
/* 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_spice_client* spice_client = (guac_spice_client*) user->client->data;
/* Send via Spice 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*) spice_client->clipboard->buffer,
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_spice_client* spice_client = (guac_spice_client*) client->data;
/* Loop through clipboard types - currently Guacamole only supports text. */
switch (type) {
case VD_AGENT_CLIPBOARD_UTF8_TEXT:
guac_client_log(client, GUAC_LOG_DEBUG, "Notifying client of text "
" on clipboard from server: %s", (char *) data);
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.");
}
guac_common_clipboard_send(spice_client->clipboard, client);
}
void guac_spice_clipboard_selection_grab_handler(SpiceMainChannel* channel,
guint selection, guint32* types, guint ntypes, 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);
/* Ignore selection types other than clipboard. */
if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
guac_client_log(client, GUAC_LOG_WARNING, "Unsupported clipboard grab type: %d", selection);
return;
}
/* Loop through the data types sent by the Spice server and process them. */
for (int i = 0; i < ntypes; i++) {
/* Currently Guacamole only supports text. */
if (types[i] != VD_AGENT_CLIPBOARD_UTF8_TEXT) {
guac_client_log(client, GUAC_LOG_WARNING, "Unsupported clipboard data type: %d", types[i]);
continue;
}
/* Reset our clipboard and request the data from the Spice serer. */
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_common_clipboard_reset(spice_client->clipboard, "text/plain");
spice_main_channel_clipboard_selection_request(channel, selection, types[i]);
}
}
void guac_spice_clipboard_selection_release_handler(SpiceMainChannel* channel,
guint selection, guac_client* client) {
guac_client_log(client, GUAC_LOG_DEBUG, "Notifying client of clipboard"
" release in the guest.");
/* Transfer data from guest to Guacamole clipboard. */
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_common_clipboard_send(spice_client->clipboard, client);
}
void guac_spice_clipboard_selection_request_handler(SpiceMainChannel* channel,
guint selection, guint type, guac_client* client) {
guac_client_log(client, GUAC_LOG_DEBUG, "Requesting clipboard data from"
" the client.");
/* Guacamole only supports one clipboard selection type. */
if (selection != VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
guac_client_log(client, GUAC_LOG_WARNING, "Unsupported selection type: %d", selection);
return;
}
/* Currently Guacamole only implements text support - other types are images. */
if (type != VD_AGENT_CLIPBOARD_UTF8_TEXT) {
guac_client_log(client, GUAC_LOG_WARNING, "Unsupported clipboard data type: %d", type);
return;
}
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_client_log(client, GUAC_LOG_DEBUG, "Sending clipboard data to server: %s", spice_client->clipboard->buffer);
/* Send the clipboard data to the guest. */
spice_main_channel_clipboard_selection_notify(channel,
selection,
type,
(const unsigned char*) spice_client->clipboard->buffer,
spice_client->clipboard->length);
}

View File

@ -0,0 +1,131 @@
/*
* 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 <guacamole/client.h>
#include <guacamole/user.h>
#include <spice-client-glib-2.0/spice-client.h>
/**
* 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, guint32* types, guint ntypes, 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, 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 type
* 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 type, guac_client* client);
#endif /* GUAC_SPICE_CLIPBOARD_H */

View File

@ -0,0 +1,78 @@
/*
* 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 <cairo/cairo.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <syslog.h>
void guac_spice_cursor_hide(SpiceChannel* channel, guac_client* client) {
guac_client_log(client, GUAC_LOG_TRACE, "Hiding the cursor.");
/* Set the cursor to a blank image, hiding it. */
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: %d, %d", x, y);
/* Update the cursor with the new coordinates. */
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);
}

View File

@ -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 <spice-client-glib-2.0/spice-client.h>
/**
* 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 Spice 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 Spice 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 */

View File

@ -0,0 +1,167 @@
/*
* 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 <cairo/cairo.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <syslog.h>
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_TRACE,
"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_TRACE, "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, which currently has no effect.");
}
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.");
/* Allocate the Guacamole display. */
guac_spice_client* spice_client = (guac_spice_client*) client->data;
spice_client->display = guac_common_display_alloc(client,
width, height);
/* Create a matching Cairo image surface. */
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);
/* Flush the default surface. */
guac_client_log(client, GUAC_LOG_TRACE, "Flushing the default surface.");
guac_common_surface_flush(spice_client->display->default_surface);
/* Mark the end of the frame and flush the socket. */
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.");
/* Free the Guacamole 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;
}

View File

@ -0,0 +1,187 @@
/*
* 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 <spice-client-glib-2.0/spice-client.h>
/* 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
/**
* 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 */

View File

@ -0,0 +1,321 @@
/*
* 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 "common/json.h"
#include "file-download.h"
#include "file-ls.h"
#include "file.h"
#include "spice.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/string.h>
#include <guacamole/user.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/inotify.h>
void* guac_spice_file_download_monitor(void* data) {
guac_spice_folder* folder = (guac_spice_folder*) data;
char download_path[GUAC_SPICE_FOLDER_MAX_PATH];
char download_events[GUAC_SPICE_FOLDER_MAX_EVENTS];
char file_path[GUAC_SPICE_FOLDER_MAX_PATH];
const struct inotify_event *event;
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Starting up file monitor thread.", __func__);
/* If folder has already been freed, or isn't open, yet, don't do anything. */
if (folder == NULL)
return NULL;
download_path[0] = '\0';
guac_strlcat(download_path, folder->path, GUAC_SPICE_FOLDER_MAX_PATH);
guac_strlcat(download_path, "/Download", GUAC_SPICE_FOLDER_MAX_PATH);
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Watching folder at path \"%s\".", __func__, download_path);
int notify = inotify_init();
if (notify == -1) {
guac_client_log(folder->client, GUAC_LOG_ERROR,
"%s: Failed to start inotify, automatic downloads will not work: %s",
__func__, strerror(errno));
return NULL;
}
if(inotify_add_watch(notify, download_path, IN_CREATE | IN_ATTRIB | IN_CLOSE_WRITE | IN_MOVED_TO | IN_ONLYDIR | IN_EXCL_UNLINK) == -1) {
guac_client_log(folder->client, GUAC_LOG_ERROR,
"%s: Failed to set inotify flags for \"%s\".",
__func__, download_path);
return NULL;
}
while (true) {
int events = read(notify, download_events, sizeof(download_events));
if (events == -1 && errno != EAGAIN) {
guac_client_log(folder->client, GUAC_LOG_ERROR,
"%s: Failed to read inotify events: %s",
__func__, strerror(errno));
return NULL;
}
if (events <= 0)
continue;
for (char* ptr = download_events; ptr < download_events + events; ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *) ptr;
if (event->mask & IN_ISDIR) {
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Ignoring event 0x%x for directory %s.", __func__, event->mask, event->name);
continue;
}
guac_client_log(folder->client, GUAC_LOG_ERROR,
"%s: 0x%x - Downloading the file: %s", __func__, event->mask, event->name, event->cookie);
file_path[0] = '\0';
guac_strlcat(file_path, "/Download/", GUAC_SPICE_FOLDER_MAX_PATH);
guac_strlcat(file_path, event->name, GUAC_SPICE_FOLDER_MAX_PATH);
// guac_client_for_owner(folder->client, guac_spice_file_download_to_user, file_path);
//int fileid = guac_spice_folder_open(folder, file_path, O_WRONLY, 0, 0);
// guac_spice_folder_delete(folder, fileid);
}
}
return NULL;
}
int guac_spice_file_download_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_spice_file_download_status* download_status = (guac_spice_file_download_status*) stream->data;
/* Get folder, return error if no folder */
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* If successful, read data */
if (status == GUAC_PROTOCOL_STATUS_SUCCESS) {
/* Attempt read into buffer */
char buffer[4096];
int bytes_read = guac_spice_folder_read(folder,
download_status->file_id,
download_status->offset, buffer, sizeof(buffer));
/* If bytes read, send as blob */
if (bytes_read > 0) {
download_status->offset += bytes_read;
guac_protocol_send_blob(user->socket, stream,
buffer, bytes_read);
}
/* If EOF, send end */
else if (bytes_read == 0) {
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(download_status);
}
/* Otherwise, fail stream */
else {
guac_user_log(user, GUAC_LOG_ERROR,
"Error reading file for download");
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
free(download_status);
}
guac_socket_flush(user->socket);
}
/* Otherwise, return stream to user */
else
guac_user_free_stream(user, stream);
return 0;
}
int guac_spice_file_download_get_handler(guac_user* user, guac_object* object,
char* name) {
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
int flags = 0;
/* Get folder, ignore request if no folder */
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL)
return 0;
flags |= O_RDONLY;
guac_user_log(user, GUAC_LOG_DEBUG, "%s: folder->path=%s, name=%s", __func__, folder->path, name);
/* Attempt to open file for reading */
int file_id = guac_spice_folder_open(folder, name, flags, 0, 0);
if (file_id < 0) {
guac_user_log(user, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* Get opened file */
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
if (file == NULL) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* If directory, send contents of directory */
if (S_ISDIR(file->stmode)) {
/* Create stream data */
guac_spice_file_ls_status* ls_status = malloc(sizeof(guac_spice_file_ls_status));
ls_status->folder = folder;
ls_status->file_id = file_id;
guac_strlcpy(ls_status->directory_name, name,
sizeof(ls_status->directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user);
stream->ack_handler = guac_spice_file_ls_ack_handler;
stream->data = ls_status;
/* Init JSON object state */
guac_common_json_begin_object(user, stream,
&ls_status->json_state);
/* Associate new stream with get request */
guac_protocol_send_body(user->socket, object, stream,
GUAC_USER_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents if downloads are allowed */
else if (!folder->disable_download) {
/* Create stream data */
guac_spice_file_download_status* download_status = malloc(sizeof(guac_spice_file_download_status));
download_status->file_id = file_id;
download_status->offset = 0;
/* Allocate stream for body */
guac_stream* stream = guac_user_alloc_stream(user);
stream->data = download_status;
stream->ack_handler = guac_spice_file_download_ack_handler;
/* Associate new stream with get request */
guac_protocol_send_body(user->socket, object, stream,
"application/octet-stream", name);
}
else
guac_client_log(client, GUAC_LOG_INFO, "Unable to download file "
"\"%s\", file downloads have been disabled.", name);
guac_socket_flush(user->socket);
return 0;
}
void* guac_spice_file_download_to_user(guac_user* user, void* data) {
/* Do not bother attempting the download if the user has left */
if (user == NULL)
return NULL;
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_spice_folder* folder = spice_client->shared_folder;
int flags = 0;
/* Ignore download if folder has been unloaded */
if (folder == NULL)
return NULL;
/* Ignore download if downloads have been disabled */
if (folder->disable_download) {
guac_client_log(client, GUAC_LOG_WARNING, "A download attempt has "
"been blocked due to downloads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
return NULL;
}
/* Attempt to open requested file */
char* path = (char*) data;
flags |= O_RDONLY;
int file_id = guac_spice_folder_open(folder, path,
flags, 0, 0);
/* If file opened successfully, start stream */
if (file_id >= 0) {
/* Associate stream with transfer status */
guac_stream* stream = guac_user_alloc_stream(user);
guac_spice_file_download_status* download_status = malloc(sizeof(guac_spice_file_download_status));
stream->data = download_status;
stream->ack_handler = guac_spice_file_download_ack_handler;
download_status->file_id = file_id;
download_status->offset = 0;
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Initiating download "
"of \"%s\"", __func__, path);
/* Begin stream */
guac_protocol_send_file(user->socket, stream,
"application/octet-stream", guac_spice_folder_basename(path));
guac_socket_flush(user->socket);
/* Download started successfully */
return stream;
}
/* Download failed */
guac_user_log(user, GUAC_LOG_ERROR, "Unable to download \"%s\"", path);
return NULL;
}

View File

@ -0,0 +1,83 @@
/*
* 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_DOWNLOAD_H
#define GUAC_SPICE_FILE_DOWNLOAD_H
#include "common/json.h"
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdint.h>
/**
* The transfer status of a file being downloaded.
*/
typedef struct guac_spice_file_download_status {
/**
* The file ID of the file being downloaded.
*/
int file_id;
/**
* The current position within the file.
*/
uint64_t offset;
} guac_spice_file_download_status;
/**
* Function which uses Linux's inotify facility to monitor the "Download"
* directory of a shared folder for changes and trigger the automatic download
* of that data to the Guacamole user who has access to the shared folder.
*
* @param data
* A pointer to the guac_spice_folder structure in which the Download
* folder is located.
*
* @return
* Always NULL
*/
void* guac_spice_file_download_monitor(void* data);
/**
* Handler for acknowledgements of receipt of data related to file downloads.
*/
guac_user_ack_handler guac_spice_file_download_ack_handler;
/**
* Handler for get messages. In context of downloads and the filesystem exposed
* via the Guacamole protocol, get messages request the body of a file within
* the filesystem.
*/
guac_user_get_handler guac_spice_file_download_get_handler;
/**
* Callback for guac_client_for_user() and similar functions which initiates a
* file download to a specific user if that user is still connected. The path
* for the file to be downloaded must be passed as the arbitrary data parameter
* for the function invoking this callback.
*/
guac_user_callback guac_spice_file_download_to_user;
#endif

View File

@ -0,0 +1,127 @@
/*
* 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 "file.h"
#include "file-ls.h"
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int guac_spice_file_ls_ack_handler(guac_user* user, guac_stream* stream,
char* message, guac_protocol_status status) {
int blob_written = 0;
const char* filename;
guac_spice_file_ls_status* ls_status = (guac_spice_file_ls_status*) stream->data;
guac_user_log(user, GUAC_LOG_DEBUG, "%s: folder=\"%s\"", __func__, ls_status->folder->path);
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_spice_folder_close(ls_status->folder, ls_status->file_id);
guac_user_free_stream(user, stream);
free(ls_status);
return 0;
}
/* While directory entries remain */
while ((filename = guac_spice_folder_read_dir(ls_status->folder,
ls_status->file_id)) != NULL
&& !blob_written) {
char absolute_path[GUAC_SPICE_FOLDER_MAX_PATH];
/* Skip current and parent directory entries */
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
/* Concatenate into absolute path - skip if invalid */
if (!guac_spice_folder_append_filename(absolute_path,
ls_status->directory_name, filename)) {
guac_user_log(user, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
guac_user_log(user, GUAC_LOG_DEBUG, "%s: absolute_path=\"%s\"", __func__, absolute_path);
/* Attempt to open file to determine type */
int flags = (0 | O_RDONLY);
int file_id = guac_spice_folder_open(ls_status->folder, absolute_path,
flags, 0, 0);
if (file_id < 0)
continue;
/* Get opened file */
guac_spice_folder_file* file = guac_spice_folder_get_file(ls_status->folder, file_id);
if (file == NULL) {
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Successful open produced "
"bad file_id: %i", __func__, file_id);
return 0;
}
/* Determine mimetype */
const char* mimetype;
if (S_ISDIR(file->stmode))
mimetype = GUAC_USER_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(user, stream,
&ls_status->json_state, absolute_path, mimetype);
guac_spice_folder_close(ls_status->folder, file_id);
}
/* Complete JSON and cleanup at end of directory */
if (filename == NULL) {
/* Complete JSON object */
guac_common_json_end_object(user, stream, &ls_status->json_state);
guac_common_json_flush(user, stream, &ls_status->json_state);
/* Clean up resources */
guac_spice_folder_close(ls_status->folder, ls_status->file_id);
free(ls_status);
/* Signal of stream */
guac_protocol_send_end(user->socket, stream);
guac_user_free_stream(user, stream);
}
guac_socket_flush(user->socket);
return 0;
}

View File

@ -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.
*/
#ifndef GUAC_RDP_LS_H
#define GUAC_RDP_LS_H
#include "common/json.h"
#include "file.h"
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdint.h>
/**
* The current state of a directory listing operation.
*/
typedef struct guac_spice_file_ls_status {
/**
* The filesystem associated with the directory being listed.
*/
guac_spice_folder* folder;
/**
* The file ID of the directory being listed.
*/
int file_id;
/**
* The absolute path of the directory being listed.
*/
char directory_name[GUAC_SPICE_FOLDER_MAX_PATH];
/**
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_spice_file_ls_status;
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation.
*/
guac_user_ack_handler guac_spice_file_ls_ack_handler;
#endif

View File

@ -0,0 +1,261 @@
/*
* 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 "file.h"
#include "spice.h"
#include "file-upload.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
/**
* Writes the given filename to the given upload path, sanitizing the filename
* and translating the filename to the root directory.
*
* @param filename
* The filename to sanitize and move to the root directory.
*
* @param path
* A pointer to a buffer which should receive the sanitized path. The
* buffer must have at least GUAC_RDP_FS_MAX_PATH bytes available.
*/
static void __generate_upload_path(const char* filename, char* path) {
int i;
/* Add initial backslash */
*(path++) = '\\';
for (i=1; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
/* Get current, stop at end */
char c = *(filename++);
if (c == '\0')
break;
/* Replace special characters with underscores */
if (c == '/' || c == '\\')
c = '_';
*(path++) = c;
}
/* Terminate path */
*path = '\0';
}
int guac_spice_file_upload_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;
int file_id;
char file_path[GUAC_SPICE_FOLDER_MAX_PATH];
/* Get filesystem, return error if no filesystem */
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FS)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* Ignore upload if uploads have been disabled */
if (folder->disable_upload) {
guac_client_log(client, GUAC_LOG_WARNING, "A upload attempt has "
"been blocked due to uploads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
guac_protocol_send_ack(user->socket, stream, "FAIL (UPLOAD DISABLED)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Translate name */
__generate_upload_path(filename, file_path);
/* Open file */
file_id = guac_spice_folder_open(folder, file_path, (O_WRONLY | O_CREAT | O_TRUNC),
1, 0);
if (file_id < 0) {
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Init upload status */
guac_spice_file_upload_status* upload_status = malloc(sizeof(guac_spice_file_upload_status));
upload_status->offset = 0;
upload_status->file_id = file_id;
stream->data = upload_status;
stream->blob_handler = guac_spice_file_upload_blob_handler;
stream->end_handler = guac_spice_file_upload_end_handler;
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
int guac_spice_file_upload_blob_handler(guac_user* user, guac_stream* stream,
void* data, int length) {
int bytes_written;
guac_spice_file_upload_status* upload_status = (guac_spice_file_upload_status*) stream->data;
/* Get filesystem, return error if no filesystem */
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* Write entire block */
while (length > 0) {
/* Attempt write */
bytes_written = guac_spice_folder_write(folder, upload_status->file_id,
upload_status->offset, data, length);
/* On error, abort */
if (bytes_written < 0) {
guac_protocol_send_ack(user->socket, stream,
"FAIL (BAD WRITE)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Update counters */
upload_status->offset += bytes_written;
data = (char *)data + bytes_written;
length -= bytes_written;
}
guac_protocol_send_ack(user->socket, stream, "OK (DATA RECEIVED)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}
int guac_spice_file_upload_end_handler(guac_user* user, guac_stream* stream) {
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
guac_spice_file_upload_status* upload_status = (guac_spice_file_upload_status*) stream->data;
/* Get folder, return error if no filesystem */
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* Close file */
guac_spice_folder_close(folder, upload_status->file_id);
/* Acknowledge stream end */
guac_protocol_send_ack(user->socket, stream, "OK (STREAM END)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
free(upload_status);
return 0;
}
int guac_spice_file_upload_put_handler(guac_user* user, guac_object* object,
guac_stream* stream, char* mimetype, char* name) {
guac_client* client = user->client;
guac_spice_client* spice_client = (guac_spice_client*) client->data;
/* Get folder, return error if no filesystem */
guac_spice_folder* folder = spice_client->shared_folder;
if (folder == NULL) {
guac_protocol_send_ack(user->socket, stream, "FAIL (NO FOLDER)",
GUAC_PROTOCOL_STATUS_SERVER_ERROR);
guac_socket_flush(user->socket);
return 0;
}
/* Ignore upload if uploads have been disabled */
if (folder->disable_upload) {
guac_client_log(client, GUAC_LOG_WARNING, "A upload attempt has "
"been blocked due to uploads being disabled, however it "
"should have been blocked at a higher level. This is likely "
"a bug.");
guac_protocol_send_ack(user->socket, stream, "FAIL (UPLOAD DISABLED)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Open file */
int file_id = guac_spice_folder_open(folder, name, (O_WRONLY | O_CREAT | O_TRUNC),
1, 0);
/* Abort on failure */
if (file_id < 0) {
guac_protocol_send_ack(user->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(user->socket);
return 0;
}
/* Init upload stream data */
guac_spice_file_upload_status* upload_status = malloc(sizeof(guac_spice_file_upload_status));
upload_status->offset = 0;
upload_status->file_id = file_id;
/* Allocate stream, init for file upload */
stream->data = upload_status;
stream->blob_handler = guac_spice_file_upload_blob_handler;
stream->end_handler = guac_spice_file_upload_end_handler;
/* Acknowledge stream creation */
guac_protocol_send_ack(user->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(user->socket);
return 0;
}

View File

@ -0,0 +1,72 @@
/*
* 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_UPLOAD_H
#define GUAC_SPICE_FILE_UPLOAD_H
#include "common/json.h"
#include <guacamole/protocol.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
#include <stdint.h>
/**
* Structure which represents the current state of an upload.
*/
typedef struct guac_spice_file_upload_status {
/**
* The overall offset within the file that the next write should
* occur at.
*/
uint64_t offset;
/**
* The ID of the file being written to.
*/
int file_id;
} guac_spice_file_upload_status;
/**
* Handler for inbound files related to file uploads.
*/
guac_user_file_handler guac_spice_file_upload_file_handler;
/**
* Handler for stream data related to file uploads.
*/
guac_user_blob_handler guac_spice_file_upload_blob_handler;
/**
* Handler for end-of-stream related to file uploads.
*/
guac_user_end_handler guac_spice_file_upload_end_handler;
/**
* Handler for put messages. In context of uploads and the filesystem exposed
* via the Guacamole protocol, put messages request write access to a file
* within the filesystem.
*/
guac_user_put_handler guac_spice_file_upload_put_handler;
#endif

View File

@ -0,0 +1,701 @@
/*
* 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 "file-download.h"
#include "file-ls.h"
#include "file-upload.h"
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/stat.h>
/**
* Translates an absolute path for a shared folder to an absolute path which is
* within the real "shared folder" path specified in the connection settings.
* No checking is performed on the path provided, which is assumed to have
* already been normalized and validated as absolute.
*
* @param folder
* The folder containing the file whose path is being translated.
*
* @param virtual_path
* The absolute path to the file on the simulated folder, relative to the
* shared folder root.
*
* @param real_path
* The buffer in which to store the absolute path to the real file on the
* local filesystem.
*/
static void __guac_spice_folder_translate_path(guac_spice_folder* folder,
const char* virtual_path, char* real_path) {
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: virtual_path=\"%s\", drive_path=\"%s\"", __func__, virtual_path, folder->path);
/* Get drive path */
char* path = folder->path;
int i;
/* Start with path from settings */
for (i=0; i<GUAC_SPICE_FOLDER_MAX_PATH-1; i++) {
/* Break on end-of-string */
char c = *(path++);
if (c == 0)
break;
/* Copy character */
*(real_path++) = c;
}
/* Translate path */
for (; i<GUAC_SPICE_FOLDER_MAX_PATH-1; i++) {
/* Stop at end of string */
char c = *(virtual_path++);
if (c == 0)
break;
/* Translate backslashes to forward slashes */
if (c == '\\')
c = '/';
/* Store in real path buffer */
*(real_path++)= c;
}
/* Null terminator */
*real_path = 0;
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: virtual_path=\"%s\", real_path=\"%s\"", __func__, virtual_path, real_path);
}
guac_spice_folder* guac_spice_folder_alloc(guac_client* client, const char* folder_path,
int create_folder, int disable_download, int disable_upload) {
guac_client_log(client, GUAC_LOG_DEBUG, "Initializing shared folder at "
"\"%s\".", folder_path);
/* Create folder if it does not exist */
if (create_folder) {
guac_client_log(client, GUAC_LOG_DEBUG,
"%s: Creating folder \"%s\" if necessary.",
__func__, folder_path);
/* Log error if directory creation fails */
if (mkdir(folder_path, S_IRWXU) && errno != EEXIST) {
guac_client_log(client, GUAC_LOG_ERROR,
"Unable to create folder \"%s\": %s",
folder_path, strerror(errno));
}
}
guac_spice_folder* folder = malloc(sizeof(guac_spice_folder));
folder->client = client;
folder->path = strdup(folder_path);
folder->file_id_pool = guac_pool_alloc(0);
folder->open_files = 0;
folder->disable_download = disable_download;
folder->disable_upload = disable_upload;
/* Set up Download directory and watch it. */
if (!disable_download) {
guac_client_log(client, GUAC_LOG_DEBUG, "%s: Setting up Download/ folder watch.", __func__);
if (create_folder) {
guac_client_log(client, GUAC_LOG_DEBUG, "%s: Creating Download/ folder.",
__func__);
char *download_path;
download_path = guac_strdup(folder_path);
guac_strlcat(download_path, "/Download", GUAC_SPICE_FOLDER_MAX_PATH);
if (mkdir(folder_path, S_IRWXU) && errno != EEXIST) {
guac_client_log(client, GUAC_LOG_ERROR,
"%s: Unable to create folder \"%s\": %s", __func__,
download_path, strerror(errno));
}
}
if(pthread_create(&(folder->download_thread), NULL, guac_spice_file_download_monitor, (void*) folder)) {
guac_client_log(client, GUAC_LOG_ERROR,
"%s: Unable to create Download folder thread monitor.", __func__);
}
}
return folder;
}
void guac_spice_folder_free(guac_spice_folder* folder) {
guac_pool_free(folder->file_id_pool);
free(folder->path);
free(folder);
}
guac_object* guac_spice_folder_alloc_object(guac_spice_folder *folder, guac_user* user) {
/* Init folder */
guac_object* folder_object = guac_user_alloc_object(user);
folder_object->get_handler = guac_spice_file_download_get_handler;
/* Assign upload handler only if uploads are not disabled. */
if (!folder->disable_upload)
folder_object->put_handler = guac_spice_file_upload_put_handler;
folder_object->data = folder;
/* Send filesystem to user */
guac_protocol_send_filesystem(user->socket, folder_object, "Shared Folder");
guac_socket_flush(user->socket);
return folder_object;
}
int guac_spice_folder_append_filename(char* fullpath, const char* path,
const char* filename) {
int i;
/* Disallow "." as a filename */
if (strcmp(filename, ".") == 0)
return 0;
/* Disallow ".." as a filename */
if (strcmp(filename, "..") == 0)
return 0;
/* Copy path, append trailing slash */
for (i=0; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
char c = path[i];
if (c == '\0') {
if (i > 0 && path[i-1] != '/' && path[i-1] != '\\')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */
for (; i<GUAC_SPICE_FOLDER_MAX_PATH; i++) {
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return 0;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */
if (i == GUAC_SPICE_FOLDER_MAX_PATH)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return 1;
}
const char* guac_spice_folder_basename(const char* path) {
for (const char* c = path; *c != '\0'; c++) {
/* Reset beginning of path if a path separator is found */
if (*c == '/' || *c == '\\')
path = c + 1;
}
/* path now points to the first character after the last path separator */
return path;
}
void guac_spice_folder_close(guac_spice_folder* folder, int file_id) {
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
if (file == NULL) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Ignoring close for bad file_id: %i",
__func__, file_id);
return;
}
file = &(folder->files[file_id]);
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Closed \"%s\" (file_id=%i)",
__func__, file->absolute_path, file_id);
/* Close directory, if open */
if (file->dir != NULL)
closedir(file->dir);
/* Close file */
close(file->fd);
/* Free paths */
free(file->absolute_path);
free(file->real_path);
/* Free ID back to pool */
guac_pool_free_int(folder->file_id_pool, file_id);
folder->open_files--;
}
int guac_spice_folder_delete(guac_spice_folder* folder, int file_id) {
/* Get file */
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
if (file == NULL) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Delete of bad file_id: %i", __func__, file_id);
return GUAC_SPICE_FOLDER_EINVAL;
}
/* If directory, attempt removal */
if (S_ISDIR(file->stmode)) {
if (rmdir(file->real_path)) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: rmdir() failed: \"%s\"", __func__, file->real_path);
return guac_spice_folder_get_errorcode(errno);
}
}
/* Otherwise, attempt deletion */
else if (unlink(file->real_path)) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: unlink() failed: \"%s\"", __func__, file->real_path);
return guac_spice_folder_get_errorcode(errno);
}
return 0;
}
void* guac_spice_folder_expose(guac_user* user, void* data) {
guac_spice_folder* folder = (guac_spice_folder*) data;
guac_user_log(user, GUAC_LOG_DEBUG, "%s: Exposing folder \"%s\" to user.", __func__, folder->path);
/* No need to expose if there is no folder or the user has left */
if (user == NULL || folder == NULL)
return NULL;
/* Allocate and expose folder object for user */
return guac_spice_folder_alloc_object(folder, user);
}
int guac_spice_folder_get_errorcode(int err) {
/* Translate errno codes to GUAC_SPICE_FOLDER codes */
switch(err) {
case ENFILE:
return GUAC_SPICE_FOLDER_ENFILE;
case ENOENT:
return GUAC_SPICE_FOLDER_ENOENT;
case ENOTDIR:
return GUAC_SPICE_FOLDER_ENOTDIR;
case ENOSPC:
return GUAC_SPICE_FOLDER_ENOSPC;
case EISDIR:
return GUAC_SPICE_FOLDER_EISDIR;
case EACCES:
return GUAC_SPICE_FOLDER_EACCES;
case EEXIST:
return GUAC_SPICE_FOLDER_EEXIST;
case EINVAL:
return GUAC_SPICE_FOLDER_EINVAL;
case ENOSYS:
return GUAC_SPICE_FOLDER_ENOSYS;
case ENOTSUP:
return GUAC_SPICE_FOLDER_ENOTSUP;
default:
return GUAC_SPICE_FOLDER_EINVAL;
}
}
guac_spice_folder_file* guac_spice_folder_get_file(guac_spice_folder* folder,
int file_id) {
/* Validate ID */
if (file_id < 0 || file_id >= GUAC_SPICE_FOLDER_MAX_FILES)
return NULL;
/* Return file at given ID */
return &(folder->files[file_id]);
}
int guac_spice_folder_normalize_path(const char* path, char* abs_path) {
int path_depth = 0;
const char* path_components[GUAC_SPICE_FOLDER_MAX_PATH_DEPTH];
/* If original path is not absolute, normalization fails */
if (path[0] != '/')
return 1;
/* Create scratch copy of path excluding leading slash (we will be
* replacing path separators with null terminators and referencing those
* substrings directly as path components) */
char path_scratch[GUAC_SPICE_FOLDER_MAX_PATH - 1];
int length = guac_strlcpy(path_scratch, path + 1,
sizeof(path_scratch));
/* Fail if provided path is too long */
if (length >= sizeof(path_scratch))
return 1;
/* Locate all path components within path */
const char* current_path_component = &(path_scratch[0]);
for (int i = 0; i <= length; i++) {
/* If current character is a path separator, parse as component */
char c = path_scratch[i];
if (c == '/' || c == '\0') {
/* Terminate current component */
path_scratch[i] = '\0';
/* If component refers to parent, just move up in depth */
if (strcmp(current_path_component, "..") == 0) {
if (path_depth > 0)
path_depth--;
}
/* Otherwise, if component not current directory, add to list */
else if (strcmp(current_path_component, ".") != 0
&& strcmp(current_path_component, "") != 0) {
/* Fail normalization if path is too deep */
if (path_depth >= GUAC_SPICE_FOLDER_MAX_PATH_DEPTH)
return 1;
path_components[path_depth++] = current_path_component;
}
/* Update start of next component */
current_path_component = &(path_scratch[i+1]);
} /* end if separator */
/* We do not currently support named streams */
else if (c == ':')
return 1;
} /* end for each character */
/* Add leading slash for resulting absolute path */
abs_path[0] = '/';
/* Append normalized components to path, separated by slashes */
guac_strljoin(abs_path + 1, path_components, path_depth,
"/", GUAC_SPICE_FOLDER_MAX_PATH - 1);
return 0;
}
int guac_spice_folder_open(guac_spice_folder* folder, const char* path,
int flags, bool overwrite, bool directory) {
char real_path[GUAC_SPICE_FOLDER_MAX_PATH];
char normalized_path[GUAC_SPICE_FOLDER_MAX_PATH];
struct stat file_stat;
int fd;
int file_id;
guac_spice_folder_file* file;
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: path=\"%s\", flags=0x%x, overwrite=0x%x, "
"directory=0x%x", __func__, path, flags, overwrite, directory);
/* If no files available, return too many open */
if (folder->open_files >= GUAC_SPICE_FOLDER_MAX_FILES) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Too many open files.",
__func__, path);
return GUAC_SPICE_FOLDER_ENFILE;
}
/* If path empty, return an error */
if (path[0] == '\0')
return GUAC_SPICE_FOLDER_EINVAL;
/* If path is relative, the file does not exist */
else if (path[0] != '\\' && path[0] != '/') {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Access denied - supplied path \"%s\" is relative.",
__func__, path);
return GUAC_SPICE_FOLDER_ENOENT;
}
/* Translate access into flags */
if (directory)
flags |= O_DIRECTORY;
else if (overwrite)
flags |= O_TRUNC;
/* Normalize path, return no-such-file if invalid */
if (guac_spice_folder_normalize_path(path, normalized_path)) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Normalization of path \"%s\" failed.", __func__, path);
return GUAC_SPICE_FOLDER_ENOENT;
}
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Normalized path \"%s\" to \"%s\".",
__func__, path, normalized_path);
/* Translate normalized path to real path */
__guac_spice_folder_translate_path(folder, normalized_path, real_path);
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Translated path \"%s\" to \"%s\".",
__func__, normalized_path, real_path);
/* Create directory first, if necessary */
if (directory && (flags & O_CREAT)) {
/* Create directory */
if (mkdir(real_path, S_IRWXU)) {
if (errno != EEXIST || (flags & O_EXCL)) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: mkdir() failed: %s",
__func__, strerror(errno));
return guac_spice_folder_get_errorcode(errno);
}
}
/* Unset O_CREAT and O_EXCL as directory must exist before open() */
flags &= ~(O_CREAT | O_EXCL);
}
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: native open: real_path=\"%s\", flags=0x%x",
__func__, real_path, flags);
/* Open file */
fd = open(real_path, flags, S_IRUSR | S_IWUSR);
/* If file open failed as we're trying to write a dir, retry as read-only */
if (fd == -1 && errno == EISDIR) {
flags &= ~(O_WRONLY | O_RDWR);
flags |= O_RDONLY;
fd = open(real_path, flags, S_IRUSR | S_IWUSR);
}
if (fd == -1) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: open() failed: %s", __func__, strerror(errno));
return guac_spice_folder_get_errorcode(errno);
}
/* Get file ID, init file */
file_id = guac_pool_next_int(folder->file_id_pool);
file = &(folder->files[file_id]);
file->id = file_id;
file->fd = fd;
file->dir = NULL;
file->dir_pattern[0] = '\0';
file->absolute_path = strdup(normalized_path);
file->real_path = strdup(real_path);
file->bytes_written = 0;
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Opened \"%s\" as file_id=%i",
__func__, normalized_path, file_id);
/* Attempt to pull file information */
if (fstat(fd, &file_stat) == 0) {
/* Load size and times */
file->size = file_stat.st_size;
file->ctime = file_stat.st_ctime;
file->mtime = file_stat.st_mtime;
file->atime = file_stat.st_atime;
file->stmode = file_stat.st_mode;
}
/* If information cannot be retrieved, fake it */
else {
/* Init information to 0, lacking any alternative */
file->size = 0;
file->ctime = 0;
file->mtime = 0;
file->atime = 0;
file->stmode = 0;
}
folder->open_files++;
return file_id;
}
int guac_spice_folder_read(guac_spice_folder* folder, int file_id, uint64_t offset,
void* buffer, int length) {
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to read from file: %s", __func__, folder->path);
int bytes_read;
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
if (file == NULL) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Read from bad file_id: %i", __func__, file_id);
return GUAC_SPICE_FOLDER_EINVAL;
}
/* Attempt read */
lseek(file->fd, offset, SEEK_SET);
bytes_read = read(file->fd, buffer, length);
/* Translate errno on error */
if (bytes_read < 0)
return guac_spice_folder_get_errorcode(errno);
return bytes_read;
}
const char* guac_spice_folder_read_dir(guac_spice_folder* folder, int file_id) {
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to read directory: %s", __func__, folder->path);
guac_spice_folder_file* file;
struct dirent* result;
/* Only read if file ID is valid */
if (file_id < 0 || file_id >= GUAC_SPICE_FOLDER_MAX_FILES)
return NULL;
file = &(folder->files[file_id]);
/* Open directory if not yet open, stop if error */
if (file->dir == NULL) {
file->dir = fdopendir(file->fd);
if (file->dir == NULL)
return NULL;
}
/* Read next entry, stop if error or no more entries */
if ((result = readdir(file->dir)) == NULL)
return NULL;
/* Return filename */
return result->d_name;
}
int guac_spice_folder_write(guac_spice_folder* folder, int file_id, uint64_t offset,
void* buffer, int length) {
guac_client_log(folder->client, GUAC_LOG_DEBUG, "%s: Attempt to write file: %s", __func__, folder->path);
int bytes_written;
guac_spice_folder_file* file = guac_spice_folder_get_file(folder, file_id);
if (file == NULL) {
guac_client_log(folder->client, GUAC_LOG_DEBUG,
"%s: Write to bad file_id: %i", __func__, file_id);
return GUAC_SPICE_FOLDER_EINVAL;
}
/* Attempt write */
lseek(file->fd, offset, SEEK_SET);
bytes_written = write(file->fd, buffer, length);
/* Translate errno on error */
if (bytes_written < 0)
return guac_spice_folder_get_errorcode(errno);
file->bytes_written += bytes_written;
return bytes_written;
}
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"

View File

@ -0,0 +1,461 @@
/*
* 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 "spice-constants.h"
#include <guacamole/client.h>
#include <guacamole/object.h>
#include <guacamole/pool.h>
#include <guacamole/user.h>
#include <dirent.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <spice-client-glib-2.0/spice-client.h>
/**
* An arbitrary file on the shared folder.
*/
typedef struct guac_spice_folder_file {
/**
* The ID of this file.
*/
int id;
/**
* The absolute path, including filename, of this file on the simulated filesystem.
*/
char* absolute_path;
/**
* The real path, including filename, of this file on the local filesystem.
*/
char* real_path;
/**
* Associated local file descriptor.
*/
int fd;
/**
* Associated directory stream, if any. This field only applies
* if the file is being used as a directory.
*/
DIR* dir;
/**
* The pattern the check directory contents against, if any.
*/
char dir_pattern[GUAC_SPICE_FOLDER_MAX_PATH];
/**
* The size of this file, in bytes.
*/
uint64_t size;
/**
* The time this file was created, as a UNIX timestamp.
*/
uint64_t ctime;
/**
* The time this file was last modified, as a UNIX timestamp.
*/
uint64_t mtime;
/**
* The time this file was last accessed, as a UNIX timestamp.
*/
uint64_t atime;
/**
* THe mode field of the file, as retrieved by a call to the stat() family
* of functions;
*/
mode_t stmode;
/**
* The number of bytes written to the file.
*/
uint64_t bytes_written;
} guac_spice_folder_file;
/**
* A shared folder for the Spice protocol.
*/
typedef struct guac_spice_folder {
/**
* The guac_client object this folder is associated with.
*/
guac_client* client;
/**
* The path to the shared folder.
*/
char* path;
/**
* The number of currently open files in the folder.
*/
int open_files;
/**
* A pool of file IDs.
*/
guac_pool* file_id_pool;
/**
* All available file structures.
*/
guac_spice_folder_file files[GUAC_SPICE_FOLDER_MAX_FILES];
/**
* Whether uploads from the client to the shared folder should be disabled.
*/
int disable_download;
/**
* Whether downloads from the shared folder to the client should be disabled.
*/
int disable_upload;
/**
* Thread which watches the Download folder and triggers the automatic
* download of files within this subfolder.
*/
pthread_t download_thread;
} guac_spice_folder;
/**
* Allocates a new filesystem given a root path which will be shared with the
* user and the remote server via WebDAV.
*
* @param client
* The guac_client associated with the current RDP session.
*
* @param folder_path
* The local directory to use as the root directory of the shared folder.
*
* @param create_folder
* Non-zero if the folder at the path specified should be automatically
* created if it does not yet exist, zero otherwise.
*
* @param disable_download
* Non-zero if downloads from the remote server to the local browser should
* be disabled.
*
* @param disable_upload
* Non-zero if uploads from the browser to the remote server should be
* disabled.
*
* @return
* The newly-allocated filesystem.
*/
guac_spice_folder* guac_spice_folder_alloc(guac_client* client, const char* folder_path,
int create_folder, int disable_download, int disable_upload);
/**
* Frees the given filesystem.
*
* @param folder
* The folder to free.
*/
void guac_spice_folder_free(guac_spice_folder* folder);
/**
* Creates and exposes a new filesystem guac_object to the given user,
* providing access to the files within the given Spice shared folder. The
* allocated guac_object must eventually be freed via guac_user_free_object().
*
* @param folder
* The guac_spice_folder object to expose.
*
* @param user
* The user that the folder should be exposed to.
*
* @return
* A new Guacamole filesystem object, configured to use Spice for uploading
* and downloading files.
*/
guac_object* guac_spice_folder_alloc_object(guac_spice_folder* folder, guac_user* user);
/**
* Concatenates the given filename with the given path, separating the two
* with a single forward slash. The full result must be no more than
* GUAC_SPICE_FOLDER_MAX_PATH bytes long, counting null terminator.
*
* @param fullpath
* The buffer to store the result within. This buffer must be at least
* GUAC_SPICE_FOLDER_MAX_PATH bytes long.
*
* @param path
* The path to append the filename to.
*
* @param filename
* The filename to append to the path.
*
* @return
* Non-zero if the filename is valid and was successfully appended to the
* path, zero otherwise.
*/
int guac_spice_folder_append_filename(char* fullpath, const char* path,
const char* filename);
/**
* Given an arbitrary path, returns a pointer to the first character following
* the last path separator in the path (the basename of the path). For example,
* given "/foo/bar/baz", this function would return a pointer to "baz".
*
* @param path
* The path to determine the basename of.
*
* @return
* A pointer to the first character of the basename within the path.
*/
const char* guac_spice_folder_basename(const char* path);
/**
* Frees the given file ID, allowing future open operations to reuse it.
*
* @param folder
* The folder containing the file to close.
*
* @param file_id
* The ID of the file to close, as returned by guac_spice_folder_open().
*/
void guac_spice_folder_close(guac_spice_folder* folder, int file_id);
/**
* Deletes the file with the given ID.
*
* @param folder
* The folder containing the file to delete.
*
* @param file_id
* The ID of the file to delete, as returned by guac_spice_folder_open().
*
* @return
* Zero if deletion succeeded, or an error code if an error occurs. All
* error codes are negative values and correspond to GUAC_SPICE_FOLDER
* constants, such as GUAC_SPICE_FOLDER_ENOENT.
*/
int guac_spice_folder_delete(guac_spice_folder* folder, int file_id);
/**
* Allocates a new filesystem guac_object for the given user, returning the
* resulting guac_object. This function is provided for convenience, as it is
* can be used as the callback for guac_client_foreach_user() or
* guac_client_for_owner(). Note that this guac_object will be tracked
* internally by libguac, will be provided to us in the parameters of handlers
* related to that guac_object, and will automatically be freed when the
* associated guac_user is freed, so the return value of this function can
* safely be ignored.
*
* If either the given user or the given filesystem are NULL, then this
* function has no effect.
*
* @param user
* The use to expose the filesystem to, or NULL if nothing should be
* exposed.
*
* @param data
* A pointer to the guac_spice_folder instance to expose to the given user,
* or NULL if nothing should be exposed.
*
* @return
* The guac_object allocated for the newly-exposed filesystem, or NULL if
* no filesystem object could be allocated.
*/
void* guac_spice_folder_expose(guac_user* user, void* data);
/**
* Translates the given errno error code to a GUAC_SPICE_FOLDER error code.
*
* @param err
* The error code, as returned within errno by a system call.
*
* @return
* A GUAC_SPICE_FOLDER error code, such as GUAC_SPICE_FOLDER_ENFILE,
* GUAC_SPICE_FOLDER_ENOENT, etc.
*/
int guac_spice_folder_get_errorcode(int err);
/**
* Returns the file having the given ID, or NULL if no such file exists.
*
* @param folder
* The folder containing the desired file.
*
* @param file_id
* The ID of the desired, as returned by guac_spice_folder_open().
*
* @return
* The file having the given ID, or NULL is no such file exists.
*/
guac_spice_folder_file* guac_spice_folder_get_file(guac_spice_folder* folder,
int file_id);
/**
* Given an arbitrary path, which may contain ".." and ".", creates an
* absolute path which does NOT contain ".." or ".". The given path MUST
* be absolute.
*
* @param path
* The path to normalize.
*
* @param abs_path
* The buffer to populate with the normalized path. The normalized path
* will not contain relative path components like ".." or ".".
*
* @return
* Zero if normalization succeeded, non-zero otherwise.
*/
int guac_spice_folder_normalize_path(const char* path, char* abs_path);
/**
* Opens the given file, returning the a new file ID, or an error code less
* than zero if an error occurs. The given path MUST be absolute, and will be
* translated to be relative to the drive path of the simulated filesystem.
*
* @param folder
* The shared folder to use when opening the file.
*
* @param path
* The absolute path to the file within the simulated filesystem.
*
* @param flags
* A bitwise-OR of various standard POSIX flags to use when opening the
* file or directory.
*
* @param overwrite
* True if the file should be overwritten when opening it, otherwise false.
*
* @param directory
* True if the path specified is a directory, otherwise false.
*
* @return
* A new file ID, which will always be a positive value, or an error code
* if an error occurs. All error codes are negative values and correspond
* to GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
*/
int guac_spice_folder_open(guac_spice_folder* folder, const char* path,
int flags, bool overwrite, bool directory);
/**
* Reads up to the given length of bytes from the given offset within the
* file having the given ID. Returns the number of bytes read, zero on EOF,
* and an error code if an error occurs.
*
* @param folder
* The folder containing the file from which data is to be read.
*
* @param file_id
* The ID of the file to read data from, as returned by guac_spice_folder_open().
*
* @param offset
* The byte offset within the file to start reading from.
*
* @param buffer
* The buffer to fill with data from the file.
*
* @param length
* The maximum number of bytes to read from the file.
*
* @return
* The number of bytes actually read, zero on EOF, or an error code if an
* error occurs. All error codes are negative values and correspond to
* GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
*/
int guac_spice_folder_read(guac_spice_folder* folder, int file_id, uint64_t offset,
void* buffer, int length);
/**
* Returns the next filename within the directory having the given file ID,
* or NULL if no more files.
*
* @param folder
* The foleer containing the file to read directory entries from.
*
* @param file_id
* The ID of the file to read directory entries from, as returned by
* guac_spice_folder_open().
*
* @return
* The name of the next filename within the directory, or NULL if the last
* file in the directory has already been returned by a previous call.
*/
const char* guac_spice_folder_read_dir(guac_spice_folder* folder, int file_id);
/**
* Writes up to the given length of bytes from the given offset within the
* file having the given ID. Returns the number of bytes written, and an
* error code if an error occurs.
*
* @param folder
* The folder containing the file to which data is to be written.
*
* @param file_id
* The ID of the file to write data to, as returned by guac_spice_folder_open().
*
* @param offset
* The byte offset within the file to start writinging at.
*
* @param buffer
* The buffer containing the data to write.
*
* @param length
* The maximum number of bytes to write to the file.
*
* @return
* The number of bytes actually written, or an error code if an error
* occurs. All error codes are negative values and correspond to
* GUAC_SPICE_FOLDER constants, such as GUAC_SPICE_FOLDER_ENOENT.
*/
int guac_spice_folder_write(guac_spice_folder* folder, int file_id, uint64_t offset,
void* buffer, int length);
/**
* 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 */

View File

@ -0,0 +1,382 @@
/*
* 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 <guacamole/client.h>
#include <guacamole/recording.h>
#include <glib/gmain.h>
#include <pthread.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdlib.h>
#include <string.h>
/**
* 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, type;
/* Get the channel ID and type. */
g_object_get(channel, SPICE_PROPERTY_CHANNEL_ID, &id, NULL);
g_object_get(channel, SPICE_PROPERTY_CHANNEL_TYPE, &type, NULL);
guac_client_log(client, GUAC_LOG_DEBUG, "New channel created: %i", id);
guac_client_log(client, GUAC_LOG_DEBUG, "New channel type: %i", type);
/* 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_input_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) {
}
}
if (SPICE_IS_USBREDIR_CHANNEL(channel)) {
guac_client_log(client, GUAC_LOG_DEBUG, "USB redirection is not yet implemented.");
return;
}
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.");
}

View File

@ -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 <guacamole/client.h>
#include <spice-client-glib-2.0/spice-client.h>
/**
* 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 */

View File

@ -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;
}

View File

@ -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

154
src/protocols/spice/input.c Normal file
View File

@ -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 <guacamole/recording.h>
#include <guacamole/user.h>
#include <spice-client-glib-2.0/spice-client.h>
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.");
}

View File

@ -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 <guacamole/user.h>
/**
* 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 */

View File

@ -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 <guacamole/client.h>
#include <stdlib.h>
/**
* 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. A 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));
}

View File

@ -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 <guacamole/client.h>
#include <spice-client-glib-2.0/spice-client.h>
/**
* 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

View File

@ -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 <string.h>
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;
}

View File

@ -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

View File

@ -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

View File

@ -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 ~ "½!"#¤%&/()=?"
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 +caps -altgr +shift 0x29 0x02..0x0C ~ "½!"#¤%&/()=?"
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

View File

@ -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 ~ "°+"*ç%&/()=?"
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..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 ~ "°+"*ç%&/()=?"
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..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

View File

@ -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 0x29 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;:_"
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;:-"
map +caps -altgr +shift 0x29 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,._"
#
# 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

View File

@ -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 ~ "Ç"

View File

@ -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<>?"

View File

@ -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 ~ "ª!"·$%&/()=?¿"
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,.-"
map +caps -altgr +shift 0x29 0x02..0x0D ~ "ª!"·$%&/()=?¿"
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)

View File

@ -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 ~ "º!"#$%&/()=?¡"
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,.-"
map +caps -altgr +shift 0x29 0x02..0x0D ~ "º!"#$%&/()=?¡"
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

View File

@ -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"

View File

@ -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?./+"
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?./+"
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,;:="
#
# 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

View File

@ -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

View File

@ -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 ~ "°+"*ç%&/()=?"
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..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 ~ "°+"*ç%&/()=?"
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..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

View File

@ -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 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?./§"
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?./§"
map +caps -altgr +shift 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,;:!"
#
# 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

View File

@ -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 <stddef.h>' . "\n"
. '#include <spice-client-glib-2.0/spice-client.h>' . "\n"
. "\n";
for my $filename (@ARGV) {
my $content = "";
my $parent = "";
my $layout_name = "";
# Parse file
open INPUT, '<', "$filename";
binmode INPUT, ":encoding(utf8)";
while (<INPUT>) {
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;

View File

@ -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

View File

@ -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 ~ "|!"£$%&/()=?^"
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
#
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 ~ "}"

View File

@ -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

View File

@ -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 ~ "|!"#¤%&/()=?"
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 +caps -altgr +shift 0x29 0x02..0x0C ~ "|!"#¤%&/()=?"
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

View File

@ -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 ~ "żźćń"

View File

@ -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

View File

@ -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 ~ "½!"#¤%&/()=?"
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 +caps -altgr +shift 0x29 0x02..0x0C ~ "½!"#¤%&/()=?"
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

View File

@ -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..0x03 ~ "é!'"
map -caps -altgr +shift 0x05..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..0x03 ~ "é!'"
map +caps -altgr +shift 0x05..0x0D ~ "+%&/()=?_"
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öç:"
#
# 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

67
src/protocols/spice/log.c Normal file
View File

@ -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 <cairo/cairo.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
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);
}

67
src/protocols/spice/log.h Normal file
View File

@ -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 <cairo/cairo.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
/**
* 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 */

View File

@ -0,0 +1,675 @@
/*
* 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 <guacamole/user.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <spice-client-glib-2.0/spice-client.h>
/* 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",
"enable-audio-input",
"file-transfer",
"file-directory",
"file-transfer-ro",
"file-transfer-create-folder",
"disable-download",
"disable-upload",
"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 to connect to.
*/
IDX_HOSTNAME,
/**
* The port of the Spice server 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 audio input should be enabled, "false" or blank otherwise.
*/
IDX_ENABLE_AUDIO_INPUT,
/**
* "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,
/**
* Whether or not Guacamole should attempt to create the shared folder
* if it does not already exist.
*/
IDX_FILE_TRANSFER_CREATE_FOLDER,
/**
* "true" if downloads from the remote server to Guacamole client should
* be disabled, otherwise false or blank.
*/
IDX_DISABLE_DOWNLOAD,
/**
* "true" if uploads from Guacamole Client to the shared folder should be
* disabled, otherwise false or blank.
*/
IDX_DISABLE_UPLOAD,
/**
* 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);
/* Audio input enable/disable */
settings->audio_input_enabled =
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
IDX_ENABLE_AUDIO_INPUT, 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);
/* Whether or not Guacamole should attempt to create a non-existent folder. */
settings->file_transfer_create_folder =
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
IDX_FILE_TRANSFER_CREATE_FOLDER, false);
/* Whether or not downloads (Server -> Client) should be disabled. */
settings->disable_download =
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
IDX_DISABLE_DOWNLOAD, false);
/* Whether or not uploads (Client -> Server) should be disabled. */
settings->disable_upload =
guac_user_parse_args_boolean(user, GUAC_SPICE_CLIENT_ARGS, argv,
IDX_DISABLE_UPLOAD, 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);
}

View File

@ -0,0 +1,345 @@
/*
* 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 <spice-client-glib-2.0/spice-client.h>
#include <stdbool.h>
/**
* 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;
/**
* Whether audio input is enabled.
*/
bool audio_input_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;
/**
* If the folder does not exist and this setting is set to True, guacd
* will attempt to create the folder.
*/
bool file_transfer_create_folder;
/**
* True if downloads (Remote Server -> Guacamole Client) should be
* disabled.
*/
bool disable_download;
/**
* True if uploads (Guacamole Client -> Remote Server) should be disabled.
*/
bool disable_upload;
/**
* 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 */

View File

@ -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 <guacamole/client.h>
#include <guacamole/stream.h>
#include <guacamole/user.h>
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);
}

View File

@ -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 <guacamole/user.h>
/**
* 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 */

View File

@ -0,0 +1,445 @@
/*
* 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
/**
* Error code returned when no more file IDs can be allocated.
*/
#define GUAC_SPICE_FOLDER_ENFILE -1
/**
* Error code returned when no such file exists.
*/
#define GUAC_SPICE_FOLDER_ENOENT -2
/**
* Error code returned when the operation required a directory
* but the file was not a directory.
*/
#define GUAC_SPICE_FOLDER_ENOTDIR -3
/**
* Error code returned when insufficient space exists to complete
* the operation.
*/
#define GUAC_SPICE_FOLDER_ENOSPC -4
/**
* Error code returned when the operation requires a normal file but
* a directory was given.
*/
#define GUAC_SPICE_FOLDER_EISDIR -5
/**
* Error code returned when permission is denied.
*/
#define GUAC_SPICE_FOLDER_EACCES -6
/**
* Error code returned when the operation cannot be completed because the
* file already exists.
*/
#define GUAC_SPICE_FOLDER_EEXIST -7
/**
* Error code returned when invalid parameters were given.
*/
#define GUAC_SPICE_FOLDER_EINVAL -8
/**
* Error code returned when the operation is not implemented.
*/
#define GUAC_SPICE_FOLDER_ENOSYS -9
/**
* Error code returned when the operation is not supported.
*/
#define GUAC_SPICE_FOLDER_ENOTSUP -10
/**
* The maximum number of events that can be monitored at a given time for
* the Spice shared folder Download folder monitor.
*/
#define GUAC_SPICE_FOLDER_MAX_EVENTS 256
/**
* The maximum length of a path in a shared folder.
*/
#define GUAC_SPICE_FOLDER_MAX_PATH 4096
/**
* The maximum number of open files in a shared folder.
*/
#define GUAC_SPICE_FOLDER_MAX_FILES 128
/**
* The maximum level of folder deptch in a shared folder.
*/
#define GUAC_SPICE_FOLDER_MAX_PATH_DEPTH 64
/**
* 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"
/**
* THe Spice client channel property that stores the type of the channel.
*/
#define SPICE_PROPERTY_CHANNEL_TYPE "channel-type"
/**
* 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 Spiec 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 "playback-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"
/**
* A signal indicating that a share folder is available.
*/
#define SPICE_SIGNAL_SHARE_FOLDER "notify::share-folder"
/**
* The signal indicating that the Spice server has gone to streaming mode.
*/
#define SPICE_SIGNAL_STREAMING_MODE "streaming-mode"
#endif /* SPICE_CONSTANTS_H */

View File

@ -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 */

189
src/protocols/spice/spice.c Normal file
View File

@ -0,0 +1,189 @@
/*
* 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/cursor.h"
#include "common/display.h"
#include "channels/audio.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 <glib/gmain.h>
#include <guacamole/client.h>
#include <guacamole/protocol.h>
#include <guacamole/socket.h>
#include <guacamole/timestamp.h>
#include <spice-client-glib-2.0/spice-client.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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();
/* 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);
/* 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);
/* If file transfer is enabled, set up the required properties. */
if (spice_settings->file_transfer) {
guac_client_log(client, GUAC_LOG_DEBUG, "File transfer enabled, configuring Spice client.");
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR, spice_settings->file_directory, NULL);
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR_RO, spice_settings->file_transfer_ro, NULL);
spice_client->shared_folder = guac_spice_folder_alloc(client,
spice_settings->file_directory,
spice_settings->file_transfer_create_folder,
spice_settings->disable_download,
spice_settings->disable_upload
);
guac_client_for_owner(client, guac_spice_folder_expose,
spice_client->shared_folder);
}
else
g_object_set(spice_session, SPICE_PROPERTY_SHARED_DIR, NULL, NULL);
/* 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_socket_flush(client->socket);
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;
}

206
src/protocols/spice/spice.h Normal file
View File

@ -0,0 +1,206 @@
/*
* 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 "channels/file.h"
#include "common/clipboard.h"
#include "common/display.h"
#include "common/iconv.h"
#include "common/surface.h"
#include "keyboard.h"
#include "settings.h"
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/layer.h>
#include <guacamole/recording.h>
#include <glib.h>
#include <spice-client-glib-2.0/spice-client.h>
#ifdef ENABLE_COMMON_SSH
#include "common-ssh/sftp.h"
#include "common-ssh/ssh.h"
#include "common-ssh/user.h"
#endif
#include <pthread.h>
/**
* 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;
/**
* Shared folder.
*/
guac_spice_folder* shared_folder;
#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;
/**
* 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;
/**
* Audio output stream, if any.
*/
guac_audio_stream* audio_playback;
/**
* Audio input stream, if any.
*/
guac_stream* audio_input;
} guac_spice_client;
/**
* Allocates a new Spice client session 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 Spice session 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 */

129
src/protocols/spice/user.c Normal file
View File

@ -0,0 +1,129 @@
/*
* 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/audio.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 <guacamole/argv.h>
#include <guacamole/audio.h>
#include <guacamole/client.h>
#include <guacamole/socket.h>
#include <guacamole/user.h>
#include <pthread.h>
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;
}
/* Handle inbound audio streams if audio input is enabled */
if (settings->audio_input_enabled)
user->audio_handler = guac_spice_client_audio_record_handler;
}
/* 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;
}

View File

@ -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 <guacamole/user.h>
/**
* 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 */