diff --git a/compositor/main.c b/compositor/main.c index 9e9670bdf..ec5601217 100644 --- a/compositor/main.c +++ b/compositor/main.c @@ -3070,40 +3070,6 @@ load_headless_backend(struct weston_compositor *c, return 0; } -static int -rdp_backend_output_configure(struct weston_output *output) -{ - struct wet_compositor *compositor = to_wet_compositor(output->compositor); - struct wet_output_config *parsed_options = compositor->parsed_options; - const struct weston_rdp_output_api *api = weston_rdp_output_get_api(output->compositor); - int width = 640; - int height = 480; - - assert(parsed_options); - - if (!api) { - weston_log("Cannot use weston_rdp_output_api.\n"); - return -1; - } - - if (parsed_options->width) - width = parsed_options->width; - - if (parsed_options->height) - height = parsed_options->height; - - weston_output_set_scale(output, 1); - weston_output_set_transform(output, WL_OUTPUT_TRANSFORM_NORMAL); - - if (api->output_set_size(output, width, height) < 0) { - weston_log("Cannot configure output \"%s\" using weston_rdp_output_api.\n", - output->name); - return -1; - } - - return 0; -} - static void weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) { @@ -3124,6 +3090,83 @@ weston_rdp_backend_config_init(struct weston_rdp_backend_config *config) config->refresh_rate = RDP_DEFAULT_FREQ; } +static void +rdp_handle_layout(struct weston_compositor *ec) +{ + struct wet_compositor *wc = to_wet_compositor(ec); + struct wet_output_config *parsed_options = wc->parsed_options; + const struct weston_rdp_output_api *api = weston_rdp_output_get_api(ec); + struct weston_rdp_monitor config; + struct weston_head *head = NULL; + int width; + int height; + int scale = 1; + + while ((head = weston_compositor_iterate_heads(ec, head))) { + struct weston_output *output = head->output; + struct weston_mode new_mode = {}; + + assert(output); + + api->head_get_monitor(head, &config); + + width = config.width; + height = config.height; + scale = config.desktop_scale / 100; + + /* If these are invalid, the backend is expecting + * us to provide defaults. + */ + width = width ? width : parsed_options->width; + height = height ? height : parsed_options->height; + scale = scale ? scale : parsed_options->scale; + + /* Fallback to 640 x 480 if we have nothing to use */ + width = width ? width : 640; + height = height ? height : 480; + scale = scale ? scale : 1; + + new_mode.width = width; + new_mode.height = height; + api->output_set_mode(output, &new_mode); + + weston_output_set_scale(output, scale); + weston_output_set_transform(output, + WL_OUTPUT_TRANSFORM_NORMAL); + weston_output_move(output, config.x, config.y); + } +} + +static void +rdp_heads_changed(struct wl_listener *listener, void *arg) +{ + struct weston_compositor *compositor = arg; + struct wet_compositor *wet = to_wet_compositor(compositor); + struct weston_head *head = NULL; + + while ((head = weston_compositor_iterate_heads(compositor, head))) { + if (head->output) + continue; + + struct weston_output *out; + + out = weston_compositor_create_output(compositor, + head, head->name); + + wet_head_tracker_create(wet, head); + weston_output_attach_head(out, head); + } + + rdp_handle_layout(compositor); + + while ((head = weston_compositor_iterate_heads(compositor, head))) { + if (!head->output->enabled) + weston_output_enable(head->output); + + weston_head_reset_device_changed(head); + } +} + static int load_rdp_backend(struct weston_compositor *c, int *argc, char *argv[], struct weston_config *wc, @@ -3133,8 +3176,9 @@ load_rdp_backend(struct weston_compositor *c, struct weston_config_section *section; int ret = 0; bool no_remotefx_codec = false; - struct wet_output_config *parsed_options = wet_init_parsed_options(c); + struct wet_compositor *wet = to_wet_compositor(c); + if (!parsed_options) return -1; @@ -3151,6 +3195,7 @@ load_rdp_backend(struct weston_compositor *c, { WESTON_OPTION_STRING, "rdp4-key", 0, &config.rdp_key }, { WESTON_OPTION_STRING, "rdp-tls-cert", 0, &config.server_cert }, { WESTON_OPTION_STRING, "rdp-tls-key", 0, &config.server_key }, + { WESTON_OPTION_INTEGER, "scale", 0, &parsed_options->scale }, { WESTON_OPTION_BOOLEAN, "force-no-compression", 0, &config.force_no_compression }, { WESTON_OPTION_BOOLEAN, "no-remotefx-codec", 0, &no_remotefx_codec }, }; @@ -3159,12 +3204,15 @@ load_rdp_backend(struct weston_compositor *c, config.remotefx_codec = !no_remotefx_codec; config.renderer = renderer; - wet_set_simple_head_configurator(c, rdp_backend_output_configure); section = weston_config_get_section(wc, "rdp", NULL, NULL); weston_config_section_get_int(section, "refresh-rate", &config.refresh_rate, RDP_DEFAULT_FREQ); + wet->heads_changed_listener.notify = rdp_heads_changed; + weston_compositor_add_heads_changed_listener(c, + &wet->heads_changed_listener); + ret = weston_compositor_load_backend(c, WESTON_BACKEND_RDP, &config.base); diff --git a/include/libweston/backend-rdp.h b/include/libweston/backend-rdp.h index eea013c15..00616507b 100644 --- a/include/libweston/backend-rdp.h +++ b/include/libweston/backend-rdp.h @@ -33,16 +33,26 @@ extern "C" { #include #include -#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v1" +#define WESTON_RDP_OUTPUT_API_NAME "weston_rdp_output_api_v2" #define RDP_DEFAULT_FREQ 60 +struct weston_rdp_monitor { + int32_t x; + int32_t y; + int32_t width; + int32_t height; + uint32_t desktop_scale; +}; + struct weston_rdp_output_api { - /** Initialize a RDP output with specified width and height. - * - * Returns 0 on success, -1 on failure. + /** Get config from RDP client when connected */ - int (*output_set_size)(struct weston_output *output, - int width, int height); + void (*head_get_monitor)(struct weston_head *head, + struct weston_rdp_monitor *monitor); + + /** Set mode for an output */ + void (*output_set_mode)(struct weston_output *base, + struct weston_mode *mode); }; static inline const struct weston_rdp_output_api * diff --git a/libweston/backend-rdp/meson.build b/libweston/backend-rdp/meson.build index 2c057066e..9b2ed0301 100644 --- a/libweston/backend-rdp/meson.build +++ b/libweston/backend-rdp/meson.build @@ -28,6 +28,7 @@ deps_rdp = [ srcs_rdp = [ 'rdp.c', 'rdpclip.c', + 'rdpdisp.c', 'rdputil.c', ] diff --git a/libweston/backend-rdp/rdp.c b/libweston/backend-rdp/rdp.c index d48d4bf94..6c0611a79 100644 --- a/libweston/backend-rdp/rdp.c +++ b/libweston/backend-rdp/rdp.c @@ -54,6 +54,9 @@ extern PWtsApiFunctionTable FreeRDP_InitWtsApi(void); +static BOOL +xf_peer_adjust_monitor_layout(freerdp_peer *client); + static struct rdp_output * rdp_get_first_output(struct rdp_backend *b) { @@ -378,80 +381,78 @@ ensure_single_mode(struct weston_output *output, struct weston_mode *target) return new_mode; } -static int -rdp_switch_mode(struct weston_output *output, struct weston_mode *target_mode) +static void +rdp_output_set_mode(struct weston_output *base, struct weston_mode *mode) { - struct rdp_output *rdpOutput = container_of(output, struct rdp_output, base); - struct rdp_backend *rdpBackend = to_rdp_backend(output->compositor); + struct rdp_output *rdpOutput = container_of(base, struct rdp_output, base); + struct rdp_backend *b = to_rdp_backend(base->compositor); + struct weston_mode *cur; + struct weston_output *output = base; struct rdp_peers_item *rdpPeer; rdpSettings *settings; pixman_image_t *new_shadow_buffer; - struct weston_mode *local_mode; - assert(output); + mode->refresh = b->rdp_monitor_refresh_rate; + cur = ensure_single_mode(base, mode); - local_mode = ensure_single_mode(output, target_mode); - if (local_mode == output->current_mode) - return 0; + base->current_mode = cur; + base->native_mode = cur; + if (base->enabled) { + weston_renderer_resize_output(output, &(struct weston_size){ + .width = output->current_mode->width, + .height = output->current_mode->height }, NULL); - output->current_mode = local_mode; - output->current_mode->flags |= WL_OUTPUT_MODE_CURRENT; + new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, mode->width, + mode->height, 0, mode->width * 4); + pixman_image_composite32(PIXMAN_OP_SRC, rdpOutput->shadow_surface, 0, new_shadow_buffer, + 0, 0, 0, 0, 0, 0, mode->width, mode->height); + pixman_image_unref(rdpOutput->shadow_surface); + rdpOutput->shadow_surface = new_shadow_buffer; + } - weston_renderer_resize_output(output, &(struct weston_size){ - .width = output->current_mode->width, - .height = output->current_mode->height }, NULL); - - new_shadow_buffer = pixman_image_create_bits(PIXMAN_x8r8g8b8, target_mode->width, - target_mode->height, 0, target_mode->width * 4); - pixman_image_composite32(PIXMAN_OP_SRC, rdpOutput->shadow_surface, 0, new_shadow_buffer, - 0, 0, 0, 0, 0, 0, target_mode->width, target_mode->height); - pixman_image_unref(rdpOutput->shadow_surface); - rdpOutput->shadow_surface = new_shadow_buffer; - - wl_list_for_each(rdpPeer, &rdpBackend->peers, link) { + /* Apparently settings->DesktopWidth is supposed to be primary only. + * For now we only work with a single monitor, so we don't need to + * check that we're primary here. + */ + wl_list_for_each(rdpPeer, &b->peers, link) { settings = rdpPeer->peer->context->settings; - if (settings->DesktopWidth == (UINT32)target_mode->width && - settings->DesktopHeight == (UINT32)target_mode->height) + if (settings->DesktopWidth == (uint32_t)mode->width && + settings->DesktopHeight == (uint32_t)mode->height) continue; if (!settings->DesktopResize) { /* too bad this peer does not support desktop resize */ + weston_log("desktop resize is not allowed\n"); rdpPeer->peer->Close(rdpPeer->peer); } else { - settings->DesktopWidth = target_mode->width; - settings->DesktopHeight = target_mode->height; + settings->DesktopWidth = mode->width; + settings->DesktopHeight = mode->height; rdpPeer->peer->context->update->DesktopResize(rdpPeer->peer->context); } } - return 0; } static int -rdp_output_set_size(struct weston_output *base, - int width, int height) +rdp_output_switch_mode(struct weston_output *base, struct weston_mode *mode) { - struct rdp_output *output = to_rdp_output(base); - struct rdp_backend *rdpBackend = to_rdp_backend(base->compositor); - struct weston_mode *currentMode; - struct weston_mode initMode; - - assert(output); - - /* We can only be called once. */ - assert(!output->base.current_mode); - - initMode.width = width; - initMode.height = height; - initMode.refresh = rdpBackend->rdp_monitor_refresh_rate; - currentMode = ensure_single_mode(&output->base, &initMode); - currentMode->flags |= WL_OUTPUT_MODE_CURRENT; - - output->base.current_mode = output->base.native_mode = currentMode; - + rdp_output_set_mode(base, mode); return 0; } +static void +rdp_head_get_monitor(struct weston_head *base, + struct weston_rdp_monitor *monitor) +{ + struct rdp_head *h = to_rdp_head(base); + + monitor->x = h->config.x; + monitor->y = h->config.y; + monitor->width = h->config.width; + monitor->height = h->config.height; + monitor->desktop_scale = h->config.attributes.desktopScaleFactor; +} + static int rdp_output_enable(struct weston_output *base) { @@ -536,7 +537,7 @@ rdp_output_create(struct weston_compositor *compositor, const char *name) output->base.start_repaint_loop = rdp_output_start_repaint_loop; output->base.repaint = rdp_output_repaint; - output->base.switch_mode = rdp_switch_mode; + output->base.switch_mode = rdp_output_switch_mode; weston_compositor_add_pending_output(&output->base, compositor); @@ -1040,31 +1041,27 @@ xf_peer_activate(freerdp_peer* client) peerCtx->audio_in_private = b->audio_in_setup(b->compositor, peerCtx->vcm); } - if (output->base.width != (int)settings->DesktopWidth || - output->base.height != (int)settings->DesktopHeight) - { - if (b->no_clients_resize) { - /* RDP peers don't dictate their resolution to weston */ + /* If we don't allow resize, we need to tell the client to resize itself. + * We still need the xf_peer_adjust_monitor_layout() call to make sure + * we've set up scaling appropriately. + */ + if (b->no_clients_resize) { + struct weston_mode *mode = output->base.current_mode; + + if (mode->width != (int)settings->DesktopWidth || + mode->height != (int)settings->DesktopHeight) { if (!settings->DesktopResize) { /* peer does not support desktop resize */ - weston_log("%s: client doesn't support resizing, closing connection\n", __FUNCTION__); + weston_log("client doesn't support resizing, closing connection\n"); return FALSE; } else { - settings->DesktopWidth = output->base.width; - settings->DesktopHeight = output->base.height; + settings->DesktopWidth = mode->width; + settings->DesktopHeight = mode->height; client->context->update->DesktopResize(client->context); } - } else { - /* ask weston to adjust size */ - struct weston_mode new_mode; - struct weston_mode *target_mode; - new_mode.width = (int)settings->DesktopWidth; - new_mode.height = (int)settings->DesktopHeight; - target_mode = ensure_single_mode(&output->base, &new_mode); - weston_output_mode_set_native(&output->base, target_mode, 1); - output->base.width = new_mode.width; - output->base.height = new_mode.height; } + } else { + xf_peer_adjust_monitor_layout(client); } weston_output = &output->base; @@ -1144,6 +1141,36 @@ xf_peer_post_connect(freerdp_peer *client) return TRUE; } +static bool +rdp_translate_and_notify_mouse_position(RdpPeerContext *peerContext, UINT16 x, UINT16 y) +{ + struct timespec time; + int sx, sy; + + if (!peerContext->item.seat) + return FALSE; + + /* (TS_POINTERX_EVENT):The xy-coordinate of the pointer relative to the top-left + * corner of the server's desktop combined all monitors */ + + /* first, convert the coordinate based on primary monitor's upper-left as (0,0) */ + sx = x + peerContext->desktop_left; + sy = y + peerContext->desktop_top; + + /* translate client's x/y to the coordinate in weston space. */ + /* TODO: to_weston_coordinate() is translate based on where pointer is, + not based-on where/which window underneath. Thus, this doesn't + work when window lays across more than 2 monitors and each monitor has + different scaling. In such case, hit test to that window area on + non primary-resident monitor (surface->output) dosn't work. */ + if (to_weston_coordinate(peerContext, &sx, &sy)) { + weston_compositor_get_time(&time); + notify_motion_absolute(peerContext->item.seat, &time, sx, sy); + return TRUE; + } + return FALSE; +} + static void dump_mouseinput(RdpPeerContext *peerContext, UINT16 flags, UINT16 x, UINT16 y, bool is_ex) { @@ -1282,7 +1309,6 @@ static BOOL xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) { RdpPeerContext *peerContext = (RdpPeerContext *)input->context; - struct rdp_output *output; uint32_t button = 0; bool need_frame = false; struct timespec time; @@ -1296,13 +1322,8 @@ xf_mouseEvent(rdpInput *input, UINT16 flags, UINT16 x, UINT16 y) * the RDP client. */ if (!(flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))) { - output = rdp_get_first_output(peerContext->rdpBackend); - if (x < output->base.width && y < output->base.height) { - weston_compositor_get_time(&time); - notify_motion_absolute(peerContext->item.seat, &time, - x, y); + if (rdp_translate_and_notify_mouse_position(peerContext, x, y)) need_frame = true; - } } if (flags & PTR_FLAGS_BUTTON1) @@ -1524,6 +1545,85 @@ xf_suppress_output(rdpContext *context, BYTE allow, const RECTANGLE_16 *area) return TRUE; } +static BOOL +xf_peer_adjust_monitor_layout(freerdp_peer *client) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + rdpSettings *settings = client->context->settings; + rdpMonitor *monitors; + unsigned int monitor_count; + BOOL success; + bool fallback = false; + unsigned int i; + + rdp_debug(b, "%s:\n", __func__); + rdp_debug(b, " DesktopWidth:%d, DesktopHeight:%d\n", settings->DesktopWidth, settings->DesktopHeight); + rdp_debug(b, " UseMultimon:%d\n", settings->UseMultimon); + rdp_debug(b, " ForceMultimon:%d\n", settings->ForceMultimon); + rdp_debug(b, " MonitorCount:%d\n", settings->MonitorCount); + rdp_debug(b, " HasMonitorAttributes:%d\n", settings->HasMonitorAttributes); + rdp_debug(b, " HiDefRemoteApp:%d\n", settings->HiDefRemoteApp); + + if (settings->MonitorCount > 1) { + weston_log("multiple monitor is not supported"); + fallback = true; + } + + if (b->no_clients_resize) + fallback = true; + + if (settings->MonitorCount > RDP_MAX_MONITOR) { + weston_log("Client reports more monitors then expected:(%d)\n", + settings->MonitorCount); + return FALSE; + } + if ((settings->MonitorCount > 0 && settings->MonitorDefArray) && !fallback) { + rdpMonitor *rdp_monitor = settings->MonitorDefArray; + monitor_count = settings->MonitorCount; + monitors = xmalloc(sizeof(*monitors) * monitor_count); + for (i = 0; i < monitor_count; i++) { + monitors[i] = rdp_monitor[i]; + if (!settings->HasMonitorAttributes) { + monitors[i].attributes.physicalWidth = 0; + monitors[i].attributes.physicalHeight = 0; + monitors[i].attributes.orientation = ORIENTATION_LANDSCAPE; + monitors[i].attributes.desktopScaleFactor = 100; + monitors[i].attributes.deviceScaleFactor = 100; + } + } + } else { + monitor_count = 1; + monitors = xmalloc(sizeof(*monitors) * monitor_count); + /* when no monitor array provided, generate from desktop settings */ + monitors[0].x = 0; + monitors[0].y = 0; + monitors[0].width = settings->DesktopWidth; + monitors[0].height = settings->DesktopHeight; + monitors[0].is_primary = 1; + monitors[0].attributes.physicalWidth = settings->DesktopPhysicalWidth; + monitors[0].attributes.physicalHeight = settings->DesktopPhysicalHeight; + monitors[0].attributes.orientation = settings->DesktopOrientation; + monitors[0].attributes.desktopScaleFactor = settings->DesktopScaleFactor; + monitors[0].attributes.deviceScaleFactor = settings->DeviceScaleFactor; + monitors[0].orig_screen = 0; + + if (b->no_clients_resize) { + /* If we're not allowing clients to resize us, set these + * to 0 so the front end knows it needs to make something + * up. + */ + monitors[0].width = 0; + monitors[0].height = 0; + monitors[0].attributes.desktopScaleFactor = 0; + } + } + success = handle_adjust_monitor_layout(client, monitor_count, monitors); + + free(monitors); + return success; +} + static int rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) { @@ -1576,6 +1676,11 @@ rdp_peer_init(freerdp_peer *client, struct rdp_backend *b) client->PostConnect = xf_peer_post_connect; client->Activate = xf_peer_activate; + if (!b->no_clients_resize) { + settings->SupportMonitorLayoutPdu = TRUE; + client->AdjustMonitorsLayout = xf_peer_adjust_monitor_layout; + } + client->context->update->SuppressOutput = (pSuppressOutput)xf_suppress_output; input = client->context->input; @@ -1648,7 +1753,8 @@ rdp_incoming_peer(freerdp_listener *instance, freerdp_peer *client) } static const struct weston_rdp_output_api api = { - rdp_output_set_size, + rdp_head_get_monitor, + rdp_output_set_mode, }; static struct rdp_backend * diff --git a/libweston/backend-rdp/rdp.h b/libweston/backend-rdp/rdp.h index a1684b237..0177f6b4b 100644 --- a/libweston/backend-rdp/rdp.h +++ b/libweston/backend-rdp/rdp.h @@ -50,6 +50,7 @@ #include "shared/string-helpers.h" #define MAX_FREERDP_FDS 32 +#define RDP_MAX_MONITOR 16 #define DEFAULT_AXIS_STEP_DISTANCE 10 #define DEFAULT_PIXEL_FORMAT PIXEL_FORMAT_BGRA32 @@ -116,6 +117,7 @@ struct rdp_peers_item { struct rdp_head { struct weston_head base; uint32_t index; + bool matched; rdpMonitor config; }; @@ -162,6 +164,10 @@ struct rdp_peer_context { struct rdp_clipboard_data_source *clipboard_inflight_client_data_source; struct wl_listener clipboard_selection_listener; + + /* Multiple monitor support (monitor topology) */ + int32_t desktop_top, desktop_left; + int32_t desktop_width, desktop_height; }; typedef struct rdp_peer_context RdpPeerContext; @@ -192,6 +198,15 @@ struct rdp_loop_task { #define rdp_debug_clipboard_continue(b, ...) \ rdp_debug_print(b->clipboard_debug, true, __VA_ARGS__) +/* rdpdisp.c */ +bool +handle_adjust_monitor_layout(freerdp_peer *client, + int monitor_count, rdpMonitor *monitors); + +struct weston_output * +to_weston_coordinate(RdpPeerContext *peerContext, + int32_t *x, int32_t *y); + /* rdputil.c */ void rdp_debug_print(struct weston_log_scope *log_scope, bool cont, char *fmt, ...); diff --git a/libweston/backend-rdp/rdpdisp.c b/libweston/backend-rdp/rdpdisp.c new file mode 100644 index 000000000..8c719350d --- /dev/null +++ b/libweston/backend-rdp/rdpdisp.c @@ -0,0 +1,356 @@ +/* + * Copyright © 2020 Microsoft + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rdp.h" + +#include "shared/xalloc.h" + +static bool +match_primary(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (a->is_primary && b->is_primary) + return true; + + return false; +} + +static bool +match_dimensions(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + int scale_a = a->attributes.desktopScaleFactor; + int scale_b = b->attributes.desktopScaleFactor; + + + if (a->width != b->width || + a->height != b->height || + scale_a != scale_b) + return false; + + return true; +} + +static bool +match_position(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (a->x != b->x || + a->y != b->y) + return false; + + return true; +} + +static bool +match_exact(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + if (match_dimensions(rdp, a, b) && + match_position(rdp, a, b)) + return true; + + return false; +} + +static bool +match_any(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b) +{ + return true; +} + +static void +update_head(struct rdp_backend *rdp, struct rdp_head *head, rdpMonitor *config) +{ + struct weston_mode mode = {}; + int scale; + bool changed = false; + + head->matched = true; + scale = config->attributes.desktopScaleFactor / 100; + scale = scale ? scale : 1; + + if (!match_position(rdp, &head->config, config)) + changed = true; + + if (!match_dimensions(rdp, &head->config, config)) { + mode.flags = WL_OUTPUT_MODE_PREFERRED; + mode.width = config->width; + mode.height = config->height; + mode.refresh = rdp->rdp_monitor_refresh_rate; + weston_output_mode_set_native(head->base.output, + &mode, scale); + changed = true; + } + + if (changed) { + weston_head_set_device_changed(&head->base); + } + head->config = *config; +} + +static void +match_heads(struct rdp_backend *rdp, rdpMonitor *config, uint32_t count, + int *done, + bool (*cmp)(struct rdp_backend *rdp, rdpMonitor *a, rdpMonitor *b)) +{ + struct weston_head *iter; + struct rdp_head *current; + uint32_t i; + + wl_list_for_each(iter, &rdp->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (current->matched) + continue; + + for (i = 0; i < count; i++) { + if (*done & (1 << i)) + continue; + + if (cmp(rdp, ¤t->config, &config[i])) { + *done |= 1 << i; + update_head(rdp, current, &config[i]); + break; + } + } + } +} + +static void +disp_layout_change(freerdp_peer *client, rdpMonitor *config, UINT32 monitorCount) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + struct rdp_backend *b = peerCtx->rdpBackend; + struct rdp_head *current; + struct weston_head *iter, *tmp; + pixman_region32_t desktop; + int done = 0; + + assert_compositor_thread(b); + + pixman_region32_init(&desktop); + + /* Prune heads that were never enabled, and flag heads as unmatched */ + wl_list_for_each_safe(iter, tmp, &b->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (!iter->output) { + rdp_head_destroy(iter); + continue; + } + current->matched = false; + } + + /* We want the primary head to remain primary - it + * should always be rdp-0. + */ + match_heads(b, config, monitorCount, &done, match_primary); + + /* Look for any exact match */ + match_heads(b, config, monitorCount, &done, match_exact); + + /* Match first head with the same dimensions */ + match_heads(b, config, monitorCount, &done, match_dimensions); + + /* Match head with the same position */ + match_heads(b, config, monitorCount, &done, match_position); + + /* Pick any available head */ + match_heads(b, config, monitorCount, &done, match_any); + + /* Destroy any heads we won't be using */ + wl_list_for_each_safe(iter, tmp, &b->compositor->head_list, compositor_link) { + current = to_rdp_head(iter); + if (!current->matched) + rdp_head_destroy(iter); + } + + + for (uint32_t i = 0; i < monitorCount; i++) { + /* accumulate monitor layout */ + pixman_region32_union_rect(&desktop, &desktop, + config[i].x, + config[i].y, + config[i].width, + config[i].height); + + /* Create new heads for any without matches */ + if (!(done & (1 << i))) + rdp_head_create(b->compositor, &config[i]); + } + peerCtx->desktop_left = desktop.extents.x1; + peerCtx->desktop_top = desktop.extents.y1; + peerCtx->desktop_width = desktop.extents.x2 - desktop.extents.x1; + peerCtx->desktop_height = desktop.extents.y2 - desktop.extents.y1; + pixman_region32_fini(&desktop); +} + +static bool +disp_sanity_check_layout(RdpPeerContext *peerCtx, rdpMonitor *config, uint32_t count) +{ + struct rdp_backend *b = peerCtx->rdpBackend; + uint32_t primaryCount = 0; + uint32_t i; + + /* dump client monitor topology */ + rdp_debug(b, "%s:---INPUT---\n", __func__); + for (i = 0; i < count; i++) { + int scale = config[i].attributes.desktopScaleFactor / 100; + + rdp_debug(b, " rdpMonitor[%d]: x:%d, y:%d, width:%d, height:%d, is_primary:%d\n", + i, config[i].x, config[i].y, + config[i].width, config[i].height, + config[i].is_primary); + rdp_debug(b, " rdpMonitor[%d]: physicalWidth:%d, physicalHeight:%d, orientation:%d\n", + i, config[i].attributes.physicalWidth, + config[i].attributes.physicalHeight, + config[i].attributes.orientation); + rdp_debug(b, " rdpMonitor[%d]: desktopScaleFactor:%d, deviceScaleFactor:%d\n", + i, config[i].attributes.desktopScaleFactor, + config[i].attributes.deviceScaleFactor); + + rdp_debug(b, " rdpMonitor[%d]: scale:%d\n", + i, scale); + } + + for (i = 0; i < count; i++) { + /* make sure there is only one primary and its position at client */ + if (config[i].is_primary) { + /* count number of primary */ + if (++primaryCount > 1) { + weston_log("%s: RDP client reported unexpected primary count (%d)\n", + __func__, primaryCount); + return false; + } + /* primary must be at (0,0) in client space */ + if (config[i].x != 0 || config[i].y != 0) { + weston_log("%s: RDP client reported primary is not at (0,0) but (%d,%d).\n", + __func__, config[i].x, config[i].y); + return false; + } + } + } + return true; +} + +bool +handle_adjust_monitor_layout(freerdp_peer *client, int monitor_count, rdpMonitor *monitors) +{ + RdpPeerContext *peerCtx = (RdpPeerContext *)client->context; + + if (!disp_sanity_check_layout(peerCtx, monitors, monitor_count)) + return true; + + disp_layout_change(client, monitors, monitor_count); + + return true; +} + +static bool +rect_contains(int32_t px, int32_t py, int32_t rx, int32_t ry, + int32_t width, int32_t height) +{ + if (px < rx) + return false; + if (py < ry) + return false; + if (px >= rx + width) + return false; + if (py >= ry + height) + return false; + + return true; +} + +static bool +rdp_head_contains(struct rdp_head *rdp_head, int32_t x, int32_t y) +{ + rdpMonitor *config = &rdp_head->config; + + /* If we're forcing RDP desktop size then we don't have + * useful information in the monitor structs, but we + * can rely on the output settings in that case. + */ + if (config->width == 0) { + struct weston_head *head = &rdp_head->base; + struct weston_output *output = head->output; + + if (!output) + return false; + + return rect_contains(x, y, output->x, output->y, + output->width * output->scale, + output->height * output->scale); + } + + return rect_contains(x, y, config->x, config->y, + config->width, config->height); +} + +/* Input x/y in client space, output x/y in weston space */ +struct weston_output * +to_weston_coordinate(RdpPeerContext *peerContext, int32_t *x, int32_t *y) +{ + struct rdp_backend *b = peerContext->rdpBackend; + int sx = *x, sy = *y; + struct weston_head *head_iter; + + /* First, find which monitor contains this x/y. */ + wl_list_for_each(head_iter, &b->compositor->head_list, compositor_link) { + struct rdp_head *head = to_rdp_head(head_iter); + + assert(head); + + if (rdp_head_contains(head, sx, sy)) { + struct weston_output *output = head->base.output; + float scale = 1.0f / head->base.output->scale; + + sx -= head->config.x; + sy -= head->config.y; + /* scale x/y to client output space. */ + sx *= scale; + sy *= scale; + /* translate x/y to offset of this output in weston space. */ + sx += output->x; + sy += output->y; + rdp_debug_verbose(b, "%s: (x:%d, y:%d) -> (sx:%d, sy:%d) at head:%s\n", + __func__, *x, *y, sx, sy, head->base.name); + *x = sx; + *y = sy; + return output; + } + } + /* x/y is outside of any monitors. */ + return NULL; +}