diff --git a/src/protocols/rdp/channels/disp.c b/src/protocols/rdp/channels/disp.c index f0463b95b..3edbb86a7 100644 --- a/src/protocols/rdp/channels/disp.c +++ b/src/protocols/rdp/channels/disp.c @@ -224,6 +224,23 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, } else if (settings->resize_method == GUAC_RESIZE_DISPLAY_UPDATE) { + + /* + * Forward any requested desktop scale factor to the server so the + * remote session keeps scaling its UI across dynamic resizes. Both + * fields default to 0 (no scaling requested), which the server ignores + * per [MS-RDPEDISP]. When set, the desktop scale factor MUST be paired + * with a protocol-valid device scale factor (100, 140, or 180) or both + * are ignored. + */ + UINT32 desktop_scale_factor = 0; + UINT32 device_scale_factor = 0; + if (settings->desktop_scale_factor != 0) { + desktop_scale_factor = settings->desktop_scale_factor; + device_scale_factor = + guac_rdp_get_device_scale_factor(settings->desktop_scale_factor); + } + DISPLAY_CONTROL_MONITOR_LAYOUT monitors[1] = {{ .Flags = 0x1, /* DISPLAYCONTROL_MONITOR_PRIMARY */ .Left = 0, @@ -233,8 +250,8 @@ void guac_rdp_disp_update_size(guac_rdp_disp* disp, .PhysicalWidth = 0, .PhysicalHeight = 0, .Orientation = 0, - .DesktopScaleFactor = 0, - .DeviceScaleFactor = 0 + .DesktopScaleFactor = desktop_scale_factor, + .DeviceScaleFactor = device_scale_factor }}; /* Send display update notification if display channel is connected */ diff --git a/src/protocols/rdp/settings.c b/src/protocols/rdp/settings.c index 1a1ff97f2..064463bdd 100644 --- a/src/protocols/rdp/settings.c +++ b/src/protocols/rdp/settings.c @@ -153,6 +153,7 @@ const char* GUAC_RDP_CLIENT_ARGS[] = { "force-lossless", "normalize-clipboard", + "desktop-scale-factor", NULL }; @@ -740,6 +741,17 @@ enum RDP_ARGS_IDX { */ IDX_NORMALIZE_CLIPBOARD, + /** + * The desktop scale factor to request from the RDP server, as a percentage + * between 100 and 500. When set, the remote Windows session is asked to + * scale its UI accordingly (the equivalent of the Windows display scaling + * setting), keeping UI and text comfortably sized even when a + * high-resolution (device-pixel) framebuffer is requested. If omitted or + * set to 0, no scale factor is sent and the server uses its default (no + * scaling). + */ + IDX_DESKTOP_SCALE_FACTOR, + RDP_ARGS_COUNT }; @@ -911,6 +923,45 @@ guac_rdp_settings* guac_rdp_parse_args(guac_user* user, settings->height, settings->resolution); + /* + * Desktop scale factor to request from the RDP server, as a percentage. A + * value of 0 (the default) disables the feature entirely, preserving the + * historical behavior of never asking the server to scale its session UI. + * Any non-zero value is clamped to the range permitted by [MS-RDPBCGR] + * (100-500), as values outside that range are silently ignored by the + * server. + */ + settings->desktop_scale_factor = + guac_user_parse_args_int(user, GUAC_RDP_CLIENT_ARGS, argv, + IDX_DESKTOP_SCALE_FACTOR, 0); + + if (settings->desktop_scale_factor != 0) { + + if (settings->desktop_scale_factor < 100) { + guac_user_log(user, GUAC_LOG_WARNING, + "Requested desktop scale factor of %i%% is below the " + "minimum supported by RDP. Clamping to 100%%.", + settings->desktop_scale_factor); + settings->desktop_scale_factor = 100; + } + + else if (settings->desktop_scale_factor > 500) { + guac_user_log(user, GUAC_LOG_WARNING, + "Requested desktop scale factor of %i%% is above the " + "maximum supported by RDP. Clamping to 500%%.", + settings->desktop_scale_factor); + settings->desktop_scale_factor = 500; + } + + guac_user_log(user, GUAC_LOG_DEBUG, + "Requesting desktop scale factor of %i%% " + "(device scale factor %i%%)", + settings->desktop_scale_factor, + guac_rdp_get_device_scale_factor( + settings->desktop_scale_factor)); + + } + /* Lossless compression */ settings->lossless = guac_user_parse_args_boolean(user, GUAC_RDP_CLIENT_ARGS, argv, @@ -1544,6 +1595,27 @@ int guac_rdp_get_depth(freerdp* rdp) { #endif } +int guac_rdp_get_device_scale_factor(int desktop_scale_factor) { + + /* + * Per [MS-RDPBCGR] and [MS-RDPEDISP], the device scale factor MUST be one + * of exactly 100, 140, or 180 percent. Any other value causes both the + * device scale factor AND the accompanying desktop scale factor to be + * ignored by the server, so we always map to one of these three values. + * The thresholds mirror the behavior of native RDP clients (mstsc / + * FreeRDP): scales below 120% use 100, 120%-160% use 140, and anything + * higher uses 180. + */ + if (desktop_scale_factor < 120) + return 100; + + if (desktop_scale_factor <= 160) + return 140; + + return 180; + +} + void guac_rdp_push_settings(guac_client* client, guac_rdp_settings* guac_settings, freerdp* rdp) { @@ -1567,6 +1639,19 @@ void guac_rdp_push_settings(guac_client* client, freerdp_settings_set_string(rdp_settings, FreeRDP_AlternateShell, guac_strdup(guac_settings->initial_program)); freerdp_settings_set_uint32(rdp_settings, FreeRDP_KeyboardLayout, guac_settings->server_layout->freerdp_keyboard_layout); + /* + * Request server-side UI scaling if a desktop scale factor was specified. + * These values are sent to the server within the client core data + * ([MS-RDPBCGR] TS_UD_CS_CORE) during connection negotiation. The desktop + * scale factor MUST be paired with a protocol-valid device scale factor + * (100, 140, or 180), otherwise the server ignores both values. + */ + if (guac_settings->desktop_scale_factor != 0) { + freerdp_settings_set_uint32(rdp_settings, FreeRDP_DesktopScaleFactor, + guac_settings->desktop_scale_factor); + freerdp_settings_set_uint32(rdp_settings, FreeRDP_DeviceScaleFactor, + guac_rdp_get_device_scale_factor(guac_settings->desktop_scale_factor)); + } /* Performance flags */ /* Explicitly set flag value */ @@ -1803,6 +1888,19 @@ void guac_rdp_push_settings(guac_client* client, rdp_settings->AlternateShell = guac_strdup(guac_settings->initial_program); rdp_settings->KeyboardLayout = guac_settings->server_layout->freerdp_keyboard_layout; + /* + * Request server-side UI scaling if a desktop scale factor was specified. + * These values are sent to the server within the client core data + * ([MS-RDPBCGR] TS_UD_CS_CORE) during connection negotiation. The desktop + * scale factor MUST be paired with a protocol-valid device scale factor + * (100, 140, or 180), otherwise the server ignores both values. + */ + if (guac_settings->desktop_scale_factor != 0) { + rdp_settings->DesktopScaleFactor = guac_settings->desktop_scale_factor; + rdp_settings->DeviceScaleFactor = + guac_rdp_get_device_scale_factor(guac_settings->desktop_scale_factor); + } + /* Performance flags */ /* Explicitly set flag value */ rdp_settings->PerformanceFlags = guac_rdp_get_performance_flags(guac_settings); diff --git a/src/protocols/rdp/settings.h b/src/protocols/rdp/settings.h index 16a04e5a2..2e010176f 100644 --- a/src/protocols/rdp/settings.h +++ b/src/protocols/rdp/settings.h @@ -217,6 +217,19 @@ typedef struct guac_rdp_settings { */ int resolution; + /** + * The desktop scale factor to request from the RDP server, as a + * percentage. When set to a value between 100 and 500, the remote Windows + * session is asked to scale its UI accordingly (the equivalent of the + * Windows "Scale and layout" / display scaling setting). This value is sent + * to the server as the [MS-RDPBCGR] desktopScaleFactor (paired with a + * protocol-valid deviceScaleFactor), allowing a high-resolution + * (device-pixel) framebuffer to remain razor sharp while keeping UI and + * text at a comfortable size. A value of 0 (the default) disables the + * feature, and no scale factor is sent to the server. + */ + int desktop_scale_factor; + /** * Whether all graphical updates for this connection should use lossless * compression only. @@ -753,6 +766,25 @@ extern const char* GUAC_RDP_CLIENT_ARGS[]; void guac_rdp_push_settings(guac_client* client, guac_rdp_settings* guac_settings, freerdp* rdp); +/** + * Returns the RDP device scale factor that should accompany the given desktop + * scale factor. Per [MS-RDPBCGR] (Client Core Data) and [MS-RDPEDISP] + * (DISPLAYCONTROL_MONITOR_LAYOUT), the device scale factor MUST be exactly + * 100, 140, or 180 percent, otherwise both the device scale factor AND the + * accompanying desktop scale factor are ignored by the server. The mapping + * used here mirrors the behavior of native clients (mstsc / FreeRDP): + * a desktop scale below 120% maps to 100, 120%-160% maps to 140, and anything + * higher maps to 180. + * + * @param desktop_scale_factor + * The desktop scale factor, as a percentage (typically 100-500). + * + * @return + * The protocol-valid device scale factor (100, 140, or 180) to pair with + * the given desktop scale factor. + */ +int guac_rdp_get_device_scale_factor(int desktop_scale_factor); + /** * Returns the width of the RDP session display. *