Compare commits
2 Commits
master
...
working/sp
Author | SHA1 | Date | |
---|---|---|---|
91ac84e72f | |||
|
c6263a25fd |
33
Dockerfile
33
Dockerfile
@ -23,26 +23,26 @@
|
|||||||
|
|
||||||
# The Alpine Linux image that should be used as the basis for the guacd image
|
# The Alpine Linux image that should be used as the basis for the guacd image
|
||||||
ARG ALPINE_BASE_IMAGE=latest
|
ARG ALPINE_BASE_IMAGE=latest
|
||||||
FROM alpine:${ALPINE_BASE_IMAGE} AS builder
|
FROM ubuntu AS builder
|
||||||
|
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apt update && apt install -y \
|
||||||
autoconf \
|
autoconf \
|
||||||
automake \
|
automake \
|
||||||
build-base \
|
gcc g++ \
|
||||||
cairo-dev \
|
libcairo2-dev \
|
||||||
cmake \
|
cmake \
|
||||||
git \
|
git \
|
||||||
grep \
|
grep \
|
||||||
libjpeg-turbo-dev \
|
libjpeg-turbo8-dev \
|
||||||
libpng-dev \
|
libpng-dev \
|
||||||
libtool \
|
libtool \
|
||||||
libwebp-dev \
|
libwebp-dev \
|
||||||
make \
|
make \
|
||||||
openssl-dev \
|
libssl-dev \
|
||||||
pango-dev \
|
libpango1.0-dev \
|
||||||
pulseaudio-dev \
|
libpulse-dev \
|
||||||
util-linux-dev
|
libspice-client-glib-2.0-dev
|
||||||
|
|
||||||
# Copy source to container for sake of build
|
# Copy source to container for sake of build
|
||||||
ARG BUILD_DIR=/tmp/guacamole-server
|
ARG BUILD_DIR=/tmp/guacamole-server
|
||||||
@ -146,7 +146,7 @@ RUN ${BUILD_DIR}/src/guacd-docker/bin/list-dependencies.sh \
|
|||||||
> ${PREFIX_DIR}/DEPENDENCIES
|
> ${PREFIX_DIR}/DEPENDENCIES
|
||||||
|
|
||||||
# Use same Alpine version as the base for the runtime image
|
# Use same Alpine version as the base for the runtime image
|
||||||
FROM alpine:${ALPINE_BASE_IMAGE}
|
FROM ubuntu
|
||||||
|
|
||||||
#
|
#
|
||||||
# Base directory for installed build artifacts. See also the
|
# Base directory for installed build artifacts. See also the
|
||||||
@ -166,16 +166,15 @@ ENV GUACD_LOG_LEVEL=info
|
|||||||
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
|
COPY --from=builder ${PREFIX_DIR} ${PREFIX_DIR}
|
||||||
|
|
||||||
# Bring runtime environment up to date and install runtime dependencies
|
# Bring runtime environment up to date and install runtime dependencies
|
||||||
RUN apk add --no-cache \
|
RUN apt update && apt install -y \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
ghostscript \
|
ghostscript \
|
||||||
netcat-openbsd \
|
netcat-openbsd \
|
||||||
shadow \
|
fonts-terminus \
|
||||||
terminus-font \
|
fonts-dejavu \
|
||||||
ttf-dejavu \
|
fonts-liberation \
|
||||||
ttf-liberation \
|
libcairo2 \
|
||||||
util-linux-login && \
|
libspice-client-glib-2.0-8
|
||||||
xargs apk add --no-cache < ${PREFIX_DIR}/DEPENDENCIES
|
|
||||||
|
|
||||||
# Checks the operating status every 5 minutes with a timeout of 5 seconds
|
# Checks the operating status every 5 minutes with a timeout of 5 seconds
|
||||||
HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
|
HEALTHCHECK --interval=5m --timeout=5s CMD nc -z 127.0.0.1 4822 || exit 1
|
||||||
|
@ -37,6 +37,7 @@ DIST_SUBDIRS = \
|
|||||||
src/pulse \
|
src/pulse \
|
||||||
src/protocols/kubernetes \
|
src/protocols/kubernetes \
|
||||||
src/protocols/rdp \
|
src/protocols/rdp \
|
||||||
|
src/protocols/spice \
|
||||||
src/protocols/ssh \
|
src/protocols/ssh \
|
||||||
src/protocols/telnet \
|
src/protocols/telnet \
|
||||||
src/protocols/vnc
|
src/protocols/vnc
|
||||||
@ -65,6 +66,10 @@ if ENABLE_RDP
|
|||||||
SUBDIRS += src/protocols/rdp
|
SUBDIRS += src/protocols/rdp
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if ENABLE_SPICE
|
||||||
|
SUBDIRS += src/protocols/spice
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_SSH
|
if ENABLE_SSH
|
||||||
SUBDIRS += src/protocols/ssh
|
SUBDIRS += src/protocols/ssh
|
||||||
endif
|
endif
|
||||||
|
@ -117,7 +117,7 @@ error() {
|
|||||||
##
|
##
|
||||||
usage() {
|
usage() {
|
||||||
cat >&2 <<END
|
cat >&2 <<END
|
||||||
guacctl 1.5.0, Apache Guacamole terminal session control utility.
|
guacctl 1.4.0, Apache Guacamole terminal session control utility.
|
||||||
Usage: guacctl [OPTION] [FILE or NAME]...
|
Usage: guacctl [OPTION] [FILE or NAME]...
|
||||||
|
|
||||||
-d, --download download each of the files listed.
|
-d, --download download each of the files listed.
|
||||||
|
26
configure.ac
26
configure.ac
@ -18,7 +18,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
AC_PREREQ([2.61])
|
AC_PREREQ([2.61])
|
||||||
AC_INIT([guacamole-server], [1.5.0])
|
AC_INIT([guacamole-server], [1.4.0])
|
||||||
AC_CONFIG_AUX_DIR([build-aux])
|
AC_CONFIG_AUX_DIR([build-aux])
|
||||||
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
|
||||||
AM_SILENT_RULES([yes])
|
AM_SILENT_RULES([yes])
|
||||||
@ -634,6 +634,27 @@ then
|
|||||||
|
|
||||||
fi
|
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)
|
# FreeRDP 2 (libfreerdp2, libfreerdp-client2, and libwinpr2)
|
||||||
@ -1188,6 +1209,7 @@ AC_CONFIG_FILES([Makefile
|
|||||||
src/protocols/kubernetes/tests/Makefile
|
src/protocols/kubernetes/tests/Makefile
|
||||||
src/protocols/rdp/Makefile
|
src/protocols/rdp/Makefile
|
||||||
src/protocols/rdp/tests/Makefile
|
src/protocols/rdp/tests/Makefile
|
||||||
|
src/protocols/spice/Makefile
|
||||||
src/protocols/ssh/Makefile
|
src/protocols/ssh/Makefile
|
||||||
src/protocols/telnet/Makefile
|
src/protocols/telnet/Makefile
|
||||||
src/protocols/vnc/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_KUBERNETES], [build_kubernetes=yes], [build_kubernetes=no])
|
||||||
AM_COND_IF([ENABLE_RDP], [build_rdp=yes], [build_rdp=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_SSH], [build_ssh=yes], [build_ssh=no])
|
||||||
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
|
AM_COND_IF([ENABLE_TELNET], [build_telnet=yes], [build_telnet=no])
|
||||||
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
|
AM_COND_IF([ENABLE_VNC], [build_vnc=yes], [build_vnc=no])
|
||||||
@ -1260,6 +1283,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION
|
|||||||
|
|
||||||
Kubernetes .... ${build_kubernetes}
|
Kubernetes .... ${build_kubernetes}
|
||||||
RDP ........... ${build_rdp}
|
RDP ........... ${build_rdp}
|
||||||
|
SPICE ......... ${build_spice}
|
||||||
SSH ........... ${build_ssh}
|
SSH ........... ${build_ssh}
|
||||||
Telnet ........ ${build_telnet}
|
Telnet ........ ${build_telnet}
|
||||||
VNC ........... ${build_vnc}
|
VNC ........... ${build_vnc}
|
||||||
|
@ -166,8 +166,6 @@ void guac_common_display_free(guac_common_display* display) {
|
|||||||
void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
||||||
guac_socket* socket) {
|
guac_socket* socket) {
|
||||||
|
|
||||||
guac_client* client = user->client;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&display->_lock);
|
pthread_mutex_lock(&display->_lock);
|
||||||
|
|
||||||
/* Sunchronize shared cursor */
|
/* Sunchronize shared cursor */
|
||||||
@ -180,9 +178,6 @@ void guac_common_display_dup(guac_common_display* display, guac_user* user,
|
|||||||
guac_common_display_dup_layers(display->layers, user, socket);
|
guac_common_display_dup_layers(display->layers, user, socket);
|
||||||
guac_common_display_dup_layers(display->buffers, user, socket);
|
guac_common_display_dup_layers(display->buffers, user, socket);
|
||||||
|
|
||||||
/* Sends a sync instruction to mark the boundary of the first frame */
|
|
||||||
guac_protocol_send_sync(socket, client->last_sent_timestamp, 1);
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&display->_lock);
|
pthread_mutex_unlock(&display->_lock);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -389,3 +384,4 @@ void guac_common_display_free_buffer(guac_common_display* display,
|
|||||||
pthread_mutex_unlock(&display->_lock);
|
pthread_mutex_unlock(&display->_lock);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
|
||||||
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||||
int pix_fmt, AVRational time_base) {
|
int pix_fmt, AVRational time_base) {
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* co
|
|||||||
}
|
}
|
||||||
|
|
||||||
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||||
const AVCodec *codec, AVDictionary **options,
|
AVCodec *codec, AVDictionary **options,
|
||||||
AVStream* stream) {
|
AVStream* stream) {
|
||||||
|
|
||||||
int ret = avcodec_open2(avcodec_context, codec, options);
|
int ret = avcodec_open2(avcodec_context, codec, options);
|
||||||
|
@ -128,7 +128,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
|
|||||||
* The pointer to the configured AVCodecContext.
|
* The pointer to the configured AVCodecContext.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* codec,
|
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec,
|
||||||
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
int bitrate, int width, int height, int gop_size, int qmax, int qmin,
|
||||||
int pix_fmt, AVRational time_base);
|
int pix_fmt, AVRational time_base);
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, const AVCodec* co
|
|||||||
* Zero on success, a negative value on error.
|
* Zero on success, a negative value on error.
|
||||||
*/
|
*/
|
||||||
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||||
const AVCodec *codec, AVDictionary **options,
|
AVCodec *codec, AVDictionary **options,
|
||||||
AVStream* stream);
|
AVStream* stream);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
||||||
int width, int height, int bitrate) {
|
int width, int height, int bitrate) {
|
||||||
|
|
||||||
const AVOutputFormat *container_format;
|
AVOutputFormat *container_format;
|
||||||
AVFormatContext *container_format_context;
|
AVFormatContext *container_format_context;
|
||||||
AVStream *video_stream;
|
AVStream *video_stream;
|
||||||
int ret;
|
int ret;
|
||||||
@ -63,7 +63,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
|||||||
container_format = container_format_context->oformat;
|
container_format = container_format_context->oformat;
|
||||||
|
|
||||||
/* Pull codec based on name */
|
/* Pull codec based on name */
|
||||||
const AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
|
AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
|
||||||
if (codec == NULL) {
|
if (codec == NULL) {
|
||||||
guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".",
|
guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".",
|
||||||
codec_name);
|
codec_name);
|
||||||
|
@ -139,7 +139,7 @@ libguac_la_CFLAGS = \
|
|||||||
-Werror -Wall -pedantic
|
-Werror -Wall -pedantic
|
||||||
|
|
||||||
libguac_la_LDFLAGS = \
|
libguac_la_LDFLAGS = \
|
||||||
-version-info 21:0:0 \
|
-version-info 20:0:0 \
|
||||||
-no-undefined \
|
-no-undefined \
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@DL_LIBS@ \
|
@DL_LIBS@ \
|
||||||
|
@ -307,10 +307,6 @@ int guac_client_add_user(guac_client* client, guac_user* user, int argc, char**
|
|||||||
|
|
||||||
pthread_rwlock_unlock(&(client->__users_lock));
|
pthread_rwlock_unlock(&(client->__users_lock));
|
||||||
|
|
||||||
/* Notify owner of user joining connection. */
|
|
||||||
if (retval == 0 && !user->owner)
|
|
||||||
guac_client_owner_notify_join(client, user);
|
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -337,10 +333,6 @@ void guac_client_remove_user(guac_client* client, guac_user* user) {
|
|||||||
|
|
||||||
pthread_rwlock_unlock(&(client->__users_lock));
|
pthread_rwlock_unlock(&(client->__users_lock));
|
||||||
|
|
||||||
/* Update owner of user having left the connection. */
|
|
||||||
if (!user->owner)
|
|
||||||
guac_client_owner_notify_leave(client, user);
|
|
||||||
|
|
||||||
/* Call handler, if defined */
|
/* Call handler, if defined */
|
||||||
if (user->leave_handler)
|
if (user->leave_handler)
|
||||||
user->leave_handler(user);
|
user->leave_handler(user);
|
||||||
@ -683,36 +675,6 @@ static void* __webp_support_callback(guac_user* user, void* data) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback function which is invoked by guac_client_owner_supports_msg()
|
|
||||||
* to determine if the owner of a client supports the "msg" instruction,
|
|
||||||
* returning zero if the user does not support the instruction or non-zero if
|
|
||||||
* the user supports it.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The guac_user that will be checked for "msg" instruction support.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* Data provided to the callback. This value is never used within this
|
|
||||||
* callback.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* A non-zero integer if the provided user who owns the connection supports
|
|
||||||
* the "msg" instruction, or zero if the user does not. The integer is cast
|
|
||||||
* as a void*.
|
|
||||||
*/
|
|
||||||
static void* guac_owner_supports_msg_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
return (void*) ((intptr_t) guac_user_supports_msg(user));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_client_owner_supports_msg(guac_client* client) {
|
|
||||||
|
|
||||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_owner_supports_msg_callback, NULL));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback function which is invoked by guac_client_owner_supports_required()
|
* A callback function which is invoked by guac_client_owner_supports_required()
|
||||||
* to determine if the owner of a client supports the "required" instruction,
|
* to determine if the owner of a client supports the "required" instruction,
|
||||||
@ -743,124 +705,6 @@ int guac_client_owner_supports_required(guac_client* client) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback function that is invokved by guac_client_owner_notify_join() to
|
|
||||||
* notify the owner of a connection that another user has joined the
|
|
||||||
* connection, returning zero if the message is sent successfully, or non-zero
|
|
||||||
* if an error occurs.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to send the notification to, which will be the owner of the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The data provided to the callback, which is the user that is joining the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the message is sent successfully to the owner, otherwise
|
|
||||||
* non-zero, cast as a void*.
|
|
||||||
*/
|
|
||||||
static void* guac_client_owner_notify_join_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
const guac_user* joiner = (const guac_user *) data;
|
|
||||||
|
|
||||||
if (user == NULL)
|
|
||||||
return (void*) ((intptr_t) -1);
|
|
||||||
|
|
||||||
char* log_owner = "owner";
|
|
||||||
if (user->info.name != NULL)
|
|
||||||
log_owner = (char *) user->info.name;
|
|
||||||
|
|
||||||
char* log_joiner = "anonymous";
|
|
||||||
char* send_joiner = "";
|
|
||||||
if (joiner->info.name != NULL) {
|
|
||||||
log_joiner = (char *) joiner->info.name;
|
|
||||||
send_joiner = (char *) joiner->info.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" joining.",
|
|
||||||
log_owner, log_joiner);
|
|
||||||
|
|
||||||
/* Send user joined notification to owner. */
|
|
||||||
const char* args[] = { (const char*)joiner->user_id, (const char*)send_joiner, NULL };
|
|
||||||
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_JOINED, args));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner) {
|
|
||||||
|
|
||||||
/* Don't send msg instruction if client does not support it. */
|
|
||||||
if (!guac_client_owner_supports_msg(client)) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Client does not support the \"msg\" instruction and "
|
|
||||||
"will not be notified of the user joining the connection.");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_join_callback, joiner));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback function that is invokved by guac_client_owner_notify_leave() to
|
|
||||||
* notify the owner of a connection that another user has left the connection,
|
|
||||||
* returning zero if the message is sent successfully, or non-zero
|
|
||||||
* if an error occurs.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The user to send the notification to, which will be the owner of the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* The data provided to the callback, which is the user that is leaving the
|
|
||||||
* connection.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the message is sent successfully to the owner, otherwise
|
|
||||||
* non-zero, cast as a void*.
|
|
||||||
*/
|
|
||||||
static void* guac_client_owner_notify_leave_callback(guac_user* user, void* data) {
|
|
||||||
|
|
||||||
const guac_user* quitter = (const guac_user *) data;
|
|
||||||
|
|
||||||
if (user == NULL)
|
|
||||||
return (void*) ((intptr_t) -1);
|
|
||||||
|
|
||||||
char* log_owner = "owner";
|
|
||||||
if (user->info.name != NULL)
|
|
||||||
log_owner = (char *) user->info.name;
|
|
||||||
|
|
||||||
char* log_quitter = "anonymous";
|
|
||||||
char* send_quitter = "";
|
|
||||||
if (quitter->info.name != NULL) {
|
|
||||||
log_quitter = (char *) quitter->info.name;
|
|
||||||
send_quitter = (char *) quitter->info.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
guac_user_log(user, GUAC_LOG_DEBUG, "Notifying owner \"%s\" of \"%s\" leaving.",
|
|
||||||
log_owner, log_quitter);
|
|
||||||
|
|
||||||
/* Send user left notification to owner. */
|
|
||||||
const char* args[] = { (const char*)quitter->user_id, (const char*)send_quitter, NULL };
|
|
||||||
return (void*) ((intptr_t) guac_protocol_send_msg(user->socket, GUAC_MESSAGE_USER_LEFT, args));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter) {
|
|
||||||
|
|
||||||
/* Don't send msg instruction if client does not support it. */
|
|
||||||
if (!guac_client_owner_supports_msg(client)) {
|
|
||||||
guac_client_log(client, GUAC_LOG_DEBUG,
|
|
||||||
"Client does not support the \"msg\" instruction and "
|
|
||||||
"will not be notified of the user leaving the connection.");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int) ((intptr_t) guac_client_for_owner(client, guac_client_owner_notify_leave_callback, quitter));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_client_supports_webp(guac_client* client) {
|
int guac_client_supports_webp(guac_client* client) {
|
||||||
|
|
||||||
#ifdef ENABLE_WEBP
|
#ifdef ENABLE_WEBP
|
||||||
|
@ -737,21 +737,6 @@ void guac_client_stream_webp(guac_client* client, guac_socket* socket,
|
|||||||
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
||||||
cairo_surface_t* surface, int quality, int lossless);
|
cairo_surface_t* surface, int quality, int lossless);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the owner of the given client supports the "msg"
|
|
||||||
* instruction, returning non-zero if the client owner does support the
|
|
||||||
* instruction, or zero if the owner does not.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The Guacamole client whose owner should be checked for supporting
|
|
||||||
* the "msg" instruction.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Non-zero if the owner of the given client supports the "msg"
|
|
||||||
* instruction, zero otherwise.
|
|
||||||
*/
|
|
||||||
int guac_client_owner_supports_msg(guac_client* client);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the owner of the given client supports the "required"
|
* Returns whether the owner of the given client supports the "required"
|
||||||
* instruction, returning non-zero if the client owner does support the
|
* instruction, returning non-zero if the client owner does support the
|
||||||
@ -767,42 +752,6 @@ int guac_client_owner_supports_msg(guac_client* client);
|
|||||||
*/
|
*/
|
||||||
int guac_client_owner_supports_required(guac_client* client);
|
int guac_client_owner_supports_required(guac_client* client);
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the owner of the given client that a user has joined the connection,
|
|
||||||
* and returns zero if the message was sent successfully, or non-zero if the
|
|
||||||
* notification failed.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The Guacamole Client whose owner should be notified of a user joining
|
|
||||||
* the connection.
|
|
||||||
*
|
|
||||||
* @param joiner
|
|
||||||
* The Guacamole User who joined the connection.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the notification to the owner was sent successfully, or non-zero
|
|
||||||
* if an error occurred.
|
|
||||||
*/
|
|
||||||
int guac_client_owner_notify_join(guac_client* client, guac_user* joiner);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the owner of the given client that a user has left the connection,
|
|
||||||
* and returns zero if the message was sent successfully, or non-zero if the
|
|
||||||
* notification failed.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The Guacamole Client whose owner should be notified of a user leaving
|
|
||||||
* the connection.
|
|
||||||
*
|
|
||||||
* @param quitter
|
|
||||||
* The Guacamole User who left the connection.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the notification to the owner was sent successfully, or non-zero
|
|
||||||
* if an error occurred.
|
|
||||||
*/
|
|
||||||
int guac_client_owner_notify_leave(guac_client* client, guac_user* quitter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether all users of the given client support WebP. If any user does
|
* Returns whether all users of the given client support WebP. If any user does
|
||||||
* not support WebP, or the server cannot encode WebP images, zero is returned.
|
* not support WebP, or the server cannot encode WebP images, zero is returned.
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
* This version is passed by the __guac_protocol_send_args() function from the
|
* This version is passed by the __guac_protocol_send_args() function from the
|
||||||
* server to the client during the client/server handshake.
|
* server to the client during the client/server handshake.
|
||||||
*/
|
*/
|
||||||
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_5_0"
|
#define GUACAMOLE_PROTOCOL_VERSION "VERSION_1_3_0"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of bytes that should be sent in any one blob instruction
|
* The maximum number of bytes that should be sent in any one blob instruction
|
||||||
|
@ -306,40 +306,9 @@ typedef enum guac_protocol_version {
|
|||||||
* allowing connections in guacd to request information from the client and
|
* allowing connections in guacd to request information from the client and
|
||||||
* await a response.
|
* await a response.
|
||||||
*/
|
*/
|
||||||
GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300,
|
GUAC_PROTOCOL_VERSION_1_3_0 = 0x010300
|
||||||
|
|
||||||
/**
|
|
||||||
* Protocol version 1.5.0, which supports the "msg" instruction, allowing
|
|
||||||
* messages to be sent to the client, and adds support for the "name"
|
|
||||||
* handshake instruction.
|
|
||||||
*/
|
|
||||||
GUAC_PROTOCOL_VERSION_1_5_0 = 0x010500
|
|
||||||
|
|
||||||
} guac_protocol_version;
|
} guac_protocol_version;
|
||||||
|
|
||||||
/**
|
|
||||||
* A type that represents codes for human-readable messages sent by the "msg"
|
|
||||||
* instruction to the Client, that will be displayed in the client's browser.
|
|
||||||
* The codes will be interpreted by the client into translatable messages, and
|
|
||||||
* make take arguments, as noted below.
|
|
||||||
*/
|
|
||||||
typedef enum guac_message_type {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message that notifies the owner of a connection that another user has
|
|
||||||
* joined their connection. There should be a single argument provided, the
|
|
||||||
* name of the user who has joined.
|
|
||||||
*/
|
|
||||||
GUAC_MESSAGE_USER_JOINED = 0x0001,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message that notifies the owner of a connection that another user has
|
|
||||||
* left their connection. There should be a single argument provided, the
|
|
||||||
* name of the user who has left.
|
|
||||||
*/
|
|
||||||
GUAC_MESSAGE_USER_LEFT = 0x0002
|
|
||||||
|
|
||||||
} guac_message_type;
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -171,27 +171,6 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...);
|
|||||||
int vguac_protocol_send_log(guac_socket* socket, const char* format,
|
int vguac_protocol_send_log(guac_socket* socket, const char* format,
|
||||||
va_list args);
|
va_list args);
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends the given string over the socket to be displayed on the client. Returns
|
|
||||||
* zero if the message was sent successfully or non-zero if an error occurs.
|
|
||||||
*
|
|
||||||
* @param socket
|
|
||||||
* The guac_socket connection to send the message to.
|
|
||||||
*
|
|
||||||
* @param msg
|
|
||||||
* The message code to send to the client.
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
* A null-terminated array of strings that will be provided to the client
|
|
||||||
* as part of the message, that the client may then place in the message,
|
|
||||||
* or null if the message requires no arguments.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the message is sent successfully; otherwise non-zero.
|
|
||||||
*/
|
|
||||||
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
|
|
||||||
const char** args);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a mouse instruction over the given guac_socket connection.
|
* Sends a mouse instruction over the given guac_socket connection.
|
||||||
*
|
*
|
||||||
|
@ -88,7 +88,7 @@ struct guac_user_info {
|
|||||||
* stated resolution of the display size request is recommended.
|
* stated resolution of the display size request is recommended.
|
||||||
*/
|
*/
|
||||||
int optimal_resolution;
|
int optimal_resolution;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The timezone of the remote system. If the client does not provide
|
* The timezone of the remote system. If the client does not provide
|
||||||
* a specific timezone then this will be NULL. The format of the timezone
|
* a specific timezone then this will be NULL. The format of the timezone
|
||||||
@ -102,14 +102,6 @@ struct guac_user_info {
|
|||||||
*/
|
*/
|
||||||
guac_protocol_version protocol_version;
|
guac_protocol_version protocol_version;
|
||||||
|
|
||||||
/**
|
|
||||||
* The human-readable name of the Guacamole user, supplied by the client
|
|
||||||
* during the handshake. This is an arbitrary value, with no requirements or
|
|
||||||
* constraints, including that it need not uniquely identify the user.
|
|
||||||
* If the client does not provide a name then this will be NULL.
|
|
||||||
*/
|
|
||||||
const char* name;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct guac_user {
|
struct guac_user {
|
||||||
@ -858,17 +850,6 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
|
|||||||
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
guac_composite_mode mode, const guac_layer* layer, int x, int y,
|
||||||
cairo_surface_t* surface, int quality, int lossless);
|
cairo_surface_t* surface, int quality, int lossless);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the given user supports the "msg" instruction.
|
|
||||||
*
|
|
||||||
* @param user
|
|
||||||
* The Guacamole user to check for support of the "msg" instruction.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Non-zero if the user supports the "msg" instruction, otherwise zero.
|
|
||||||
*/
|
|
||||||
int guac_user_supports_msg(guac_user* user);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given user supports the "required" instruction.
|
* Returns whether the given user supports the "required" instruction.
|
||||||
*
|
*
|
||||||
|
@ -65,7 +65,6 @@ guac_protocol_version_mapping guac_protocol_version_table[] = {
|
|||||||
{ GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" },
|
{ GUAC_PROTOCOL_VERSION_1_0_0, "VERSION_1_0_0" },
|
||||||
{ GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" },
|
{ GUAC_PROTOCOL_VERSION_1_1_0, "VERSION_1_1_0" },
|
||||||
{ GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" },
|
{ GUAC_PROTOCOL_VERSION_1_3_0, "VERSION_1_3_0" },
|
||||||
{ GUAC_PROTOCOL_VERSION_1_5_0, "VERSION_1_5_0" },
|
|
||||||
{ GUAC_PROTOCOL_VERSION_UNKNOWN, NULL }
|
{ GUAC_PROTOCOL_VERSION_UNKNOWN, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -659,23 +658,6 @@ int guac_protocol_send_log(guac_socket* socket, const char* format, ...) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int guac_protocol_send_msg(guac_socket* socket, guac_message_type msg,
|
|
||||||
const char** args) {
|
|
||||||
|
|
||||||
int ret_val;
|
|
||||||
|
|
||||||
guac_socket_instruction_begin(socket);
|
|
||||||
ret_val =
|
|
||||||
guac_socket_write_string(socket, "3.msg,")
|
|
||||||
|| __guac_socket_write_length_int(socket, msg)
|
|
||||||
|| guac_socket_write_array(socket, args)
|
|
||||||
|| guac_socket_write_string(socket, ";");
|
|
||||||
|
|
||||||
guac_socket_instruction_end(socket);
|
|
||||||
return ret_val;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream,
|
int guac_protocol_send_file(guac_socket* socket, const guac_stream* stream,
|
||||||
const char* mimetype, const char* name) {
|
const char* mimetype, const char* name) {
|
||||||
|
|
||||||
|
@ -27,11 +27,11 @@
|
|||||||
*/
|
*/
|
||||||
void test_guac_protocol__version_to_string() {
|
void test_guac_protocol__version_to_string() {
|
||||||
|
|
||||||
guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_5_0;
|
guac_protocol_version version_a = GUAC_PROTOCOL_VERSION_1_3_0;
|
||||||
guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0;
|
guac_protocol_version version_b = GUAC_PROTOCOL_VERSION_1_0_0;
|
||||||
guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN;
|
guac_protocol_version version_c = GUAC_PROTOCOL_VERSION_UNKNOWN;
|
||||||
|
|
||||||
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_5_0");
|
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_a), "VERSION_1_3_0");
|
||||||
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0");
|
CU_ASSERT_STRING_EQUAL(guac_protocol_version_to_string(version_b), "VERSION_1_0_0");
|
||||||
CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c));
|
CU_ASSERT_PTR_NULL(guac_protocol_version_to_string(version_c));
|
||||||
|
|
||||||
|
@ -64,7 +64,6 @@ __guac_instruction_handler_mapping __guac_handshake_handler_map[] = {
|
|||||||
{"video", __guac_handshake_video_handler},
|
{"video", __guac_handshake_video_handler},
|
||||||
{"image", __guac_handshake_image_handler},
|
{"image", __guac_handshake_image_handler},
|
||||||
{"timezone", __guac_handshake_timezone_handler},
|
{"timezone", __guac_handshake_timezone_handler},
|
||||||
{"name", __guac_handshake_name_handler},
|
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -685,23 +684,6 @@ int __guac_handshake_image_handler(guac_user* user, int argc, char** argv) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int __guac_handshake_name_handler(guac_user* user, int argc, char** argv) {
|
|
||||||
|
|
||||||
/* Free any past value for the user's name */
|
|
||||||
free((char *) user->info.name);
|
|
||||||
|
|
||||||
/* If a value is provided for the name, copy it into guac_user. */
|
|
||||||
if (argc > 0 && strcmp(argv[0], ""))
|
|
||||||
user->info.name = (const char*) strdup(argv[0]);
|
|
||||||
|
|
||||||
/* No or empty value was provided, so make sure this is NULLed out. */
|
|
||||||
else
|
|
||||||
user->info.name = NULL;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
|
int __guac_handshake_timezone_handler(guac_user* user, int argc, char** argv) {
|
||||||
|
|
||||||
/* Free any past value */
|
/* Free any past value */
|
||||||
|
@ -218,13 +218,6 @@ __guac_instruction_handler __guac_handshake_video_handler;
|
|||||||
*/
|
*/
|
||||||
__guac_instruction_handler __guac_handshake_image_handler;
|
__guac_instruction_handler __guac_handshake_image_handler;
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal handler function that is called when the name instruction is
|
|
||||||
* received during the handshake process, specifying the name of the Guacamole
|
|
||||||
* user establishing the connection.
|
|
||||||
*/
|
|
||||||
__guac_instruction_handler __guac_handshake_name_handler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal handler function that is called when the timezone instruction is
|
* Internal handler function that is called when the timezone instruction is
|
||||||
* received during the handshake process, specifying the timezone of the
|
* received during the handshake process, specifying the timezone of the
|
||||||
|
@ -296,7 +296,6 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
|
|||||||
user->info.audio_mimetypes = NULL;
|
user->info.audio_mimetypes = NULL;
|
||||||
user->info.image_mimetypes = NULL;
|
user->info.image_mimetypes = NULL;
|
||||||
user->info.video_mimetypes = NULL;
|
user->info.video_mimetypes = NULL;
|
||||||
user->info.name = NULL;
|
|
||||||
user->info.timezone = NULL;
|
user->info.timezone = NULL;
|
||||||
|
|
||||||
/* Count number of arguments. */
|
/* Count number of arguments. */
|
||||||
@ -371,8 +370,7 @@ int guac_user_handle_connection(guac_user* user, int usec_timeout) {
|
|||||||
guac_free_mimetypes((char **) user->info.image_mimetypes);
|
guac_free_mimetypes((char **) user->info.image_mimetypes);
|
||||||
guac_free_mimetypes((char **) user->info.video_mimetypes);
|
guac_free_mimetypes((char **) user->info.video_mimetypes);
|
||||||
|
|
||||||
/* Free name and timezone info. */
|
/* Free timezone info. */
|
||||||
free((char *) user->info.name);
|
|
||||||
free((char *) user->info.timezone);
|
free((char *) user->info.timezone);
|
||||||
|
|
||||||
guac_parser_free(parser);
|
guac_parser_free(parser);
|
||||||
|
@ -316,15 +316,6 @@ void guac_user_stream_webp(guac_user* user, guac_socket* socket,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int guac_user_supports_msg(guac_user* user) {
|
|
||||||
|
|
||||||
if (user == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return (user->info.protocol_version >= GUAC_PROTOCOL_VERSION_1_5_0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int guac_user_supports_required(guac_user* user) {
|
int guac_user_supports_required(guac_user* user) {
|
||||||
|
|
||||||
if (user == NULL)
|
if (user == NULL)
|
||||||
|
@ -230,7 +230,6 @@ BUILT_SOURCES = \
|
|||||||
rdp_keymaps = \
|
rdp_keymaps = \
|
||||||
$(srcdir)/keymaps/base.keymap \
|
$(srcdir)/keymaps/base.keymap \
|
||||||
$(srcdir)/keymaps/failsafe.keymap \
|
$(srcdir)/keymaps/failsafe.keymap \
|
||||||
$(srcdir)/keymaps/cs-cz-qwertz.keymap \
|
|
||||||
$(srcdir)/keymaps/de_de_qwertz.keymap \
|
$(srcdir)/keymaps/de_de_qwertz.keymap \
|
||||||
$(srcdir)/keymaps/de_ch_qwertz.keymap \
|
$(srcdir)/keymaps/de_ch_qwertz.keymap \
|
||||||
$(srcdir)/keymaps/en_gb_qwerty.keymap \
|
$(srcdir)/keymaps/en_gb_qwerty.keymap \
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
#
|
|
||||||
# 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 "cs-cz-qwertz"
|
|
||||||
freerdp "KBD_CZECH"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Basic keys
|
|
||||||
#
|
|
||||||
|
|
||||||
map -caps -altgr -shift 0x29 0x02..0x0D ~ ";+ěščřžýáíé=´"
|
|
||||||
map -caps -altgr -shift 0x10..0x1B ~ "qwertzuıopú)"
|
|
||||||
map -caps -altgr -shift 0x1E..0x28 0x2B ~ "asdfghjklů§¨"
|
|
||||||
map -caps -altgr -shift 0x2C..0x35 ~ "yxcvbnm,.-"
|
|
||||||
|
|
||||||
map -caps -altgr +shift 0x29 0x02..0x0D ~ "°1234567890%ˇ"
|
|
||||||
map -caps -altgr +shift 0x10..0x1B ~ "QWERTZUIOP/("
|
|
||||||
map -caps -altgr +shift 0x1E..0x28 0x2B ~ "ASDFGHJKL"!'"
|
|
||||||
map -caps -altgr +shift 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 0x2C..0x35 ~ "YXCVBNM,.-"
|
|
||||||
|
|
||||||
map +caps -altgr +shift 0x29 0x02..0x0D ~ "°1234567890%ˇ"
|
|
||||||
map +caps -altgr +shift 0x10..0x1B ~ "qwertzuiop/("
|
|
||||||
map +caps -altgr +shift 0x1E..0x28 0x2B ~ "asdfghjkl"!'"
|
|
||||||
map +caps -altgr +shift 0x2C..0x35 ~ "yxcvbnm?:_"
|
|
||||||
|
|
||||||
#
|
|
||||||
# Keys requiring AltGr
|
|
||||||
#
|
|
||||||
|
|
||||||
map +altgr -shift 0x02 ~ "~"
|
|
||||||
|
|
||||||
map +altgr -shift 0x10 ~ "\"
|
|
||||||
map +altgr -shift 0x11 ~ "|"
|
|
||||||
map +altgr -shift 0x12 ~ "€"
|
|
||||||
map +altgr -shift 0x1A ~ "÷"
|
|
||||||
map +altgr -shift 0x1B ~ "×"
|
|
||||||
|
|
||||||
map +altgr -shift 0x1F ~ "đ"
|
|
||||||
map +altgr -shift 0x20 ~ "Đ"
|
|
||||||
map +altgr -shift 0x21 ~ "["
|
|
||||||
map +altgr -shift 0x22 ~ "]"
|
|
||||||
map +altgr -shift 0x25 ~ "ł"
|
|
||||||
map +altgr -shift 0x26 ~ "Ł"
|
|
||||||
map +altgr -shift 0x27 ~ "$"
|
|
||||||
map +altgr -shift 0x28 ~ "ß"
|
|
||||||
map +altgr -shift 0x2B ~ "¤"
|
|
||||||
|
|
||||||
map +altgr -shift 0x2D ~ "#"
|
|
||||||
map +altgr -shift 0x2E ~ "&"
|
|
||||||
map +altgr -shift 0x2F ~ "@"
|
|
||||||
map +altgr -shift 0x30 ~ "{"
|
|
||||||
map +altgr -shift 0x31 ~ "}"
|
|
||||||
map +altgr -shift 0x33 ~ "<"
|
|
||||||
map +altgr -shift 0x34 ~ ">"
|
|
||||||
map +altgr -shift 0x35 ~ "*"
|
|
||||||
|
|
||||||
# END
|
|
139
src/protocols/spice/Makefile.am
Normal file
139
src/protocols/spice/Makefile.am
Normal 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
|
53
src/protocols/spice/argv.c
Normal file
53
src/protocols/spice/argv.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
47
src/protocols/spice/argv.h
Normal file
47
src/protocols/spice/argv.h
Normal 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 */
|
||||||
|
|
71
src/protocols/spice/auth.c
Normal file
71
src/protocols/spice/auth.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
46
src/protocols/spice/auth.h
Normal file
46
src/protocols/spice/auth.h
Normal 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 */
|
||||||
|
|
360
src/protocols/spice/channels/audio.c
Normal file
360
src/protocols/spice/channels/audio.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
138
src/protocols/spice/channels/audio.h
Normal file
138
src/protocols/spice/channels/audio.h
Normal 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 */
|
||||||
|
|
184
src/protocols/spice/channels/clipboard.c
Normal file
184
src/protocols/spice/channels/clipboard.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
131
src/protocols/spice/channels/clipboard.h
Normal file
131
src/protocols/spice/channels/clipboard.h
Normal 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 */
|
||||||
|
|
78
src/protocols/spice/channels/cursor.c
Normal file
78
src/protocols/spice/channels/cursor.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
101
src/protocols/spice/channels/cursor.h
Normal file
101
src/protocols/spice/channels/cursor.h
Normal 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 */
|
||||||
|
|
167
src/protocols/spice/channels/display.c
Normal file
167
src/protocols/spice/channels/display.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
187
src/protocols/spice/channels/display.h
Normal file
187
src/protocols/spice/channels/display.h
Normal 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 */
|
||||||
|
|
321
src/protocols/spice/channels/file-download.c
Normal file
321
src/protocols/spice/channels/file-download.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
83
src/protocols/spice/channels/file-download.h
Normal file
83
src/protocols/spice/channels/file-download.h
Normal 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
|
||||||
|
|
127
src/protocols/spice/channels/file-ls.c
Normal file
127
src/protocols/spice/channels/file-ls.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
66
src/protocols/spice/channels/file-ls.h
Normal file
66
src/protocols/spice/channels/file-ls.h
Normal 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
|
||||||
|
|
261
src/protocols/spice/channels/file-upload.c
Normal file
261
src/protocols/spice/channels/file-upload.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
72
src/protocols/spice/channels/file-upload.h
Normal file
72
src/protocols/spice/channels/file-upload.h
Normal 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
|
||||||
|
|
701
src/protocols/spice/channels/file.c
Normal file
701
src/protocols/spice/channels/file.c
Normal 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"
|
461
src/protocols/spice/channels/file.h
Normal file
461
src/protocols/spice/channels/file.h
Normal 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 */
|
||||||
|
|
382
src/protocols/spice/client.c
Normal file
382
src/protocols/spice/client.c
Normal 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.");
|
||||||
|
|
||||||
|
}
|
73
src/protocols/spice/client.h
Normal file
73
src/protocols/spice/client.h
Normal 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 */
|
||||||
|
|
176
src/protocols/spice/decompose.c
Normal file
176
src/protocols/spice/decompose.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
50
src/protocols/spice/decompose.h
Normal file
50
src/protocols/spice/decompose.h
Normal 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
154
src/protocols/spice/input.c
Normal 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.");
|
||||||
|
|
||||||
|
}
|
50
src/protocols/spice/input.h
Normal file
50
src/protocols/spice/input.h
Normal 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 */
|
||||||
|
|
652
src/protocols/spice/keyboard.c
Normal file
652
src/protocols/spice/keyboard.c
Normal 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));
|
||||||
|
|
||||||
|
}
|
323
src/protocols/spice/keyboard.h
Normal file
323
src/protocols/spice/keyboard.h
Normal 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
|
41
src/protocols/spice/keymap.c
Normal file
41
src/protocols/spice/keymap.c
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
202
src/protocols/spice/keymap.h
Normal file
202
src/protocols/spice/keymap.h
Normal 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
|
||||||
|
|
93
src/protocols/spice/keymaps/base.keymap
Normal file
93
src/protocols/spice/keymaps/base.keymap
Normal 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
|
||||||
|
|
73
src/protocols/spice/keymaps/da_dk_qwerty.keymap
Normal file
73
src/protocols/spice/keymaps/da_dk_qwerty.keymap
Normal 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
|
67
src/protocols/spice/keymaps/de_ch_qwertz.keymap
Normal file
67
src/protocols/spice/keymaps/de_ch_qwertz.keymap
Normal 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
|
74
src/protocols/spice/keymaps/de_de_qwertz.keymap
Normal file
74
src/protocols/spice/keymaps/de_de_qwertz.keymap
Normal 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
|
||||||
|
|
79
src/protocols/spice/keymaps/en_gb_qwerty.keymap
Normal file
79
src/protocols/spice/keymaps/en_gb_qwerty.keymap
Normal 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 ~ "Ç"
|
||||||
|
|
42
src/protocols/spice/keymaps/en_us_qwerty.keymap
Normal file
42
src/protocols/spice/keymaps/en_us_qwerty.keymap
Normal 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<>?"
|
||||||
|
|
63
src/protocols/spice/keymaps/es_es_qwerty.keymap
Normal file
63
src/protocols/spice/keymaps/es_es_qwerty.keymap
Normal 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)
|
63
src/protocols/spice/keymaps/es_latam_qwerty.keymap
Normal file
63
src/protocols/spice/keymaps/es_latam_qwerty.keymap
Normal 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
|
||||||
|
|
22
src/protocols/spice/keymaps/failsafe.keymap
Normal file
22
src/protocols/spice/keymaps/failsafe.keymap
Normal 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"
|
||||||
|
|
76
src/protocols/spice/keymaps/fr_be_azerty.keymap
Normal file
76
src/protocols/spice/keymaps/fr_be_azerty.keymap
Normal 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
|
||||||
|
|
54
src/protocols/spice/keymaps/fr_ca_qwerty.keymap
Normal file
54
src/protocols/spice/keymaps/fr_ca_qwerty.keymap
Normal 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
|
68
src/protocols/spice/keymaps/fr_ch_qwertz.keymap
Normal file
68
src/protocols/spice/keymaps/fr_ch_qwertz.keymap
Normal 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
|
||||||
|
|
65
src/protocols/spice/keymaps/fr_fr_azerty.keymap
Normal file
65
src/protocols/spice/keymaps/fr_fr_azerty.keymap
Normal 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
|
||||||
|
|
253
src/protocols/spice/keymaps/generate.pl
Executable file
253
src/protocols/spice/keymaps/generate.pl
Executable 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;
|
||||||
|
|
108
src/protocols/spice/keymaps/hu_hu_qwertz.keymap
Normal file
108
src/protocols/spice/keymaps/hu_hu_qwertz.keymap
Normal 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
|
59
src/protocols/spice/keymaps/it_it_qwerty.keymap
Normal file
59
src/protocols/spice/keymaps/it_it_qwerty.keymap
Normal 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 ~ "}"
|
||||||
|
|
35
src/protocols/spice/keymaps/ja_jp_qwerty.keymap
Normal file
35
src/protocols/spice/keymaps/ja_jp_qwerty.keymap
Normal 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
|
75
src/protocols/spice/keymaps/no_no_qwerty.keymap
Normal file
75
src/protocols/spice/keymaps/no_no_qwerty.keymap
Normal 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
|
63
src/protocols/spice/keymaps/pl_pl_qwerty.keymap
Normal file
63
src/protocols/spice/keymaps/pl_pl_qwerty.keymap
Normal 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 ~ "żźćń"
|
66
src/protocols/spice/keymaps/pt_br_qwerty.keymap
Normal file
66
src/protocols/spice/keymaps/pt_br_qwerty.keymap
Normal 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
|
74
src/protocols/spice/keymaps/sv_se_qwerty.keymap
Normal file
74
src/protocols/spice/keymaps/sv_se_qwerty.keymap
Normal 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
|
||||||
|
|
92
src/protocols/spice/keymaps/tr_tr_qwerty.keymap
Normal file
92
src/protocols/spice/keymaps/tr_tr_qwerty.keymap
Normal 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
67
src/protocols/spice/log.c
Normal 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
67
src/protocols/spice/log.h
Normal 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 */
|
||||||
|
|
675
src/protocols/spice/settings.c
Normal file
675
src/protocols/spice/settings.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
345
src/protocols/spice/settings.h
Normal file
345
src/protocols/spice/settings.h
Normal 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 */
|
42
src/protocols/spice/sftp.c
Normal file
42
src/protocols/spice/sftp.c
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
34
src/protocols/spice/sftp.h
Normal file
34
src/protocols/spice/sftp.h
Normal 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 */
|
||||||
|
|
445
src/protocols/spice/spice-constants.h
Normal file
445
src/protocols/spice/spice-constants.h
Normal 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 */
|
||||||
|
|
49
src/protocols/spice/spice-defaults.h
Normal file
49
src/protocols/spice/spice-defaults.h
Normal 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
189
src/protocols/spice/spice.c
Normal 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
206
src/protocols/spice/spice.h
Normal 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
129
src/protocols/spice/user.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
38
src/protocols/spice/user.h
Normal file
38
src/protocols/spice/user.h
Normal 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 */
|
||||||
|
|
@ -77,8 +77,6 @@ libguac_terminal_la_LIBADD = \
|
|||||||
@LIBGUAC_LTLIB@
|
@LIBGUAC_LTLIB@
|
||||||
|
|
||||||
libguac_terminal_la_LDFLAGS = \
|
libguac_terminal_la_LDFLAGS = \
|
||||||
-version-info 0:0:0 \
|
|
||||||
-no-undefined \
|
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@MATH_LIBS@ \
|
@MATH_LIBS@ \
|
||||||
@PANGO_LIBS@ \
|
@PANGO_LIBS@ \
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
#include "common/clipboard.h"
|
#include "common/clipboard.h"
|
||||||
#include "common/cursor.h"
|
#include "common/cursor.h"
|
||||||
#include "common/iconv.h"
|
|
||||||
#include "terminal/buffer.h"
|
#include "terminal/buffer.h"
|
||||||
#include "terminal/color-scheme.h"
|
#include "terminal/color-scheme.h"
|
||||||
#include "terminal/common.h"
|
#include "terminal/common.h"
|
||||||
@ -937,13 +936,14 @@ void guac_terminal_commit_cursor(guac_terminal* term) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int guac_terminal_write(guac_terminal* term, const char* buffer, int length) {
|
int guac_terminal_write(guac_terminal* term, const char* c, int size) {
|
||||||
|
|
||||||
guac_terminal_lock(term);
|
guac_terminal_lock(term);
|
||||||
for (int written = 0; written < length; written++) {
|
while (size > 0) {
|
||||||
|
|
||||||
/* Read and advance to next character */
|
/* Read and advance to next character */
|
||||||
char current = *(buffer++);
|
char current = *(c++);
|
||||||
|
size--;
|
||||||
|
|
||||||
/* Write character to typescript, if any */
|
/* Write character to typescript, if any */
|
||||||
if (term->typescript != NULL)
|
if (term->typescript != NULL)
|
||||||
@ -956,7 +956,7 @@ int guac_terminal_write(guac_terminal* term, const char* buffer, int length) {
|
|||||||
guac_terminal_unlock(term);
|
guac_terminal_unlock(term);
|
||||||
|
|
||||||
guac_terminal_notify(term);
|
guac_terminal_notify(term);
|
||||||
return length;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2165,20 +2165,11 @@ void guac_terminal_clipboard_reset(guac_terminal* terminal,
|
|||||||
|
|
||||||
void guac_terminal_clipboard_append(guac_terminal* terminal,
|
void guac_terminal_clipboard_append(guac_terminal* terminal,
|
||||||
const char* data, int length) {
|
const char* data, int length) {
|
||||||
|
guac_common_clipboard_append(terminal->clipboard, data, length);
|
||||||
/* Allocate and clear space for the converted data */
|
|
||||||
char output_data[GUAC_COMMON_CLIPBOARD_MAX_LENGTH];
|
|
||||||
char* output = output_data;
|
|
||||||
|
|
||||||
/* Convert clipboard contents */
|
|
||||||
guac_iconv(GUAC_READ_UTF8_NORMALIZED, &data, length,
|
|
||||||
GUAC_WRITE_UTF8, &output, GUAC_COMMON_CLIPBOARD_MAX_LENGTH);
|
|
||||||
|
|
||||||
guac_common_clipboard_append(terminal->clipboard, output_data, output - output_data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void guac_terminal_remove_user(guac_terminal* terminal, guac_user* user) {
|
void guac_terminal_remove_user(guac_terminal* terminal, guac_user* user) {
|
||||||
|
|
||||||
/* Remove the user from the terminal cursor */
|
/* Remove the user from the terminal cursor */
|
||||||
guac_common_cursor_remove_user(terminal->cursor, user);
|
guac_common_cursor_remove_user(terminal->cursor, user);
|
||||||
}
|
}
|
@ -29,22 +29,6 @@
|
|||||||
#include "terminal.h"
|
#include "terminal.h"
|
||||||
#include "typescript.h"
|
#include "typescript.h"
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for characters printed to the terminal. When a character is printed,
|
|
||||||
* the current char handler for the terminal is called and given that
|
|
||||||
* character.
|
|
||||||
*
|
|
||||||
* @param term
|
|
||||||
* The terminal receiving the character.
|
|
||||||
*
|
|
||||||
* @param c
|
|
||||||
* The received character.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the character was handled successfully, non-zero otherwise.
|
|
||||||
*/
|
|
||||||
typedef int guac_terminal_char_handler(guac_terminal* term, unsigned char c);
|
|
||||||
|
|
||||||
struct guac_terminal {
|
struct guac_terminal {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,35 +141,19 @@ typedef enum guac_terminal_cursor_type {
|
|||||||
} guac_terminal_cursor_type;
|
} guac_terminal_cursor_type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler that is invoked whenever the necessary terminal codes are sent to
|
* Handler for characters printed to the terminal. When a character is printed,
|
||||||
* to the given terminal to change the path for future file uploads.
|
* the current char handler for the terminal is called and given that
|
||||||
*
|
* character.
|
||||||
* @param client
|
*/
|
||||||
* The guac_client associated with the terminal receiving the upload path
|
typedef int guac_terminal_char_handler(guac_terminal* term, unsigned char c);
|
||||||
* change terminal code.
|
|
||||||
*
|
/**
|
||||||
* @param path
|
* Handler for setting the destination path for file uploads.
|
||||||
* The requested path.
|
|
||||||
*/
|
*/
|
||||||
typedef void guac_terminal_upload_path_handler(guac_client* client, char* path);
|
typedef void guac_terminal_upload_path_handler(guac_client* client, char* path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler that is invoked whenever the necessary terminal codes are sent to
|
* Handler for creating an outbound file download stream for a specified file.
|
||||||
* initiate a download of a given remote file.
|
|
||||||
*
|
|
||||||
* @param client
|
|
||||||
* The guac_client associated with the terminal receiving the file download
|
|
||||||
* terminal code.
|
|
||||||
*
|
|
||||||
* @param filename
|
|
||||||
* The name of the requested file. This may be a relative or absolute path,
|
|
||||||
* and it is up to the implementation to define how this value is
|
|
||||||
* interpreted.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The file stream created for the file download, already configured to
|
|
||||||
* properly handle "ack" responses, etc. from the client, or NULL if the
|
|
||||||
* stream could not be created.
|
|
||||||
*/
|
*/
|
||||||
typedef guac_stream* guac_terminal_file_download_handler(guac_client* client, char* filename);
|
typedef guac_stream* guac_terminal_file_download_handler(guac_client* client, char* filename);
|
||||||
|
|
||||||
@ -292,16 +276,11 @@ guac_terminal_options* guac_terminal_options_create(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees all resources associated with the given terminal.
|
* Frees all resources associated with the given terminal.
|
||||||
*
|
|
||||||
* @param term
|
|
||||||
* The terminal to free.
|
|
||||||
*/
|
*/
|
||||||
void guac_terminal_free(guac_terminal* term);
|
void guac_terminal_free(guac_terminal* term);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the upload path handler for the given terminal. The upload path handler
|
* Set the upload path handler for the given terminal.
|
||||||
* is invoked whenever the terminal codes requesting an upload path change are
|
|
||||||
* sent.
|
|
||||||
*
|
*
|
||||||
* @param terminal
|
* @param terminal
|
||||||
* The terminal to set the upload path handler for.
|
* The terminal to set the upload path handler for.
|
||||||
@ -314,14 +293,12 @@ void guac_terminal_set_upload_path_handler(guac_terminal* terminal,
|
|||||||
guac_terminal_upload_path_handler* upload_path_handler);
|
guac_terminal_upload_path_handler* upload_path_handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file download handler for the given terminal. The file download
|
* Set the file download handler for the given terminal.
|
||||||
* handler is invoked whenever the terminal codes requesting download of a
|
|
||||||
* given remote file are sent.
|
|
||||||
*
|
*
|
||||||
* @param terminal
|
* @param terminal
|
||||||
* The terminal to set the file download handler for.
|
* The terminal to set the file download handler for.
|
||||||
*
|
*
|
||||||
* @param file_download_handler
|
* @param upload_path_handler
|
||||||
* The handler to be called whenever the necessary terminal codes are sent to
|
* The handler to be called whenever the necessary terminal codes are sent to
|
||||||
* the given terminal to initiate a download of a given remote file.
|
* the given terminal to initiate a download of a given remote file.
|
||||||
*/
|
*/
|
||||||
@ -331,13 +308,6 @@ void guac_terminal_set_file_download_handler(guac_terminal* terminal,
|
|||||||
/**
|
/**
|
||||||
* Renders a single frame of terminal data. If data is not yet available,
|
* Renders a single frame of terminal data. If data is not yet available,
|
||||||
* this function will block until data is written.
|
* this function will block until data is written.
|
||||||
*
|
|
||||||
* @param terminal
|
|
||||||
* The terminal that should be rendered.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the frame was rendered successfully, non-zero if an error
|
|
||||||
* occurred.
|
|
||||||
*/
|
*/
|
||||||
int guac_terminal_render_frame(guac_terminal* terminal);
|
int guac_terminal_render_frame(guac_terminal* terminal);
|
||||||
|
|
||||||
@ -346,19 +316,6 @@ int guac_terminal_render_frame(guac_terminal* terminal);
|
|||||||
* supplied by calls to guac_terminal_send_key(),
|
* supplied by calls to guac_terminal_send_key(),
|
||||||
* guac_terminal_send_mouse(), and guac_terminal_send_stream(). If input is not
|
* guac_terminal_send_mouse(), and guac_terminal_send_stream(). If input is not
|
||||||
* yet available, this function will block.
|
* yet available, this function will block.
|
||||||
*
|
|
||||||
* @param terminal
|
|
||||||
* The terminal to read from.
|
|
||||||
*
|
|
||||||
* @param c
|
|
||||||
* The buffer that should receive the bytes read.
|
|
||||||
*
|
|
||||||
* @param size
|
|
||||||
* The number of bytes available within the buffer.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes read into the buffer, zero if end-of-file is reached
|
|
||||||
* (STDIN has been closed), or a negative value if an error occurs.
|
|
||||||
*/
|
*/
|
||||||
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
|
int guac_terminal_read_stdin(guac_terminal* terminal, char* c, int size);
|
||||||
|
|
||||||
@ -420,23 +377,8 @@ char* guac_terminal_prompt(guac_terminal* terminal, const char* title,
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given format string and arguments to this terminal's STDOUT in
|
* Writes the given format string and arguments to this terminal's STDOUT in
|
||||||
* the same manner as printf(). The entire populated format string will always
|
* the same manner as printf(). This function may block until space is
|
||||||
* be written unless an error occurs. This function may block until space is
|
|
||||||
* freed in the output buffer by guac_terminal_render_frame().
|
* freed in the output buffer by guac_terminal_render_frame().
|
||||||
*
|
|
||||||
* @param terminal
|
|
||||||
* The terminal to write to.
|
|
||||||
*
|
|
||||||
* @param format
|
|
||||||
* A printf-style format string describing the data to be written to
|
|
||||||
* STDOUT.
|
|
||||||
*
|
|
||||||
* @param ...
|
|
||||||
* Any arguments to use when filling the format string.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes written to STDOUT, or a negative value if an error
|
|
||||||
* occurs preventing the data from being written in its entirety.
|
|
||||||
*/
|
*/
|
||||||
int guac_terminal_printf(guac_terminal* terminal, const char* format, ...);
|
int guac_terminal_printf(guac_terminal* terminal, const char* format, ...);
|
||||||
|
|
||||||
@ -544,24 +486,9 @@ int guac_terminal_send_data(guac_terminal* term, const char* data, int length);
|
|||||||
int guac_terminal_send_string(guac_terminal* term, const char* data);
|
int guac_terminal_send_string(guac_terminal* term, const char* data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given buffer to the given terminal's STDOUT. All requested bytes
|
* Writes the given string of characters to the terminal.
|
||||||
* will be written unless an error occurs. This function may block until space
|
|
||||||
* is freed in the output buffer by guac_terminal_render_frame().
|
|
||||||
*
|
|
||||||
* @param term
|
|
||||||
* The terminal to write to.
|
|
||||||
*
|
|
||||||
* @param buffer
|
|
||||||
* A buffer containing the characters to be written to the terminal.
|
|
||||||
*
|
|
||||||
* @param length
|
|
||||||
* The number of bytes within the given string to write to the terminal.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* The number of bytes written to STDOUT, or a negative value if an error
|
|
||||||
* occurs preventing the data from being written in its entirety.
|
|
||||||
*/
|
*/
|
||||||
int guac_terminal_write(guac_terminal* term, const char* buffer, int length);
|
int guac_terminal_write(guac_terminal* term, const char* c, int size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the handlers of the given guac_stream such that it serves as the
|
* Initializes the handlers of the given guac_stream such that it serves as the
|
||||||
@ -605,7 +532,7 @@ int guac_terminal_send_stream(guac_terminal* term, guac_user* user,
|
|||||||
* STDIN.
|
* STDIN.
|
||||||
*
|
*
|
||||||
* @param ...
|
* @param ...
|
||||||
* Any arguments to use when filling the format string.
|
* Any srguments to use when filling the format string.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* The number of bytes written to STDIN, or a negative value if an error
|
* The number of bytes written to STDIN, or a negative value if an error
|
||||||
@ -633,20 +560,7 @@ void guac_terminal_dup(guac_terminal* term, guac_user* user,
|
|||||||
guac_socket* socket);
|
guac_socket* socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resize the client display and terminal to the given pixel dimensions.
|
* Resize the terminal to the given dimensions.
|
||||||
*
|
|
||||||
* @param term
|
|
||||||
* The terminal to resize.
|
|
||||||
*
|
|
||||||
* @param width
|
|
||||||
* The new terminal width, in pixels.
|
|
||||||
*
|
|
||||||
* @param height
|
|
||||||
* The new terminal height, in pixels.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
* Zero if the terminal was successfully resized to the given dimensions,
|
|
||||||
* non-zero otherwise.
|
|
||||||
*/
|
*/
|
||||||
int guac_terminal_resize(guac_terminal* term, int width, int height);
|
int guac_terminal_resize(guac_terminal* term, int width, int height);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user