rdpei.c (7461B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "channels/rdpei.h" 21#include "common/surface.h" 22#include "plugins/channels.h" 23#include "rdp.h" 24#include "settings.h" 25 26#include <freerdp/client/rdpei.h> 27#include <freerdp/freerdp.h> 28#include <freerdp/event.h> 29#include <guacamole/client.h> 30#include <guacamole/mem.h> 31#include <guacamole/timestamp.h> 32 33#include <stdlib.h> 34#include <string.h> 35 36guac_rdp_rdpei* guac_rdp_rdpei_alloc(guac_client* client) { 37 38 guac_rdp_rdpei* rdpei = guac_mem_alloc(sizeof(guac_rdp_rdpei)); 39 rdpei->client = client; 40 41 /* Not yet connected */ 42 rdpei->rdpei = NULL; 43 44 /* No active touches */ 45 for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) 46 rdpei->touch[i].active = 0; 47 48 return rdpei; 49 50} 51 52void guac_rdp_rdpei_free(guac_rdp_rdpei* rdpei) { 53 guac_mem_free(rdpei); 54} 55 56/** 57 * Callback which associates handlers specific to Guacamole with the 58 * RdpeiClientContext instance allocated by FreeRDP to deal with received 59 * RDPEI (multi-touch input) messages. 60 * 61 * This function is called whenever a channel connects via the PubSub event 62 * system within FreeRDP, but only has any effect if the connected channel is 63 * the RDPEI channel. This specific callback is registered with the 64 * PubSub system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is 65 * called. 66 * 67 * @param context 68 * The rdpContext associated with the active RDP session. 69 * 70 * @param e 71 * Event-specific arguments, mainly the name of the channel, and a 72 * reference to the associated plugin loaded for that channel by FreeRDP. 73 */ 74static void guac_rdp_rdpei_channel_connected(rdpContext* context, 75 ChannelConnectedEventArgs* e) { 76 77 guac_client* client = ((rdp_freerdp_context*) context)->client; 78 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 79 guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei; 80 81 /* Ignore connection event if it's not for the RDPEI channel */ 82 if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) != 0) 83 return; 84 85 /* Store reference to the RDPEI plugin once it's connected */ 86 RdpeiClientContext* rdpei = (RdpeiClientContext*) e->pInterface; 87 guac_rdpei->rdpei = rdpei; 88 89 /* Declare level of multi-touch support */ 90 guac_common_surface_set_multitouch(rdp_client->display->default_surface, 91 GUAC_RDP_RDPEI_MAX_TOUCHES); 92 93 guac_client_log(client, GUAC_LOG_DEBUG, "RDPEI channel will be used for " 94 "multi-touch support."); 95 96} 97 98/** 99 * Callback which disassociates Guacamole from the RdpeiClientContext instance 100 * that was originally allocated by FreeRDP and is about to be deallocated. 101 * 102 * This function is called whenever a channel disconnects via the PubSub event 103 * system within FreeRDP, but only has any effect if the disconnected channel 104 * is the RDPEI channel. This specific callback is registered with the PubSub 105 * system of the relevant rdpContext when guac_rdp_rdpei_load_plugin() is 106 * called. 107 * 108 * @param context 109 * The rdpContext associated with the active RDP session. 110 * 111 * @param e 112 * Event-specific arguments, mainly the name of the channel, and a 113 * reference to the associated plugin loaded for that channel by FreeRDP. 114 */ 115static void guac_rdp_rdpei_channel_disconnected(rdpContext* context, 116 ChannelDisconnectedEventArgs* e) { 117 118 guac_client* client = ((rdp_freerdp_context*) context)->client; 119 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 120 guac_rdp_rdpei* guac_rdpei = rdp_client->rdpei; 121 122 /* Ignore disconnection event if it's not for the RDPEI channel */ 123 if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) != 0) 124 return; 125 126 /* Channel is no longer connected */ 127 guac_rdpei->rdpei = NULL; 128 129 guac_client_log(client, GUAC_LOG_DEBUG, "RDPDI channel disconnected."); 130 131} 132 133void guac_rdp_rdpei_load_plugin(rdpContext* context) { 134 135 /* Subscribe to and handle channel connected events */ 136 PubSub_SubscribeChannelConnected(context->pubSub, 137 (pChannelConnectedEventHandler) guac_rdp_rdpei_channel_connected); 138 139 /* Subscribe to and handle channel disconnected events */ 140 PubSub_SubscribeChannelDisconnected(context->pubSub, 141 (pChannelDisconnectedEventHandler) guac_rdp_rdpei_channel_disconnected); 142 143 /* Add "rdpei" channel */ 144 guac_freerdp_dynamic_channel_collection_add(context->settings, "rdpei", NULL); 145 146} 147 148int guac_rdp_rdpei_touch_update(guac_rdp_rdpei* rdpei, int id, int x, int y, 149 double force) { 150 151 guac_client* client = rdpei->client; 152 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 153 154 int contact_id; /* Ignored */ 155 156 /* Track touches only if channel is connected */ 157 RdpeiClientContext* context = rdpei->rdpei; 158 if (context == NULL) 159 return 1; 160 161 /* Locate active touch having provided ID */ 162 guac_rdp_rdpei_touch* touch = NULL; 163 for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) { 164 if (rdpei->touch[i].active && rdpei->touch[i].id == id) { 165 touch = &rdpei->touch[i]; 166 break; 167 } 168 } 169 170 /* If no such touch exists, add it */ 171 if (touch == NULL) { 172 for (int i = 0; i < GUAC_RDP_RDPEI_MAX_TOUCHES; i++) { 173 if (!rdpei->touch[i].active) { 174 touch = &rdpei->touch[i]; 175 touch->id = id; 176 break; 177 } 178 } 179 } 180 181 /* If the touch couldn't be added, we're already at maximum touch capacity. 182 * Drop the event. */ 183 if (touch == NULL) 184 return 1; 185 186 /* Signal the end of an established touch if touch force has become zero 187 * (this should be a safe comparison, as zero has an exact representation 188 * in floating point, and the client side will use an exact value to 189 * represent the absence of a touch) */ 190 if (force == 0.0) { 191 192 /* Ignore release of touches that we aren't tracking */ 193 if (!touch->active) 194 return 1; 195 196 pthread_mutex_lock(&(rdp_client->message_lock)); 197 context->TouchEnd(context, id, x, y, &contact_id); 198 pthread_mutex_unlock(&(rdp_client->message_lock)); 199 200 touch->active = 0; 201 202 } 203 204 /* Signal the start of a touch if this is the first we've seen it */ 205 else if (!touch->active) { 206 207 pthread_mutex_lock(&(rdp_client->message_lock)); 208 context->TouchBegin(context, id, x, y, &contact_id); 209 pthread_mutex_unlock(&(rdp_client->message_lock)); 210 211 touch->active = 1; 212 213 } 214 215 /* Established touches need only be updated */ 216 else { 217 pthread_mutex_lock(&(rdp_client->message_lock)); 218 context->TouchUpdate(context, id, x, y, &contact_id); 219 pthread_mutex_unlock(&(rdp_client->message_lock)); 220 } 221 222 return 0; 223 224} 225