cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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