diff --git a/src/libguac/guacamole/user-fntypes.h b/src/libguac/guacamole/user-fntypes.h index 646a3b007..b9de901a3 100644 --- a/src/libguac/guacamole/user-fntypes.h +++ b/src/libguac/guacamole/user-fntypes.h @@ -220,12 +220,15 @@ typedef int guac_user_clipboard_handler(guac_user* user, guac_stream* stream, * @param height * The desired height of the display, in pixels. * + * @param monitors + * The count of monitors. + * * @return * Zero if the size event has been successfully handled, non-zero * otherwise. */ typedef int guac_user_size_handler(guac_user* user, - int width, int height); + int width, int height, int monitors); /** * Handler for Guacamole file streams received from a user. Each such file diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 13fb146d4..4c041ab22 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -293,7 +293,7 @@ struct guac_user { * * Example: * @code - * int size_handler(guac_user* user, int width, int height); + * int size_handler(guac_user* user, int width, int height, int monitor); * * int guac_user_init(guac_user* user, int argc, char** argv) { * user->size_handler = size_handler; diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index b9013bc7c..450aa4892 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -372,7 +372,8 @@ int __guac_handle_size(guac_user* user, int argc, char** argv) { return user->size_handler( user, atoi(argv[0]), /* width */ - atoi(argv[1]) /* height */ + atoi(argv[1]), /* height */ + (argc == 3 ? atoi(argv[2]) : 1) /* Monitors count */ ); return 0; } diff --git a/src/protocols/kubernetes/input.c b/src/protocols/kubernetes/input.c index b61d55643..dd2ada6fe 100644 --- a/src/protocols/kubernetes/input.c +++ b/src/protocols/kubernetes/input.c @@ -70,7 +70,7 @@ int guac_kubernetes_user_key_handler(guac_user* user, int keysym, int pressed) { } -int guac_kubernetes_user_size_handler(guac_user* user, int width, int height) { +int guac_kubernetes_user_size_handler(guac_user* user, int width, int height, int monitors) { /* Get terminal */ guac_client* client = user->client; diff --git a/src/protocols/rdp/channels/disp.c b/src/protocols/rdp/channels/disp.c index da1ca800d..44be6198c 100644 --- a/src/protocols/rdp/channels/disp.c +++ b/src/protocols/rdp/channels/disp.c @@ -44,9 +44,10 @@ guac_rdp_disp* guac_rdp_disp_alloc(guac_client* client) { /* No requests have been made */ disp->last_request = guac_timestamp_current(); - disp->requested_width = 0; - disp->requested_height = 0; - disp->reconnect_needed = 0; + disp->requested_width = 0; + disp->requested_height = 0; + disp->reconnect_needed = 0; + disp->requested_monitors = 1; return disp; @@ -88,7 +89,7 @@ static void guac_rdp_disp_channel_connected(rdpContext* context, /* Init module with current display size */ guac_rdp_disp_set_size(guac_disp, rdp_client->settings, context->instance, guac_rdp_get_width(context->instance), - guac_rdp_get_height(context->instance)); + guac_rdp_get_height(context->instance), 1); /* Store reference to the display update plugin once it's connected */ DispClientContext* disp = (DispClientContext*) args->pInterface; @@ -151,7 +152,7 @@ void guac_rdp_disp_load_plugin(rdpContext* context) { } void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, - freerdp* rdp_inst, int width, int height) { + freerdp* rdp_inst, int width, int height, int monitors) { guac_rect resize = { .left = 0, @@ -180,6 +181,7 @@ void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, /* Store deferred size */ disp->requested_width = width; disp->requested_height = height; + disp->requested_monitors = monitors; /* Send display update notification if possible */ guac_rdp_disp_update_size(disp, settings, rdp_inst); @@ -191,6 +193,15 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, int width = disp->requested_width; int height = disp->requested_height; + int monitors_count = disp->requested_monitors; + + /* Prevent opening too many monitors than allowed */ + if (settings->max_secondary_monitors + 1 < monitors_count) + monitors_count = settings->max_secondary_monitors + 1; + + /* At least one monitor is required */ + if (monitors_count < 1) + monitors_count = 1; /* Do not update size if no requests have been received */ if (width == 0 || height == 0) @@ -204,7 +215,7 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, /* Do NOT send requests unless the size will change */ if (rdp_inst != NULL - && width == guac_rdp_get_width(rdp_inst) + && width * monitors_count == guac_rdp_get_width(rdp_inst) && height == guac_rdp_get_height(rdp_inst)) return; @@ -221,32 +232,46 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, } - else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { - DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ - .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ - .Left = 0, - .Top = 0, - .Width = width, - .Height = height, - .PhysicalWidth = 0, - .PhysicalHeight = 0, - .Orientation = 0, - .DesktopScaleFactor = 0, - .DeviceScaleFactor = 0 - }}; - - /* Send display update notification if display channel is connected */ - if (disp->disp != NULL) { - - guac_client* client = disp->client; - guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; - - pthread_mutex_lock(&(rdp_client->message_lock)); - disp->disp->SendMonitorLayout(disp->disp, 1, monitors); - pthread_mutex_unlock(&(rdp_client->message_lock)); + /* Send display update notification if display channel is connected */ + else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE + && disp->disp != NULL) { + + /* Init monitors layout */ + DISPLAY_CONTROL_MONITOR_LAYOUT* monitors; + monitors = guac_mem_alloc(monitors_count * sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT)); + + for (int i = 0; i < monitors_count; i++) { + + /* First monitor is the primary */ + int primary_monitor = (i == 0 ? 1 : 0); + /* Shift each monitor to the right */ + int monitor_left = i * width; + + /* Get current monitor */ + DISPLAY_CONTROL_MONITOR_LAYOUT* monitor = &monitors[i]; + + /* Set current monitor properties */ + monitor->Flags = primary_monitor; + monitor->Left = monitor_left; + monitor->Top = 0; + monitor->Width = width; + monitor->Height = height; + monitor->Orientation = 0; + monitor->PhysicalWidth = 0; + monitor->PhysicalHeight = 0; } + /* Send display update notification */ + guac_client* client = disp->client; + guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; + + pthread_mutex_lock(&(rdp_client->message_lock)); + disp->disp->SendMonitorLayout(disp->disp, monitors_count, monitors); + pthread_mutex_unlock(&(rdp_client->message_lock)); + + guac_mem_free(monitors); + } } diff --git a/src/protocols/rdp/channels/disp.h b/src/protocols/rdp/channels/disp.h index 54fe4ccfa..4aa997170 100644 --- a/src/protocols/rdp/channels/disp.h +++ b/src/protocols/rdp/channels/disp.h @@ -74,6 +74,11 @@ typedef struct guac_rdp_disp { */ int requested_height; + /** + * The number of monitors requested. + */ + int requested_monitors; + /** * Whether the size has changed and the RDP connection must be closed and * reestablished. @@ -154,9 +159,12 @@ void guac_rdp_disp_load_plugin(rdpContext* context); * The desired display height, in pixels. Due to the restrictions of the * RDP display update channel, this will be constrained to the range of 200 * through 8192 inclusive. + * + * @param monitors + * The count of monitors. */ void guac_rdp_disp_set_size(guac_rdp_disp* disp, guac_rdp_settings* settings, - freerdp* rdp_inst, int width, int height); + freerdp* rdp_inst, int width, int height, int monitors); /** * Sends an actual display update request to the RDP server based on previous diff --git a/src/protocols/rdp/client.c b/src/protocols/rdp/client.c index 263ab8d20..2daff84fe 100644 --- a/src/protocols/rdp/client.c +++ b/src/protocols/rdp/client.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -130,6 +131,16 @@ static int guac_rdp_join_pending_handler(guac_client* client) { /* Bring user up to date with any registered static channels */ guac_rdp_pipe_svc_send_pipes(client, broadcast_socket); + /* Get max secondary monitors */ + char* max_monitors = guac_mem_alloc(12); + guac_itoa(max_monitors, + (unsigned int) rdp_client->settings->max_secondary_monitors); + + /* Send current max allowed secondary monitors */ + guac_client_stream_argv(client, broadcast_socket, "text/plain", + "secondary-monitors", max_monitors); + guac_mem_free(max_monitors); + /* Synchronize with current display */ if (rdp_client->display != NULL) { guac_display_dup(rdp_client->display, broadcast_socket); diff --git a/src/protocols/rdp/input.c b/src/protocols/rdp/input.c index d8890571e..4fc08bae5 100644 --- a/src/protocols/rdp/input.c +++ b/src/protocols/rdp/input.c @@ -191,7 +191,7 @@ int guac_rdp_user_key_handler(guac_user* user, int keysym, int pressed) { } -int guac_rdp_user_size_handler(guac_user* user, int width, int height) { +int guac_rdp_user_size_handler(guac_user* user, int width, int height, int monitors) { guac_client* client = user->client; guac_rdp_client* rdp_client = (guac_rdp_client*) client->data; @@ -203,7 +203,7 @@ int guac_rdp_user_size_handler(guac_user* user, int width, int height) { height = height * settings->resolution / user->info.optimal_resolution; /* Send display update */ - guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height); + guac_rdp_disp_set_size(rdp_client->disp, settings, rdp_inst, width, height, monitors); return 0; diff --git a/src/protocols/rdp/rdp.c b/src/protocols/rdp/rdp.c index a6e7720d3..80c45d1f5 100644 --- a/src/protocols/rdp/rdp.c +++ b/src/protocols/rdp/rdp.c @@ -607,7 +607,6 @@ static int guac_rdp_handle_connection(guac_client* client) { guac_rdp_disp_update_size(rdp_client->disp, settings, rdp_inst); /* Wait for data and construct a reasonable frame */ - int wait_result = rdp_guac_client_wait_for_messages(client, GUAC_RDP_MESSAGE_CHECK_INTERVAL); if (wait_result < 0) break; diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index f47b1cff8..6a5586a03 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -128,6 +128,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "create-recording-path", "recording-write-existing", "resize-method", + "secondary-monitors", "enable-audio-input", "enable-touch", "read-only", @@ -598,6 +599,12 @@ enum RDP_ARGS_IDX { */ IDX_RESIZE_METHOD, + /** + * The maximum allowed count of secondary monitors. + * 0 to disable. + */ + IDX_SECONDARY_MONITORS, + /** * "true" if audio input (microphone) should be enabled for the RDP * connection, "false" or blank otherwise. @@ -1234,6 +1241,11 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->resize_method = GUAC_RESIZE_NONE; } + /* Maximum secondary monitors (default 0 = disabled) */ + settings->max_secondary_monitors = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_SECONDARY_MONITORS, 0); + /* RDP Graphics Pipeline enable/disable */ settings->enable_gfx = !guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, @@ -1729,6 +1741,7 @@ void guac_rdp_push_settings(guac_client* client, freerdp_settings_set_uint32(rdp_settings, FreeRDP_OsMajorType, OSMAJORTYPE_UNSPECIFIED); freerdp_settings_set_uint32(rdp_settings, FreeRDP_OsMinorType, OSMINORTYPE_UNSPECIFIED); freerdp_settings_set_bool(rdp_settings, FreeRDP_DesktopResize, TRUE); + freerdp_settings_set_bool(rdp_settings, FreeRDP_UseMultimon, TRUE); /* Claim support only for specific updates, independent of FreeRDP defaults */ BYTE* order_support = freerdp_settings_get_pointer_writable(rdp_settings, FreeRDP_OrderSupport); @@ -1975,6 +1988,7 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->OsMajorType = OSMAJORTYPE_UNSPECIFIED; rdp_settings->OsMinorType = OSMINORTYPE_UNSPECIFIED; rdp_settings->DesktopResize = TRUE; + rdp_settings->UseMultimon = TRUE; /* Claim support only for specific updates, independent of FreeRDP defaults */ ZeroMemory(rdp_settings->OrderSupport, GUAC_RDP_ORDER_SUPPORT_LENGTH); diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 253b7628b..b337e7b44 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -596,6 +596,11 @@ typedef struct guac_rdp_settings { */ guac_rdp_resize_method resize_method; + /** + * The maximum allowed count of secondary monitors. + */ + int max_secondary_monitors; + /** * Whether audio input (microphone) is enabled. */ diff --git a/src/protocols/ssh/input.c b/src/protocols/ssh/input.c index f60e1c9fa..07ab380a1 100644 --- a/src/protocols/ssh/input.c +++ b/src/protocols/ssh/input.c @@ -68,7 +68,7 @@ int guac_ssh_user_key_handler(guac_user* user, int keysym, int pressed) { return 0; } -int guac_ssh_user_size_handler(guac_user* user, int width, int height) { +int guac_ssh_user_size_handler(guac_user* user, int width, int height, int monitors) { /* Get terminal */ guac_client* client = user->client; diff --git a/src/protocols/telnet/input.c b/src/protocols/telnet/input.c index 849d1897b..e79108363 100644 --- a/src/protocols/telnet/input.c +++ b/src/protocols/telnet/input.c @@ -120,7 +120,7 @@ int guac_telnet_user_key_handler(guac_user* user, int keysym, int pressed) { } -int guac_telnet_user_size_handler(guac_user* user, int width, int height) { +int guac_telnet_user_size_handler(guac_user* user, int width, int height, int monitors) { /* Get terminal */ guac_client* client = user->client; diff --git a/src/protocols/vnc/input.c b/src/protocols/vnc/input.c index 04c80cf0a..4dea0a4ac 100644 --- a/src/protocols/vnc/input.c +++ b/src/protocols/vnc/input.c @@ -65,7 +65,7 @@ int guac_vnc_user_key_handler(guac_user* user, int keysym, int pressed) { } #ifdef LIBVNC_HAS_SIZE_MSG -int guac_vnc_user_size_handler(guac_user* user, int width, int height) { +int guac_vnc_user_size_handler(guac_user* user, int width, int height, int monitors) { guac_user_log(user, GUAC_LOG_TRACE, "Running user size handler.");