Michael Jumper 2524af80a9 GUACAMOLE-1388: Ensure RDP-specific resources are cleaned up after channel disconnect.
Without these changes, RDP-specific resources like the CLIPRDR and RDPEI
channels may remain from past connections if the RDP connection is
dynamically reconnected via the "Reconnect" display resize method,
resulting in assertion failures or memory errors if those stale
resources are reused after reconnect is completed.
2021-07-28 15:50:18 -07:00

225 lines
7.2 KiB
C

/*
* 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_client* client) {
guac_rdp_rdpei* rdpei = malloc(sizeof(guac_rdp_rdpei));
rdpei->client = client;
/* 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.");
}
/**
* Callback which disassociates Guacamole from the RdpeiClientContext instance
* that was originally allocated by FreeRDP and is about to be deallocated.
*
* This function is called whenever a channel disconnects via the PubSub event
* system within FreeRDP, but only has any effect if the disconnected 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_disconnected(rdpContext* context,
ChannelDisconnectedEventArgs* 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 disconnection event if it's not for the RDPEI channel */
if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) != 0)
return;
/* Channel is no longer connected */
guac_rdpei->rdpei = NULL;
guac_client_log(client, GUAC_LOG_DEBUG, "RDPDI channel disconnected.");
}
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);
/* Subscribe to and handle channel disconnected events */
PubSub_SubscribeChannelDisconnected(context->pubSub,
(pChannelDisconnectedEventHandler) guac_rdp_rdpei_channel_disconnected);
/* 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) {
guac_client* client = rdpei->client;
guac_rdp_client* rdp_client = (guac_rdp_client*) client->data;
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;
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchEnd(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 0;
}
/* Signal the start of a touch if this is the first we've seen it */
else if (!touch->active) {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchBegin(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
touch->active = 1;
}
/* Established touches need only be updated */
else {
pthread_mutex_lock(&(rdp_client->message_lock));
context->TouchUpdate(context, id, x, y, &contact_id);
pthread_mutex_unlock(&(rdp_client->message_lock));
}
return 0;
}