disp.c (9665B)
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/disp.h" 21#include "plugins/channels.h" 22#include "fs.h" 23#include "rdp.h" 24#include "settings.h" 25 26#include <freerdp/client/disp.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_disp* guac_rdp_disp_alloc(guac_client* client) { 37 38 guac_rdp_disp* disp = guac_mem_alloc(sizeof(guac_rdp_disp)); 39 disp->client = client; 40 41 /* Not yet connected */ 42 disp->disp = NULL; 43 44 /* No requests have been made */ 45 disp->last_request = guac_timestamp_current(); 46 disp->requested_width = 0; 47 disp->requested_height = 0; 48 disp->reconnect_needed = 0; 49 50 return disp; 51 52} 53 54void guac_rdp_disp_free(guac_rdp_disp* disp) { 55 guac_mem_free(disp); 56} 57 58/** 59 * Callback which associates handlers specific to Guacamole with the 60 * DispClientContext instance allocated by FreeRDP to deal with received 61 * Display Update (client-initiated dynamic display resizing) messages. 62 * 63 * This function is called whenever a channel connects via the PubSub event 64 * system within FreeRDP, but only has any effect if the connected channel is 65 * the Display Update channel. This specific callback is registered with the 66 * PubSub system of the relevant rdpContext when guac_rdp_disp_load_plugin() is 67 * called. 68 * 69 * @param context 70 * The rdpContext associated with the active RDP session. 71 * 72 * @param e 73 * Event-specific arguments, mainly the name of the channel, and a 74 * reference to the associated plugin loaded for that channel by FreeRDP. 75 */ 76static void guac_rdp_disp_channel_connected(rdpContext* context, 77 ChannelConnectedEventArgs* e) { 78 79 guac_client* client = ((rdp_freerdp_context*) context)->client; 80 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 81 guac_rdp_disp* guac_disp = rdp_client->disp; 82 83 /* Ignore connection event if it's not for the Display Update channel */ 84 if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) != 0) 85 return; 86 87 /* Init module with current display size */ 88 guac_rdp_disp_set_size(guac_disp, rdp_client->settings, 89 context->instance, guac_rdp_get_width(context->instance), 90 guac_rdp_get_height(context->instance)); 91 92 /* Store reference to the display update plugin once it's connected */ 93 DispClientContext* disp = (DispClientContext*) e->pInterface; 94 guac_disp->disp = disp; 95 96 guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel " 97 "will be used for display size changes."); 98 99} 100 101/** 102 * Callback which disassociates Guacamole from the DispClientContext instance 103 * that was originally allocated by FreeRDP and is about to be deallocated. 104 * 105 * This function is called whenever a channel disconnects via the PubSub event 106 * system within FreeRDP, but only has any effect if the disconnected channel 107 * is the Display Update channel. This specific callback is registered with the 108 * PubSub system of the relevant rdpContext when guac_rdp_disp_load_plugin() is 109 * called. 110 * 111 * @param context 112 * The rdpContext associated with the active RDP session. 113 * 114 * @param e 115 * Event-specific arguments, mainly the name of the channel, and a 116 * reference to the associated plugin loaded for that channel by FreeRDP. 117 */ 118static void guac_rdp_disp_channel_disconnected(rdpContext* context, 119 ChannelDisconnectedEventArgs* e) { 120 121 guac_client* client = ((rdp_freerdp_context*) context)->client; 122 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 123 guac_rdp_disp* guac_disp = rdp_client->disp; 124 125 /* Ignore disconnection event if it's not for the Display Update channel */ 126 if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) != 0) 127 return; 128 129 /* Channel is no longer connected */ 130 guac_disp->disp = NULL; 131 132 guac_client_log(client, GUAC_LOG_DEBUG, "Display update channel " 133 "disconnected."); 134 135} 136 137void guac_rdp_disp_load_plugin(rdpContext* context) { 138 139 /* Subscribe to and handle channel connected events */ 140 PubSub_SubscribeChannelConnected(context->pubSub, 141 (pChannelConnectedEventHandler) guac_rdp_disp_channel_connected); 142 143 /* Subscribe to and handle channel disconnected events */ 144 PubSub_SubscribeChannelDisconnected(context->pubSub, 145 (pChannelDisconnectedEventHandler) guac_rdp_disp_channel_disconnected); 146 147 /* Add "disp" channel */ 148 guac_freerdp_dynamic_channel_collection_add(context->settings, "disp", NULL); 149 150} 151 152/** 153 * Fits a given dimension within the allowed bounds for Display Update 154 * messages, adjusting the other dimension such that aspect ratio is 155 * maintained. 156 * 157 * @param a The dimension to fit within allowed bounds. 158 * 159 * @param b 160 * The other dimension to adjust if and only if necessary to preserve 161 * aspect ratio. 162 */ 163static void guac_rdp_disp_fit(int* a, int* b) { 164 165 int a_value = *a; 166 int b_value = *b; 167 168 /* Ensure first dimension is within allowed range */ 169 if (a_value < GUAC_RDP_DISP_MIN_SIZE) { 170 171 /* Adjust other dimension to maintain aspect ratio */ 172 int adjusted_b = b_value * GUAC_RDP_DISP_MIN_SIZE / a_value; 173 if (adjusted_b > GUAC_RDP_DISP_MAX_SIZE) 174 adjusted_b = GUAC_RDP_DISP_MAX_SIZE; 175 176 *a = GUAC_RDP_DISP_MIN_SIZE; 177 *b = adjusted_b; 178 179 } 180 else if (a_value > GUAC_RDP_DISP_MAX_SIZE) { 181 182 /* Adjust other dimension to maintain aspect ratio */ 183 int adjusted_b = b_value * GUAC_RDP_DISP_MAX_SIZE / a_value; 184 if (adjusted_b < GUAC_RDP_DISP_MIN_SIZE) 185 adjusted_b = GUAC_RDP_DISP_MIN_SIZE; 186 187 *a = GUAC_RDP_DISP_MAX_SIZE; 188 *b = adjusted_b; 189 190 } 191 192} 193 194void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, 195 freerdp* rdp_inst, int width, int height) { 196 197 /* Fit width within bounds, adjusting height to maintain aspect ratio */ 198 guac_rdp_disp_fit(&width, &height); 199 200 /* Fit height within bounds, adjusting width to maintain aspect ratio */ 201 guac_rdp_disp_fit(&height, &width); 202 203 /* Width must be even */ 204 if (width % 2 == 1) 205 width -= 1; 206 207 /* Store deferred size */ 208 disp->requested_width = width; 209 disp->requested_height = height; 210 211 /* Send display update notification if possible */ 212 guac_rdp_disp_update_size(disp, settings, rdp_inst); 213 214} 215 216void guac_rdp_disp_update_size(guac_rdp_disp* disp, 217 guac_rdp_settings* settings, freerdp* rdp_inst) { 218 219 int width = disp->requested_width; 220 int height = disp->requested_height; 221 222 /* Do not update size if no requests have been received */ 223 if (width == 0 || height == 0) 224 return; 225 226 guac_timestamp now = guac_timestamp_current(); 227 228 /* Limit display update frequency */ 229 if (now - disp->last_request <= GUAC_RDP_DISP_UPDATE_INTERVAL) 230 return; 231 232 /* Do NOT send requests unless the size will change */ 233 if (rdp_inst != NULL 234 && width == guac_rdp_get_width(rdp_inst) 235 && height == guac_rdp_get_height(rdp_inst)) 236 return; 237 238 disp->last_request = now; 239 240 if (settings->resize_method == GUAC_RESIZE_RECONNECT) { 241 242 /* Update settings with new dimensions */ 243 settings->width = width; 244 settings->height = height; 245 246 /* Signal reconnect */ 247 disp->reconnect_needed = 1; 248 249 } 250 251 else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { 252 DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ 253 .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ 254 .Left = 0, 255 .Top = 0, 256 .Width = width, 257 .Height = height, 258 .PhysicalWidth = 0, 259 .PhysicalHeight = 0, 260 .Orientation = 0, 261 .DesktopScaleFactor = 0, 262 .DeviceScaleFactor = 0 263 }}; 264 265 /* Send display update notification if display channel is connected */ 266 if (disp->disp != NULL) { 267 268 guac_client* client = disp->client; 269 guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; 270 271 pthread_mutex_lock(&(rdp_client->message_lock)); 272 disp->disp->SendMonitorLayout(disp->disp, 1, monitors); 273 pthread_mutex_unlock(&(rdp_client->message_lock)); 274 275 } 276 277 } 278 279} 280 281int guac_rdp_disp_reconnect_needed(guac_rdp_disp* disp) { 282 guac_rdp_client* rdp_client = (guac_rdp_client*) disp->client->data; 283 284 /* Do not reconnect if files are open. */ 285 if (rdp_client->filesystem != NULL 286 && rdp_client->filesystem->open_files > 0) 287 return 0; 288 289 /* Do not reconnect if an active print job is present */ 290 if (rdp_client->active_job != NULL) 291 return 0; 292 293 294 return disp->reconnect_needed; 295} 296 297void guac_rdp_disp_reconnect_complete(guac_rdp_disp* disp) { 298 disp->reconnect_needed = 0; 299 disp->last_request = guac_timestamp_current(); 300} 301