GUACAMOLE-1204: Add RDP support for multi-touch events via RDPEI channel.
This commit is contained in:
parent
048a59310b
commit
5eb2867733
@ -56,6 +56,7 @@ libguac_client_rdp_la_SOURCES = \
|
||||
channels/rdpdr/rdpdr-messages.c \
|
||||
channels/rdpdr/rdpdr-printer.c \
|
||||
channels/rdpdr/rdpdr.c \
|
||||
channels/rdpei.c \
|
||||
channels/rdpsnd/rdpsnd-messages.c \
|
||||
channels/rdpsnd/rdpsnd.c \
|
||||
client.c \
|
||||
@ -101,6 +102,7 @@ noinst_HEADERS = \
|
||||
channels/rdpdr/rdpdr-messages.h \
|
||||
channels/rdpdr/rdpdr-printer.h \
|
||||
channels/rdpdr/rdpdr.h \
|
||||
channels/rdpei.h \
|
||||
channels/rdpsnd/rdpsnd-messages.h \
|
||||
channels/rdpsnd/rdpsnd.h \
|
||||
client.h \
|
||||
|
170
src/protocols/rdp/channels/rdpei.c
Normal file
170
src/protocols/rdp/channels/rdpei.c
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
#include "channels/rdpei.h"
|
||||
#include "common/surface.h"
|
||||
#include "plugins/channels.h"
|
||||
#include "rdp.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <freerdp/client/rdpei.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/event.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
guac_rdp_rdpei* guac_rdp_rdpei_alloc() {
|
||||
|
||||
guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei));
|
||||
|
||||
/* Not yet connected */
|
||||
rdpei->rdpei = NULL;
|
||||
|
||||
/* No active touches */
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++)
|
||||
rdpei->touch[i].active = 0;
|
||||
|
||||
return rdpei;
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei) {
|
||||
free(rdpei);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback which associates handlers specific to Guacamole with the
|
||||
* RdpeiClientContext instance allocated by FreeRDP to deal with received
|
||||
* RDPEI (multi-touch input) messages.
|
||||
*
|
||||
* This function is called whenever a channel connects via the PubSub event
|
||||
* system within FreeRDP, but only has any effect if the connected channel is
|
||||
* the RDPEI channel. This specific callback is registered with the
|
||||
* PubSub system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is
|
||||
* called.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*
|
||||
* @param e
|
||||
* Event-specific arguments, mainly the name of the channel, and a
|
||||
* reference to the associated plugin loaded for that channel by FreeRDP.
|
||||
*/
|
||||
static void guac_rdp_rdpei_channel_connected(rdpContext* context,
|
||||
ChannelConnectedEventArgs* e) {
|
||||
|
||||
guac_client* client = ((rdp_freerdp_context*) context)->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei;
|
||||
|
||||
/* Ignore connection event if it's not for the RDPEI channel */
|
||||
if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) != 0)
|
||||
return;
|
||||
|
||||
/* Store reference to the RDPEI plugin once it's connected */
|
||||
RdpeiClientContext* rdpei = (RdpeiClientContext*) e->pInterface;
|
||||
guac_rdpei->rdpei = rdpei;
|
||||
|
||||
/* Declare level of multi-touch support */
|
||||
guac_common_surface_set_multitouch(rdp_client->display->default_surface,
|
||||
GUAC_RDP_RDPEI_MAX_TOUCHES);
|
||||
|
||||
guac_client_log(client, GUAC_LOG_DEBUG, "RDPEI channel will be used for "
|
||||
"multi-touch support.");
|
||||
|
||||
}
|
||||
|
||||
void guac_rdp_rdpei_load_plugin(rdpContext* context) {
|
||||
|
||||
/* Subscribe to and handle channel connected events */
|
||||
PubSub_SubscribeChannelConnected(context->pubSub,
|
||||
(pChannelConnectedEventHandler) guac_rdp_rdpei_channel_connected);
|
||||
|
||||
/* Add "rdpei" channel */
|
||||
guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpei", NULL);
|
||||
|
||||
}
|
||||
|
||||
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
|
||||
double force) {
|
||||
|
||||
int contact_id; /* Ignored */
|
||||
|
||||
/* Track touches only if channel is connected */
|
||||
RdpeiClientContext* context = rdpei->rdpei;
|
||||
if (context == NULL)
|
||||
return 1;
|
||||
|
||||
/* Locate active touch having provided ID */
|
||||
guac_rdp_rdpei_touch* touch = NULL;
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
|
||||
if (rdpei->touch[i].active && rdpei->touch[i].id == id) {
|
||||
touch = &rdpei->touch[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If no such touch exists, add it */
|
||||
if (touch == NULL) {
|
||||
for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) {
|
||||
if (!rdpei->touch[i].active) {
|
||||
touch = &rdpei->touch[i];
|
||||
touch->id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the touch couldn't be added, we're already at maximum touch capacity.
|
||||
* Drop the event. */
|
||||
if (touch == NULL)
|
||||
return 1;
|
||||
|
||||
/* Signal the end of an established touch if touch force has become zero
|
||||
* (this should be a safe comparison, as zero has an exact representation
|
||||
* in floating point, and the client side will use an exact value to
|
||||
* represent the absence of a touch) */
|
||||
if (force == 0.0) {
|
||||
|
||||
/* Ignore release of touches that we aren't tracking */
|
||||
if (!touch->active)
|
||||
return 1;
|
||||
|
||||
context->TouchEnd(context, id, x, y, &contact_id);
|
||||
touch->active = 0;
|
||||
|
||||
}
|
||||
|
||||
/* Signal the start of a touch if this is the first we've seen it */
|
||||
else if (!touch->active) {
|
||||
context->TouchBegin(context, id, x, y, &contact_id);
|
||||
touch->active = 1;
|
||||
}
|
||||
|
||||
/* Established touches need only be updated */
|
||||
else
|
||||
context->TouchUpdate(context, id, x, y, &contact_id);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
154
src/protocols/rdp/channels/rdpei.h
Normal file
154
src/protocols/rdp/channels/rdpei.h
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.
|
||||
*/
|
||||
|
||||
#ifndef GUAC_RDP_CHANNELS_RDPEI_H
|
||||
#define GUAC_RDP_CHANNELS_RDPEI_H
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
#include <freerdp/client/rdpei.h>
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
/**
|
||||
* The maximum number of simultaneously-tracked touches.
|
||||
*/
|
||||
#define GUAC_RDP_RDPEI_MAX_TOUCHES 10
|
||||
|
||||
/**
|
||||
* A single, tracked touch contact.
|
||||
*/
|
||||
typedef struct guac_rdp_rdpei_touch {
|
||||
|
||||
/**
|
||||
* Whether this touch is active (1) or inactive (0). An active touch is
|
||||
* being tracked, while an inactive touch is simple an empty space awaiting
|
||||
* use by some future touch event.
|
||||
*/
|
||||
int active;
|
||||
|
||||
/**
|
||||
* The unique ID representing this touch contact.
|
||||
*/
|
||||
int id;
|
||||
|
||||
/**
|
||||
* The X-coordinate of this touch, in pixels.
|
||||
*/
|
||||
int x;
|
||||
|
||||
/**
|
||||
* The Y-coordinate of this touch, in pixels.
|
||||
*/
|
||||
int y;
|
||||
|
||||
} guac_rdp_rdpei_touch;
|
||||
|
||||
/**
|
||||
* Multi-touch input module.
|
||||
*/
|
||||
typedef struct guac_rdp_rdpei {
|
||||
|
||||
/**
|
||||
* RDPEI control interface.
|
||||
*/
|
||||
RdpeiClientContext* rdpei;
|
||||
|
||||
/**
|
||||
* All currently-tracked touches.
|
||||
*/
|
||||
guac_rdp_rdpei_touch touch[GUAC_RDP_RDPEI_MAX_TOUCHES];
|
||||
|
||||
} guac_rdp_rdpei;
|
||||
|
||||
/**
|
||||
* Allocates a new RDPEI module, which will ultimately control the RDPEI
|
||||
* channel once connected. The RDPEI channel allows multi-touch input
|
||||
* events to be sent to the RDP server.
|
||||
*
|
||||
* @return
|
||||
* A newly-allocated RDPEI module.
|
||||
*/
|
||||
guac_rdp_rdpei* guac_rdp_rdpei_alloc();
|
||||
|
||||
/**
|
||||
* Frees the resources associated with support for the RDPEI channel. Only
|
||||
* resources specific to Guacamole are freed. Resources specific to FreeRDP's
|
||||
* handling of the RDPEI channel will be freed by FreeRDP. If no resources are
|
||||
* currently allocated for RDPEI, this function has no effect.
|
||||
*
|
||||
* @param rdpei
|
||||
* The RDPEI module to free.
|
||||
*/
|
||||
void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei);
|
||||
|
||||
/**
|
||||
* Adds FreeRDP's "rdpei" plugin to the list of dynamic virtual channel plugins
|
||||
* to be loaded by FreeRDP's "drdynvc" plugin. The context of the plugin will
|
||||
* automatically be assicated with the guac_rdp_rdpei instance pointed to by the
|
||||
* current guac_rdp_client. The plugin will only be loaded once the "drdynvc"
|
||||
* plugin is loaded. The "rdpei" plugin ultimately adds support for multi-touch
|
||||
* input via the RDPEI channel.
|
||||
*
|
||||
* If failures occur, messages noting the specifics of those failures will be
|
||||
* logged, and the RDP side of multi-touch support will not be functional.
|
||||
*
|
||||
* This MUST be called within the PreConnect callback of the freerdp instance
|
||||
* for multi-touch support to be loaded.
|
||||
*
|
||||
* @param context
|
||||
* The rdpContext associated with the active RDP session.
|
||||
*/
|
||||
void guac_rdp_rdpei_load_plugin(rdpContext* context);
|
||||
|
||||
/**
|
||||
* Reports to the RDP server that the status of a single touch contact has
|
||||
* changed. Depending on the amount of force associated with the touch and
|
||||
* whether the touch has been encountered before, this will result a new touch
|
||||
* contact, updates to an existing contact, or removal of an existing contact.
|
||||
* If the RDPEI channel has not yet been connected, touches will be ignored and
|
||||
* dropped until it is connected.
|
||||
*
|
||||
* @param rdpei
|
||||
* The RDPEI module associated with the RDP session.
|
||||
*
|
||||
* @param id
|
||||
* An arbitrary integer ID unique to the touch being updated.
|
||||
*
|
||||
* @param x
|
||||
* The X-coordinate of the touch, in pixels.
|
||||
*
|
||||
* @param y
|
||||
* The Y-coordinate of the touch, in pixels.
|
||||
*
|
||||
* @param force
|
||||
* The amount of force currently being exerted on the device by the touch
|
||||
* contact in question, where 1.0 is the maximum amount of force
|
||||
* representable and 0.0 indicates the contact has been lifted.
|
||||
*
|
||||
* @return
|
||||
* Zero if the touch event was successfully processed, non-zero if the
|
||||
* touch event had to be dropped.
|
||||
*/
|
||||
int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y,
|
||||
double force);
|
||||
|
||||
#endif
|
||||
|
@ -147,6 +147,9 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
|
||||
/* Init display update module */
|
||||
rdp_client->disp = guac_rdp_disp_alloc();
|
||||
|
||||
/* Init multi-touch support module (RDPEI) */
|
||||
rdp_client->rdpei = guac_rdp_rdpei_alloc();
|
||||
|
||||
/* Redirect FreeRDP log messages to guac_client_log() */
|
||||
guac_rdp_redirect_wlog(client);
|
||||
|
||||
@ -187,6 +190,9 @@ int guac_rdp_client_free_handler(guac_client* client) {
|
||||
/* Free display update module */
|
||||
guac_rdp_disp_free(rdp_client->disp);
|
||||
|
||||
/* Free multi-touch support module (RDPEI) */
|
||||
guac_rdp_rdpei_free(rdp_client->rdpei);
|
||||
|
||||
/* Clean up filesystem, if allocated */
|
||||
if (rdp_client->filesystem != NULL)
|
||||
guac_rdp_fs_free(rdp_client->filesystem);
|
||||
|
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "channels/disp.h"
|
||||
#include "channels/rdpei.h"
|
||||
#include "common/cursor.h"
|
||||
#include "common/display.h"
|
||||
#include "common/recording.h"
|
||||
@ -122,6 +123,28 @@ complete:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int guac_rdp_user_touch_handler(guac_user* user, int id, int x, int y,
|
||||
int x_radius, int y_radius, double angle, double force) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
|
||||
|
||||
pthread_rwlock_rdlock(&(rdp_client->lock));
|
||||
|
||||
/* Skip if not yet connected */
|
||||
freerdp* rdp_inst = rdp_client->rdp_inst;
|
||||
if (rdp_inst == NULL)
|
||||
goto complete;
|
||||
|
||||
/* Forward touch event along RDPEI channel */
|
||||
guac_rdp_rdpei_touch_update(rdp_client->rdpei, id, x, y, force);
|
||||
|
||||
complete:
|
||||
pthread_rwlock_unlock(&(rdp_client->lock));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) {
|
||||
|
||||
guac_client* client = user->client;
|
||||
|
@ -27,6 +27,11 @@
|
||||
*/
|
||||
guac_user_mouse_handler guac_rdp_user_mouse_handler;
|
||||
|
||||
/**
|
||||
* Handler for Guacamole user touch events.
|
||||
*/
|
||||
guac_user_touch_handler guac_rdp_user_touch_handler;
|
||||
|
||||
/**
|
||||
* Handler for Guacamole user key events.
|
||||
*/
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "channels/pipe-svc.h"
|
||||
#include "channels/rail.h"
|
||||
#include "channels/rdpdr/rdpdr.h"
|
||||
#include "channels/rdpei.h"
|
||||
#include "channels/rdpsnd/rdpsnd.h"
|
||||
#include "client.h"
|
||||
#include "color.h"
|
||||
@ -100,6 +101,10 @@ BOOL rdp_freerdp_pre_connect(freerdp* instance) {
|
||||
if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE)
|
||||
guac_rdp_disp_load_plugin(context);
|
||||
|
||||
/* Load "rdpei" plugin for multi-touch support */
|
||||
if (settings->enable_touch)
|
||||
guac_rdp_rdpei_load_plugin(context);
|
||||
|
||||
/* Load "AUDIO_INPUT" plugin for audio input*/
|
||||
if (settings->enable_audio_input) {
|
||||
rdp_client->audio_input = guac_rdp_audio_buffer_alloc();
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "channels/audio-input/audio-buffer.h"
|
||||
#include "channels/cliprdr.h"
|
||||
#include "channels/disp.h"
|
||||
#include "channels/rdpei.h"
|
||||
#include "common/clipboard.h"
|
||||
#include "common/display.h"
|
||||
#include "common/list.h"
|
||||
@ -148,6 +149,11 @@ typedef struct guac_rdp_client {
|
||||
*/
|
||||
guac_rdp_disp* disp;
|
||||
|
||||
/**
|
||||
* Multi-touch support module (RDPEI).
|
||||
*/
|
||||
guac_rdp_rdpei* rdpei;
|
||||
|
||||
/**
|
||||
* List of all available static virtual channels.
|
||||
*/
|
||||
|
@ -108,6 +108,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = {
|
||||
"create-recording-path",
|
||||
"resize-method",
|
||||
"enable-audio-input",
|
||||
"enable-touch",
|
||||
"read-only",
|
||||
|
||||
"gateway-hostname",
|
||||
@ -527,6 +528,12 @@ enum RDP_ARGS_IDX {
|
||||
*/
|
||||
IDX_ENABLE_AUDIO_INPUT,
|
||||
|
||||
/**
|
||||
* "true" if multi-touch support should be enabled for the RDP connection,
|
||||
* "false" or blank otherwise.
|
||||
*/
|
||||
IDX_ENABLE_TOUCH,
|
||||
|
||||
/**
|
||||
* "true" if this connection should be read-only (user input should be
|
||||
* dropped), "false" or blank otherwise.
|
||||
@ -1070,6 +1077,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user,
|
||||
settings->resize_method = GUAC_RESIZE_NONE;
|
||||
}
|
||||
|
||||
/* Multi-touch input enable/disable */
|
||||
settings->enable_touch =
|
||||
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||
IDX_ENABLE_TOUCH, 0);
|
||||
|
||||
/* Audio input enable/disable */
|
||||
settings->enable_audio_input =
|
||||
guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv,
|
||||
|
@ -525,6 +525,11 @@ typedef struct guac_rdp_settings {
|
||||
*/
|
||||
int enable_audio_input;
|
||||
|
||||
/**
|
||||
* Whether multi-touch support is enabled.
|
||||
*/
|
||||
int enable_touch;
|
||||
|
||||
/**
|
||||
* The hostname of the remote desktop gateway that should be used as an
|
||||
* intermediary for the remote desktop connection. If no gateway should
|
||||
|
@ -105,6 +105,10 @@ int guac_rdp_user_join_handler(guac_user* user, int argc, char** argv) {
|
||||
user->mouse_handler = guac_rdp_user_mouse_handler;
|
||||
user->key_handler = guac_rdp_user_key_handler;
|
||||
|
||||
/* Multi-touch events */
|
||||
if (settings->enable_touch)
|
||||
user->touch_handler = guac_rdp_user_touch_handler;
|
||||
|
||||
/* Inbound (client to server) clipboard transfer */
|
||||
if (!settings->disable_paste)
|
||||
user->clipboard_handler = guac_rdp_clipboard_handler;
|
||||
|
Loading…
Reference in New Issue
Block a user