From 74d319b797a941092a9f626aeb93bd0bad61fec4 Mon Sep 17 00:00:00 2001 From: Virtually Nick Date: Mon, 1 Jan 2024 16:39:01 -0500 Subject: [PATCH] GUACAMOLE-1231: Implement basic support for restoring minimized RAIL windows. --- src/protocols/rdp/channels/rail.c | 87 ++++++++++++++++++++++++------- src/protocols/rdp/channels/rail.h | 27 ++++++++++ src/protocols/rdp/client.c | 1 + src/protocols/rdp/rdp.h | 7 +++ 4 files changed, 104 insertions(+), 18 deletions(-) diff --git a/src/protocols/rdp/channels/rail.c b/src/protocols/rdp/channels/rail.c index b193c98a8a..2fbac78a97 100644 --- a/src/protocols/rdp/channels/rail.c +++ b/src/protocols/rdp/channels/rail.c @@ -26,27 +26,15 @@ #include #include #include +#include #include +#include #include #include #include #include -#ifdef FREERDP_RAIL_CALLBACKS_REQUIRE_CONST -/** - * FreeRDP 2.0.0-rc4 and newer requires the final argument for all RAIL - * callbacks to be const. - */ -#define RAIL_CONST const -#else -/** - * FreeRDP 2.0.0-rc3 and older requires the final argument for all RAIL - * callbacks to NOT be const. - */ -#define RAIL_CONST -#endif - /** * Completes initialization of the RemoteApp session, responding to the server * handshake, sending client status and system parameters, and executing the @@ -86,6 +74,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) { }; /* Send client handshake response */ + guac_client_log(client, GUAC_LOG_TRACE, "Sending RAIL handshake."); pthread_mutex_lock(&(rdp_client->message_lock)); status = rail->ClientHandshake(rail, &handshake); pthread_mutex_unlock(&(rdp_client->message_lock)); @@ -94,10 +83,13 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) { return status; RAIL_CLIENT_STATUS_ORDER client_status = { - .flags = 0x00 + .flags = + TS_RAIL_CLIENTSTATUS_ALLOWLOCALMOVESIZE + | TS_RAIL_CLIENTSTATUS_APPBAR_REMOTING_SUPPORTED }; /* Send client status */ + guac_client_log(client, GUAC_LOG_TRACE, "Sending RAIL client status."); pthread_mutex_lock(&(rdp_client->message_lock)); status = rail->ClientInformation(rail, &client_status); pthread_mutex_unlock(&(rdp_client->message_lock)); @@ -135,8 +127,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) { }, .params = - SPI_MASK_SET_DRAG_FULL_WINDOWS - | SPI_MASK_SET_HIGH_CONTRAST + SPI_MASK_SET_HIGH_CONTRAST | SPI_MASK_SET_KEYBOARD_CUES | SPI_MASK_SET_KEYBOARD_PREF | SPI_MASK_SET_MOUSE_BUTTON_SWAP @@ -145,6 +136,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) { }; /* Send client system parameters */ + guac_client_log(client, GUAC_LOG_TRACE, "Sending RAIL client system parameters."); pthread_mutex_lock(&(rdp_client->message_lock)); status = rail->ClientSystemParam(rail, &sysparam); pthread_mutex_unlock(&(rdp_client->message_lock)); @@ -160,6 +152,7 @@ static UINT guac_rdp_rail_complete_handshake(RailClientContext* rail) { }; /* Execute desired RemoteApp command */ + guac_client_log(client, GUAC_LOG_TRACE, "Executing remote application."); pthread_mutex_lock(&(rdp_client->message_lock)); status = rail->ClientExecute(rail, &exec); pthread_mutex_unlock(&(rdp_client->message_lock)); @@ -220,6 +213,8 @@ static UINT guac_rdp_rail_execute_result(RailClientContext* context, */ static UINT guac_rdp_rail_handshake(RailClientContext* rail, RAIL_CONST RAIL_HANDSHAKE_ORDER* handshake) { + guac_client* client = (guac_client*) rail->custom; + guac_client_log(client, GUAC_LOG_TRACE, "RAIL handshake callback."); return guac_rdp_rail_complete_handshake(rail); } @@ -244,9 +239,63 @@ static UINT guac_rdp_rail_handshake(RailClientContext* rail, */ static UINT guac_rdp_rail_handshake_ex(RailClientContext* rail, RAIL_CONST RAIL_HANDSHAKE_EX_ORDER* handshake_ex) { + guac_client* client = (guac_client*) rail->custom; + guac_client_log(client, GUAC_LOG_TRACE, "RAIL handshake ex callback."); return guac_rdp_rail_complete_handshake(rail); } +/** + * A callback function that is executed when an update for a RAIL window is + * received from the RDP server. + * + * @param context + * A pointer to the rdpContext structure used by FreeRDP to handle the + * window update. + * + * @param orderInfo + * A pointer to the data structure that contains information about what + * window was updated what updates were performed. + * + * @param windowState + * A pointer to the data structure that contains details of the updates + * to the window, as indicated by flags in the orderInfo field. + * + * @return + * TRUE if the client-side processing of the updates as successful; otherwise + * FALSE. This implementation always returns TRUE. + */ +static BOOL guac_rdp_rail_window_update(rdpContext* context, + RAIL_CONST WINDOW_ORDER_INFO* orderInfo, + RAIL_CONST WINDOW_STATE_ORDER* windowState) { + + guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + guac_client_log(client, GUAC_LOG_TRACE, "RAIL window update callback: %d", orderInfo->fieldFlags); + + UINT32 fieldFlags = orderInfo->fieldFlags; + + /* If the flag for window visibilty is set, check visibility. */ + if (fieldFlags & WINDOW_ORDER_FIELD_SHOW) { + guac_client_log(client, GUAC_LOG_TRACE, "RAIL window visibility change: %d", windowState->showState); + + /* State is either hidden or minimized - send restore command. */ + if (windowState->showState == GUAC_RDP_RAIL_WINDOW_STATE_HIDDEN + || windowState->showState == GUAC_RDP_RAIL_WINDOW_STATE_MINIMIZED) { + + guac_client_log(client, GUAC_LOG_DEBUG, "RAIL window minimized, sending restore command."); + + RAIL_SYSCOMMAND_ORDER syscommand; + syscommand.windowId = orderInfo->windowId; + syscommand.command = SC_RESTORE; + rdp_client->rail_interface->ClientSystemCommand(rdp_client->rail_interface, &syscommand); + } + } + + return true; + +} + /** * Callback which associates handlers specific to Guacamole with the * RailClientContext instance allocated by FreeRDP to deal with received @@ -269,6 +318,7 @@ static void guac_rdp_rail_channel_connected(rdpContext* context, ChannelConnectedEventArgs* args) { guac_client* client = ((rdp_freerdp_context*) context)->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; /* Ignore connection event if it's not for the RAIL channel */ if (strcmp(args->name, RAIL_SVC_CHANNEL_NAME) != 0) @@ -277,6 +327,7 @@ static void guac_rdp_rail_channel_connected(rdpContext* context, /* The structure pointed to by pInterface is guaranteed to be a * RailClientContext if the channel is RAIL */ RailClientContext* rail = (RailClientContext*) args->pInterface; + rdp_client->rail_interface = rail; /* Init FreeRDP RAIL context, ensuring the guac_client can be accessed from * within any RAIL-specific callbacks */ @@ -284,6 +335,7 @@ static void guac_rdp_rail_channel_connected(rdpContext* context, rail->ServerExecuteResult = guac_rdp_rail_execute_result; rail->ServerHandshake = guac_rdp_rail_handshake; rail->ServerHandshakeEx = guac_rdp_rail_handshake_ex; + context->update->window->WindowUpdate = guac_rdp_rail_window_update; guac_client_log(client, GUAC_LOG_DEBUG, "RAIL (RemoteApp) channel " "connected."); @@ -312,4 +364,3 @@ void guac_rdp_rail_load_plugin(rdpContext* context) { "registered. Awaiting channel connection."); } - diff --git a/src/protocols/rdp/channels/rail.h b/src/protocols/rdp/channels/rail.h index 0856034363..0842871387 100644 --- a/src/protocols/rdp/channels/rail.h +++ b/src/protocols/rdp/channels/rail.h @@ -20,7 +20,34 @@ #ifndef GUAC_RDP_CHANNELS_RAIL_H #define GUAC_RDP_CHANNELS_RAIL_H +#include "config.h" + #include +#include + +#ifdef FREERDP_RAIL_CALLBACKS_REQUIRE_CONST +/** + * FreeRDP 2.0.0-rc4 and newer requires the final arguments for RAIL + * callbacks to be const. + */ +#define RAIL_CONST const +#else +/** + * FreeRDP 2.0.0-rc3 and older requires the final arguments for RAIL + * callbacks to NOT be const. + */ +#define RAIL_CONST +#endif + +/** + * The RAIL window state that indicates a hidden window. + */ +#define GUAC_RDP_RAIL_WINDOW_STATE_HIDDEN 0x00 + +/** + * The RAIL window state that indicates a visible but minimized window. + */ +#define GUAC_RDP_RAIL_WINDOW_STATE_MINIMIZED 0x02 /** * Initializes RemoteApp support for RDP and handling of the RAIL channel. If diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 6412e5224f..4d9ba8f6c2 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -22,6 +22,7 @@ #include "channels/cliprdr.h" #include "channels/disp.h" #include "channels/pipe-svc.h" +#include "channels/rail.h" #include "config.h" #include "fs.h" #include "log.h" diff --git a/src/protocols/rdp/rdp.h b/src/protocols/rdp/rdp.h index 70668f4202..b3680e5827 100644 --- a/src/protocols/rdp/rdp.h +++ b/src/protocols/rdp/rdp.h @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -197,6 +198,12 @@ typedef struct guac_rdp_client { */ pthread_mutex_t message_lock; + /** + * A pointer to the RAIL interface provided by the RDP client when rail is + * in use. + */ + RailClientContext* rail_interface; + } guac_rdp_client; /**